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

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

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

然后打开C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\(SDK版本)\Generic\generic.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响应触摸。

posted on 2018-05-27 22:44 叫我蓝火火 阅读(...) 评论(...) 编辑 收藏

导航