• UWP中实现大爆炸效果(二)


    上一回实现了一个宽度不均匀的Panel,这次我们编写一个简单的BigbangView主体。

    首先创建一个模板化控件,删掉Themes/Generic.xaml中的<Style TargetType="BigbangView">...</Style>段。

    然后打开C:Program Files (x86)Windows Kits10DesignTimeCommonConfigurationNeutralUAP(SDK版本)Genericgeneric.xaml,在里面找到

    <Style TargetType="ListViewItem" x:Key="ListViewItemExpanded">
    ...
    </Style>
    <Style TargetType="ListView">
    ...
    </Style>

    这两段,复制到项目中Themes/Generic.xaml中,将TargetType="ListView"修改为TargetType="BigbangView",添加Setter:

    <Setter Property="SelectionMode" Value="Multiple"></Setter>
    <Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
    <Setter Property="VerticalAlignment" Value="Center"></Setter>
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="TabNavigation" Value="Once" />
    <Setter Property="IsSwipeEnabled" Value="True" />
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
    <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled" />
    <Setter Property="ScrollViewer.IsHorizontalRailEnabled" Value="False" />
    <Setter Property="ScrollViewer.VerticalScrollMode" Value="Enabled" />
    <Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="True" />
    <Setter Property="ScrollViewer.ZoomMode" Value="Disabled" />
    <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" />
    <Setter Property="ScrollViewer.BringIntoViewOnFocusChange" Value="True" />
    <Setter Property="UseSystemFocusVisuals" Value="True" />
    <Setter Property="ItemContainerTransitions">
        <Setter.Value>
            <TransitionCollection>
                <AddDeleteThemeTransition />
                <ContentThemeTransition />
                <ReorderThemeTransition />
                <EntranceThemeTransition IsStaggeringEnabled="False" />
            </TransitionCollection>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="ListViewItem">
            前面复制的ListViewItemExpanded的内容剪贴到这里
            </Style>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <control:BigbangPanel >
                    <control:BigbangPanel.ChildrenTransitions>
                        <TransitionCollection>
                            <AddDeleteThemeTransition />
                        </TransitionCollection>
                    </control:BigbangPanel.ChildrenTransitions>
                </control:BigbangPanel>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    View Code

     其中BigbangPanel是咱们上回书写的面板。

    然后打开BigbangView.cs,修改基类:

    public sealed class BigbangView : ListView
    {
        public BigbangView()
        {
            this.DefaultStyleKey = typeof(BigbangView);
        }
    }
    

      

    接下来就是整个过程中最复杂,最枯燥的部分,编写按下滑动选中。

    先打开安卓版的大爆炸(不是锤子的可以拿个安卓手机下载IT之家客户端看效果),对整个过程进行分析发现,有以下几种状态。

    1、点击选中;

    2、Panel高度小于控件高度,也就是ScrollViewer不启用时,按下向四周滑动可以更改选中状态;

    3、Panel高度大于控件高度,上下滑动可以滚动ScrollViewer,左右滑动禁用ScrollViewer的滚动并且更改选中状态 。

    这篇文章先实现鼠标的操作。由于鼠标在页面上下滑动并不会触发ScrollViewer的滚动,所以在此不考虑第三项。

    首先修改Style中的Template段,

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:BigbangView">
                <Border x:Name="RootBorder" BorderBrush="{TemplateBinding BorderBrush}"
                        Background="{TemplateBinding Background}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ScrollViewer x:Name="ScrollViewer"
                        TabNavigation="{TemplateBinding TabNavigation}"
                        HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
                        HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
                        IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}"
                        VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
                        VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
                        IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}"
                        IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
                        IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
                        ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}"
                        IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
                        BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}"
                        AutomationProperties.AccessibilityView="Raw">
                        <Grid x:Name="ItemsGrid" Background="Transparent" ManipulationMode="System">
                            <ItemsPresenter
                                Header="{TemplateBinding Header}"
                                HeaderTemplate="{TemplateBinding HeaderTemplate}"
                                HeaderTransitions="{TemplateBinding HeaderTransitions}"
                                Footer="{TemplateBinding Footer}"
                                FooterTemplate="{TemplateBinding FooterTemplate}"
                                FooterTransitions="{TemplateBinding FooterTransitions}"
                                Padding="{TemplateBinding Padding}"/>
                        </Grid>
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    View Code

    在后台代码中重载OnApplyTemplate()(实际应该做空判断,我偷懒了)

    public BigbangView()
    {
        this.DefaultStyleKey = typeof(BigbangView);
        this.Loaded += BigbangView_Loaded;
        this.Unloaded += BigbangView_Unloaded;
        this.SelectionChanged += BigbangView_SelectionChanged;
    
        PointerPressedHandler = new PointerEventHandler(_PointerPressed);
        PointerReleasedHandler = new PointerEventHandler(_PointerReleased);
        PointerMovedHandler = new PointerEventHandler(_PointerMoved);
    }
    
    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        RootBorder = GetTemplateChild("RootBorder") as Border;
        ItemsGrid = GetTemplateChild("ItemsGrid") as Grid;
        ScrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer;
    
        ScrollViewer.AddHandler(UIElement.PointerPressedEvent, PointerPressedHandler, true);
        ScrollViewer.AddHandler(UIElement.PointerReleasedEvent, PointerReleasedHandler, true);
        ScrollViewer.AddHandler(UIElement.PointerCanceledEvent, PointerReleasedHandler, true);
        ScrollViewer.AddHandler(UIElement.PointerExitedEvent, PointerReleasedHandler, true);
        ScrollViewer.ViewChanging += _ScrollViewer_ViewChanging;
    }
    

      

    然后我们需要把每个子元素在Panel中的位置缓存下来,在Panel中添加属性

    private Dictionary<UIElement, Rect> _ChildrenRects;
    
    public Dictionary<UIElement, Rect> ChildrenRects
    {
        get
        {
            if (_ChildrenRects == null) _ChildrenRects = new Dictionary<UIElement, Rect>();
            return _ChildrenRects;
        }
        set => _ChildrenRects = value;
    }
    

      

    将ArrangeOverride中Children[x].Arrange(new Rect...)修改为如下

    var rect = new Rect(x, y, Children[i].DesiredSize.Width, Children[i].DesiredSize.Height);
    Children[i].Arrange(rect);
    ChildrenRects[Children[i]] = rect;
    

      

    编写以下几个辅助的方法:

    //从Item获取在Panel中的布局信息
    private Rect? GetItemRect(object Item)
    {
        var itemContainer = base.ContainerFromItem(Item) as UIElement;
        if (itemContainer != null && _Panel != null)
        {
            if (_Panel.ChildrenRects.ContainsKey(itemContainer))
            {
                return _Panel.ChildrenRects[itemContainer];
            }
        }
        return null;
    }
    
    //从容器获取Item和Index
    private int GetIndexFromContainer(UIElement Container, out object Item, IList<object> SourceList = null)
    {
        Item = ItemFromContainer(Container);
        if (Item != null)
        {
            if (SourceList == null) SourceList = GetSourceList();
            return SourceList.IndexOf(Item);
        }
        return -1;
    }
    
    //获取坐标位置的Item和Index
    private int GetIndexFromPosition(Point Position, out object Item)
    {
        var sourceList = GetSourceList();
    
        for (int i = 0; i < sourceList.Count; i++)
        {
            var rect = GetItemRect(sourceList[i]);
            if (!rect.HasValue) break;
    
            if (rect.Value.Contains(Position))
            {
                Item = sourceList[i];
                return i;
            }
        }
        Item = null;
        return -1;
    }
    
    //获取源列表
    private IList<object> GetSourceList()
    {
        if (ItemsSource == null) return Items.ToList();
        else
        {
            var tmp = new List<object>();
            foreach (var item in (IEnumerable)ItemsSource)
            {
                tmp.Add(item);
            }
            return tmp;
        }
    }
    

      

    然后编写三个状态方法:OnSelectionStart初始化各个变量的状态,获取开始的点;OnSelecting更新被选中的项,OnSelectionComplate做最后的清理,还原状态:

    int StartIndex = -1;    //本次选择开始的位置
    int EndIndex = -1;    //本次选择结束的位置
    bool? IsFirstItemHadSelected;    //本次选择是选中后续还是取消选中后续
    Point? StartPoint;    //选中开始的坐标
    UIElement StartContainer;    //选择开始时的容器
    
    private void OnSelectionStarted(Point Position)
    {
        var tmpIndex = GetIndexFromPosition(Position, out var item);
        if (tmpIndex == -1)
        {
            return;
        }
        if (StartIndex < 0)
        {
            StartIndex = tmpIndex;
            IsFirstItemHadSelected = SelectedItems.Contains(item);
            StartContainer = ContainerFromItem(item) as UIElement;
        }
    }
    
    private void OnSelecting(UIElement Container)
    {
        if (IsFirstItemHadSelected.HasValue)
        {
            var sourceList = GetSourceList();
            var tmpIndex = GetIndexFromContainer(Container, out var item, sourceList);
            if (tmpIndex == -1) return;
            if (StartIndex < 0)
            {
                StartIndex = tmpIndex;
                return;
            }
            else
            {
                EndIndex = tmpIndex;
                if (EndIndex >= 0)
                {
                    for (int i = Math.Min(StartIndex, EndIndex); i <= Math.Max(StartIndex, EndIndex); i++)
                    {
                        if (IsFirstItemHadSelected.Value)
                        {
                            if (SelectedItems.Contains(sourceList[i])) SelectedItems.Remove(sourceList[i]);
                        }
                        else
                        {
                            if (!SelectedItems.Contains(sourceList[i])) SelectedItems.Add(sourceList[i]);
                        }
    
                    }
                }
    
            }
        }
    
    }
    
    private void OnSelectionComplated()
    {
        StartIndex = -1;
        EndIndex = -1;
        IsFirstItemHadSelected = null;
        StartPoint = null;
        StartContainer = null;
    }
    

      

    然后编写事件:

    private void Container_PointerEntered(object sender, PointerRoutedEventArgs e)
    {
        if (IsFirstItemHadSelected.HasValue)
        {
            if (sender is UIElement ele && ele != StartContainer)
            {
                if (StartContainer == null)
                {
                    StartContainer = ele;
                    StartIndex = GetIndexFromContainer(StartContainer, out var item);
                }
                else
                {
                    OnSelecting(ele);
                }
            }
        }
    }
    
    private void _PointerPressed(object sender, PointerRoutedEventArgs e)
    {
        StartPoint = e.GetCurrentPoint(ItemsGrid).Position;
        OnSelectionStarted(StartPoint.Value);
        IsSwipeEnable = true;
    }
    
    private void _PointerReleased(object sender, PointerRoutedEventArgs e)
    {
        if (IsSwipeEnable.HasValue)
        {
            OnSelectionComplated();
        }
    }
    

      

    至此,BigbangView已经可以响应鼠标的滑动选择。

    下回预告:BigbangView响应触摸。

  • 相关阅读:
    【NOIP2009】【Vijos1752】潜伏者
    【NOIP2008】【Vijos1493】传纸条
    【NOIP2007】【Vijos1378】矩阵取数游戏
    【NOIP2006】【Luogu1063】能量项链
    【NOIP2004】【Luogu1091】合唱队形
    【NOIP2004】【Luogu1089】津津的储蓄计划
    【NOIP2005】【Luogu1052】过河
    【NOIP2004】【Luogu1090】合并果子
    【NOI2002】【Luogu1196】银河英雄传说(并查集带边权)
    【POJ3190】Stall Reservations
  • 原文地址:https://www.cnblogs.com/blue-fire/p/9097918.html
Copyright © 2020-2023  润新知