zoukankan      html  css  js  c++  java
  • UWP Composition API

    需求:

    光看标题大家肯定不知道是什么东西,先上效果图:

    这不就是ListView的Group效果吗?? 看上去是的。但是请听完需求.
    1.Group中的集合需要支持增量加载ISupportIncrementalLoading

    2.支持UI Virtualization

    oh,no。ListView 自带的Group都不支持这2个需求。好吧,只有靠自己撸Code了。。

    实现前思考:

    仔细想了下,其实要解决的主要问题有2个
    数据源的处理 和 GroupHeader的UI的处理

    1.数据源的处理 

     因为之前在写 UWP VirtualizedVariableSizedGridView 支持可虚拟化可变大小Item的View的时候已经做过这种处理源的工作了,所以方案出来的比较快。

    不管有几个group,其实当第1个hasMore等false的时候,我们就可以加载第2个group里面的集合。

    我为此写了一个类GroupObservableCollection<T> 它是继承 ObservableCollection<T>, IGroupCollection

            public class GroupObservableCollection<T> : ObservableCollection<T>, IGroupCollection
        {
            private List<IList<T>> souresList;
    
            private List<int> firstIndexInEachGroup = new List<int>();
            private List<IGroupHeader> groupHeaders;
    
            bool _isLoadingMoreItems = false;
    
            public GroupObservableCollection(List<IList<T>> souresList, List<IGroupHeader> groupHeaders)
            {
                this.souresList = souresList;
                this.groupHeaders = groupHeaders;
            }
    
            public bool HasMoreItems
            {
                get
                {
                    if (CurrentGroupIndex < souresList.Count)
                    {
                        var source = souresList[currentGroupIndex];
                        if (source is ISupportIncrementalLoading)
                        {
                            if (!(source as ISupportIncrementalLoading).HasMoreItems)
                            {
                                if (!_isLoadingMoreItems)
                                {
                                    if (this.Count < GetSourceListTotoalCount())
                                    {
                                        int count = 0;
                                        int preCount = this.Count;
                                        foreach (var item in souresList)
                                        {
                                            foreach (var item1 in item)
                                            {
                                                if (count >= preCount)
                                                {
                                                    this.Add(item1);
                                                    if (item == source && groupHeaders[currentGroupIndex].FirstIndex==-1)
                                                    {
                                                        groupHeaders[currentGroupIndex].FirstIndex = this.Count - 1;
                                                    }
                                                }
                                                count++;
                                            }
                                        }
                                    }
    
                                    groupHeaders[currentGroupIndex].LastIndex = this.Count - 1;
    
                                    return false;
                                }
                                else
                                {
                                    return true;
                                }
                            }
                            else
                            {
                                return true;
                            }
                        }
                        else
                        {
                            if (CurrentGroupIndex == source.Count - 1)
                            {
                                if (this.Count < GetSourceListTotoalCount())
                                {
                                    int count = 0;
                                    int preCount = this.Count;
                                    foreach (var item in souresList)
                                    {
                                        foreach (var item1 in item)
                                        {
                                            if (count >= preCount)
                                            {
                                                this.Add(item1);
                                                if (item == source && groupHeaders[currentGroupIndex].FirstIndex == -1)
                                                {
                                                    groupHeaders[currentGroupIndex].FirstIndex = this.Count - 1;
                                                }
                                            }
                                            count++;
                                        }
                                    }
                                }
                                groupHeaders[currentGroupIndex].LastIndex = this.Count - 1;
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                }
            }
    
            int GetSourceListTotoalCount()
            {
                int i = 0;
                foreach (var item in souresList)
                {
                    i += item.Count;
                }
                return i;
            }
    
            public List<int> FirstIndexInEachGroup
            {
                get
                {
                    return firstIndexInEachGroup;
                }
    
                set
                {
                    firstIndexInEachGroup = value;
                }
            }
    
            public List<IGroupHeader> GroupHeaders
            {
                get
                {
                    return groupHeaders;
                }
    
                set
                {
                    groupHeaders = value;
                }
            }
    
            public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
            {
                return FetchItems(count).AsAsyncOperation();
            }
    
            private int currentGroupIndex;
            public int CurrentGroupIndex
            {
                get
                {
                    int count = 0;
    
                    for (int i = 0; i < souresList.Count; i++)
                    {
                        var source = souresList[i];
                        count += source.Count;
                        if (count > this.Count)
                        {
                            currentGroupIndex = i;
                            return currentGroupIndex;
                        }
                        else if (count == this.Count)
                        {
                            currentGroupIndex = i;
                            if ((source is ISupportIncrementalLoading))
                            {
                                if (!(source as ISupportIncrementalLoading).HasMoreItems)
                                {
                                    if (!_isLoadingMoreItems)
                                    {
                                        groupHeaders[i].LastIndex = this.Count - 1;
                                        if (currentGroupIndex + 1 < souresList.Count)
                                        {
                                            currentGroupIndex = i + 1;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                //next
                                if (currentGroupIndex + 1 < souresList.Count)
                                {
                                    currentGroupIndex = i + 1;
                                }
                            }
    
                            return currentGroupIndex;
                        }
                        else
                        {
                            continue;
                        }
                    }
                    currentGroupIndex = 0;
                    return currentGroupIndex;
                }
            }
    
            private async Task<LoadMoreItemsResult> FetchItems(uint count)
            {
                var source = souresList[CurrentGroupIndex];
    
                if (source is ISupportIncrementalLoading)
                {
                    int firstIndex = 0;
                    if (groupHeaders[currentGroupIndex].FirstIndex != -1)
                    {
                        firstIndex = source.Count;
                    }
                    _isLoadingMoreItems = true;
                    var result = await (source as ISupportIncrementalLoading).LoadMoreItemsAsync(count);
    
                    for (int i = firstIndex; i < source.Count; i++)
                    {
                        this.Add(source[i]);
                        if (i == 0)
                        {
                            groupHeaders[currentGroupIndex].FirstIndex = this.Count - 1;
                        }
                    }
                    _isLoadingMoreItems = false;
                    return result;
                }
                else
                {
                    int firstIndex = 0;
                    if (groupHeaders[currentGroupIndex].FirstIndex != -1)
                    {
                        firstIndex = source.Count;
                    }
                    for (int i = firstIndex; i < source.Count; i++)
                    {
                        this.Add(source[i]);
                        if (i == 0)
                        {
                            groupHeaders[currentGroupIndex].FirstIndex = this.Count - 1;
                        }
                    }
                    groupHeaders[currentGroupIndex].LastIndex = this.Count - 1;
    
                    return new LoadMoreItemsResult() { Count = (uint)source.Count };
                }
            }
        }
    View Code

    而IGroupCollection是个接口。

        public interface IGroupCollection: ISupportIncrementalLoading
        {
            List<IGroupHeader> GroupHeaders { get; set; }
            int CurrentGroupIndex { get; }
        }
    
        public interface IGroupHeader
        {
            string Name { get; set; }
            int FirstIndex { get; set; }
            int LastIndex { get; set; }
            double Height { get; set; }
        }
    
        public class DefaultGroupHeader : IGroupHeader
        {
            public string Name { get; set; }
            public int FirstIndex { get; set; }
            public int LastIndex { get; set; }
            public double Height { get; set; }
            public DefaultGroupHeader()
            {
                FirstIndex = -1;
                LastIndex = -1;
            }
        }

    IGroupHeader 是用来描述Group header的,你可以继承它,添加一些绑定GroupHeader的属性(注意请给FirstIndex和LastIndex赋值-1的初始值)

    比如:在效果图中,如果只有全部评论,没有精彩评论,那么后面的导航的按钮是应该不现实的,所以我加了GoToButtonVisibility属性来控制。

        public class MyGroupHeader : IGroupHeader, INotifyPropertyChanged
        {
            public string Name { get; set; }
            public int FirstIndex { get; set; }
            public int LastIndex { get; set; }
            public double Height { get; set; }
            public string GoTo { get; set; }
            private Visibility _goToButtonVisibility = Visibility.Collapsed;
    
            public Visibility GoToButtonVisibility
            {
                get { return _goToButtonVisibility; }
                set
                {
                    _goToButtonVisibility = value;
                    OnPropertyChanged("GoToButtonVisibility");
                }
            }
    
            public MyGroupHeader()
            {
                FirstIndex = -1;
                LastIndex = -1;
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            void OnPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

    数据源的处理还是比较简单的。

     2.GroupHeader的UI的处理

    首先我想到的是加一个Grid,然后这些GroupHeader放在里面,通过ScrollViewer的ViewChanged来处理它们。

    比较了下ListView的Group效果,Scrollbar是会挡住GroupHeader的,所以我把这个Grid放进了ScrollViewer的模板里面。

    GroupListView的模板,这里大家可以看到我加入了个ProgressRing,这个是后面做导航功能需要的,后面再讲。

     <ControlTemplate TargetType="local:GroupListView">
                        <Grid BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                            <ScrollViewer x:Name="ScrollViewer" Style="{StaticResource GroupListViewScrollViewer}" AutomationProperties.AccessibilityView="Raw" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" TabNavigation="{TemplateBinding TabNavigation}" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}">
                                <ItemsPresenter FooterTransitions="{TemplateBinding FooterTransitions}" FooterTemplate="{TemplateBinding FooterTemplate}" Footer="{TemplateBinding Footer}" HeaderTemplate="{TemplateBinding HeaderTemplate}" Header="{TemplateBinding Header}" HeaderTransitions="{TemplateBinding HeaderTransitions}" Padding="{TemplateBinding Padding}"/>
                            </ScrollViewer>
                            <ProgressRing x:Name="ProgressRing" Visibility="Collapsed" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Grid>
                    </ControlTemplate>

    ScrollViewer的模板

                           <Grid Background="{TemplateBinding Background}">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <ScrollContentPresenter x:Name="ScrollContentPresenter" Grid.ColumnSpan="2" ContentTemplate="{TemplateBinding ContentTemplate}" Margin="{TemplateBinding Padding}" Grid.RowSpan="2"/>
                                <Grid x:Name="GroupHeadersCanvas" Grid.RowSpan="2" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
                                <ContentControl x:Name="TopGroupHeader" Grid.RowSpan="2" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
                                <ScrollBar x:Name="VerticalScrollBar" Grid.Column="1" HorizontalAlignment="Right" IsTabStop="False" Maximum="{TemplateBinding ScrollableHeight}" Orientation="Vertical" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{TemplateBinding VerticalOffset}" ViewportSize="{TemplateBinding ViewportHeight}"/>
                                <ScrollBar x:Name="HorizontalScrollBar" IsTabStop="False" Maximum="{TemplateBinding ScrollableWidth}" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{TemplateBinding HorizontalOffset}" ViewportSize="{TemplateBinding ViewportWidth}"/>
                                <Border x:Name="ScrollBarSeparator" Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}" Grid.Column="1" Grid.Row="1"/>
                            </Grid>

    下面就是实现对GroupHeader显示的控制了。

    很快代码写好了。。运行起来效果还可以。。但是童鞋们说。。你这个跟Composition API 一毛钱关系都没有啊。。

    大家别急。。听我说。。模拟器里面运行还行,拿实体机器上运行的时候,当我快速向上或者向下滑动的时候,GroupHeader会出现顿一顿的感觉,卡一下,不会有惯性的感觉。

    看到这个,我立马明白了。。不管是ViewChanging或者ViewChanged事件,它们跟Manipulation都不是同步的。

    看了上一盘 UWP Composition API - PullToRefresh的童鞋会说,好吧,隐藏的真深。

    那我们还是用Composition API来建立GroupHeader和ScrollViewer之间的关系。

    1.首先我想的是,当进入Viewport再用Composition API来建立关系,但是很快被我否决了。还是因为ViewChanged这个事件是有惯性的原因,这样没法让创建GroupHeader和ScrollViewer之间的关系的初始数据完全准确。

    就是说GroupHeader因为初始数据不正确的情况会造成没放在我想要的位置,只有当惯性停止的时候获取的位置信息才是准确的。

    在PrepareContainerForItemOverride中判断是否GroupHeader 的那个Item已经准备添加到ItemsPanel里面。

             protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
            {
                base.PrepareContainerForItemOverride(element, item);
                ListViewItem listViewItem = element as ListViewItem;
                listViewItem.SizeChanged -= ListViewItem_SizeChanged;
                if (listViewItem.Tag == null)
                {
                    defaultListViewItemMargin = listViewItem.Margin;
                }
    
                if (groupCollection != null)
                {
                    var index = IndexFromContainer(element);
                    var group = groupCollection.GroupHeaders.FirstOrDefault(x => x.FirstIndex == index || x.LastIndex == index);
                    if (group != null)
                    {
                        if (!groupDic.ContainsKey(group))
                        {
                            ContentControl groupheader = CreateGroupHeader(group);
                            ContentControl tempGroupheader = CreateGroupHeader(group);
    
                            ExpressionAnimationItem expressionAnimationItem = new ExpressionAnimationItem();
                            expressionAnimationItem.VisualElement = groupheader;
                            expressionAnimationItem.TempElement = tempGroupheader;
    
                            groupDic[group] = expressionAnimationItem;
    
                            var temp = new Dictionary<IGroupHeader, ExpressionAnimationItem>();
                            foreach (var keyValue in groupDic.OrderBy(x => x.Key.FirstIndex))
                            {
                                temp[keyValue.Key] = keyValue.Value;
                            }
                            groupDic = temp;
                            if (groupHeadersCanvas != null)
                            {
                                groupHeadersCanvas.Children.Add(groupheader);
                                groupHeadersCanvas.Children.Add(tempGroupheader);
    
                                groupheader.Measure(new Windows.Foundation.Size(this.ActualWidth, this.ActualHeight));
    
                                group.Height = groupheader.DesiredSize.Height;
    
                                groupheader.Height = tempGroupheader.Height = group.Height;
                                groupheader.Width = tempGroupheader.Width = this.ActualWidth;
    
                                if (group.FirstIndex == index)
                                {
                                    listViewItem.Tag = listViewItem.Margin;
                                    listViewItem.Margin = GetItemMarginBaseOnDeafult(groupheader.DesiredSize.Height);
                                    listViewItem.SizeChanged += ListViewItem_SizeChanged;
                                }
    
                                groupheader.Visibility = Visibility.Collapsed;
                                tempGroupheader.Visibility = Visibility.Collapsed;
                                UpdateGroupHeaders();
                            }
    
                        }
                        else
                        {
                            if (group.FirstIndex == index)
                            {
                                listViewItem.Tag = listViewItem.Margin;
                                listViewItem.Margin = GetItemMarginBaseOnDeafult(group.Height);
                                listViewItem.SizeChanged += ListViewItem_SizeChanged;
                            }
                            else
                            {
                                listViewItem.Margin = defaultListViewItemMargin;
                            }
                        }
    
                    }
                    else
                    {
                        listViewItem.Margin = defaultListViewItemMargin;
                    }
                }
                else
                {
                    listViewItem.Margin = defaultListViewItemMargin;
                }
            }
    View Code

    在UpdateGroupHeader方法里面去设置Header的状态

            internal void UpdateGroupHeaders(bool isIntermediate = true)
            {
                var firstVisibleItemIndex = this.GetFirstVisibleIndex();
                foreach (var item in groupDic)
                {
                    //top header
                    if (item.Key.FirstIndex <= firstVisibleItemIndex && (firstVisibleItemIndex <= item.Key.LastIndex || item.Key.LastIndex == -1))
                    {
                        currentTopGroupHeader.Visibility = Visibility.Visible;
                        currentTopGroupHeader.Margin = new Thickness(0);
                        currentTopGroupHeader.Clip = null;
                        currentTopGroupHeader.DataContext = item.Key;
    
                        if (item.Key.FirstIndex == firstVisibleItemIndex)
                        {
                            if (item.Value.ScrollViewer == null)
                            {
                                item.Value.ScrollViewer = scrollViewer;
                            }
    
                            var isActive = item.Value.IsActive;
    
    
                            item.Value.StopAnimation();
                            item.Value.VisualElement.Clip = null;
                            item.Value.VisualElement.Visibility = Visibility.Collapsed;
    
                            if (!isActive)
                            {
                                if (!isIntermediate)
                                {
                                    item.Value.VisualElement.Margin = new Thickness(0);
                                    item.Value.StartAnimation(true);
                                }
                            }
                            else
                            {
                                item.Value.StartAnimation(false);
                            }
    
                        }
                        ClearTempElement(item);
                    }
                    //moving header
                    else
                    {
                        HandleGroupHeader(isIntermediate, item);
                    }
                }
            }
    View Code

    这里我简单说下几种状态:
    1. 在ItemsPanel里面

    1)全部在Viewport里面

    动画开启,Clip设置为Null

    2)部分在Viewport里面

    动画开启,并且设置Clip
    3)没有在viewport里面

    动画开启,Visible 设置为Collapsed
    2. 没有在ItemsPanel里面

    动画停止。

    关于GroupHeader初始状态的设置,这里是最坑的,遇到很多问题。

            public void StartAnimation(bool update = false)
            {
    
                if (update || expression == null || visual == null)
                {
                    visual = ElementCompositionPreview.GetElementVisual(VisualElement);
                    //if (0 <= VisualElement.Margin.Top && VisualElement.Margin.Top <= ScrollViewer.ActualHeight)
                    //{
                    //    min = (float)-VisualElement.Margin.Top;
                    //    max = (float)ScrollViewer.ActualHeight + min;
                    //}
                    //else if (VisualElement.Margin.Top < 0)
                    //{
    
                    //}
                    //else if (VisualElement.Margin.Top > ScrollViewer.ActualHeight)
                    //{
    
                    //}
                    if (scrollViewerManipProps == null)
                    {
                        scrollViewerManipProps = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(ScrollViewer);
                    }
                    Compositor compositor = scrollViewerManipProps.Compositor;
    
                    // Create the expression
                    //expression = compositor.CreateExpressionAnimation("min(max((ScrollViewerManipProps.Translation.Y + VerticalOffset), MinValue), MaxValue)");
                    ////Expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +VerticalOffset");
    
                    //expression.SetScalarParameter("MinValue", min);
                    //expression.SetScalarParameter("MaxValue", max);
                    //expression.SetScalarParameter("VerticalOffset", (float)ScrollViewer.VerticalOffset);
    
                    expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y + VerticalOffset");
                    ////Expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +VerticalOffset");
    
                    //expression.SetScalarParameter("MinValue", min);
                    //expression.SetScalarParameter("MaxValue", max);
                    VerticalOffset = ScrollViewer.VerticalOffset;
                    expression.SetScalarParameter("VerticalOffset", (float)ScrollViewer.VerticalOffset);
    
                    // set "dynamic" reference parameter that will be used to evaluate the current position of the scrollbar every frame
                    expression.SetReferenceParameter("ScrollViewerManipProps", scrollViewerManipProps);
    
                }
    
    
    
                visual.StartAnimation("Offset.Y", expression);
    
                IsActive = true;
                //Windows.UI.Xaml.Media.CompositionTarget.Rendering -= OnCompositionTargetRendering;
    
                //Windows.UI.Xaml.Media.CompositionTarget.Rendering += OnCompositionTargetRendering;
            }

    注释掉了的代码是处理:

    当GroupHeader进入Viewport的时候才启动动画,离开之后就关闭动画,表达式就是一个限制,这个就不讲了。

    expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y + VerticalOffset");

    可以看到我给表达式加了一个VericalOffset。。嗯。其实Visual的Offset是表示 Visual 相对于其父 Visual 的位置偏移量。

    举2个例子,整个Viewport的高度是500,现在滚动条的VericalOffset是100。

    1.如果我想把Header(header高度为50)放到Viewport的最下面(Header刚好全部进入Viewport),那么初始的参数应该是哪些呢?

    Header.Margin = new Thickness(450);

    Header.Clip=null;

    expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +100");

    这样向上滚ScrollViewerManipProps.Translation.Y(-450),Header 就会滚Viewport的顶部。


    2.如果我想把Header(header高度为50)放到Viewport的最下面(Header刚好一半全部进入Viewport),那么初始的参数应该是哪些呢?

    Header.Margin = new Thickness(475);

    Header.Clip=new RectangleGeometry() { Rect = new Rect(0, 0, this.ActualWidth, 25) };

    expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +100");

    当向上或者向下滚动的时候,记得更新Clip值就可以了。

    说到为什么要加Clip,因为如果你的控件不是整个Page大小的时候,这个Header会显示到控件外部去,大家应该都是懂得。

    这里说下这个里面碰到一个问题。当GroupHeader Viewport之外的时候(在Grid之外的,Margin大于Grid的高度)创建动画,会发现你怎么修改Header属性都是没有效果的。

    最终结果的是不会在屏幕上显示任何东西。

    实验了下用Canvas发现就可以了,但是Grid却不行,是不是可以认为Visual在创建的时候如果对象不在它父容器的Size范围之内,创建出来都是看不见的??

    这个希望懂得童鞋能留言告诉一下。

    把ScrollViewer模板里面的Grid换成Canvas就好了。。

    剩下的都是一些计算,计算位置,计算大小变化。

    最后就是GoToGroup方法,当跳转的Group没有load出来的时候(也就是FirstIndex还没有值得时候),我们就Load,Load,Load,直到

    它有值,这个可能是个长的时间过程,所以加了ProgressRing,找到Index,最后用ListView的API来跳转就好了。

            public async Task GoToGroupAsync(int groupIndex, ScrollIntoViewAlignment scrollIntoViewAlignment = ScrollIntoViewAlignment.Leading)
            {
                if (groupCollection != null)
                {
                    var gc = groupCollection;
                    if (groupIndex < gc.GroupHeaders.Count && groupIndex >= 0 && !isGotoGrouping)
                    {
                        isGotoGrouping = true;
                        //load more so that ScrollIntoViewAlignment.Leading can go to top
                        var loadcount = this.GetVisibleItemsCount() + 1;
    
                        progressRing.IsActive = true;
                        progressRing.Visibility = Visibility.Visible;
                        //make sure user don't do any other thing at the time.
                        this.IsHitTestVisible = false;
                        //await Task.Delay(3000);
                        while (gc.GroupHeaders[groupIndex].FirstIndex == -1)
                        {
                            if (gc.HasMoreItems)
                            {
                                await gc.LoadMoreItemsAsync(loadcount);
                            }
                            else
                            {
                                break;
                            }
                        }
    
                        if (gc.GroupHeaders[groupIndex].FirstIndex != -1)
                        {
                            //make sure there are enought items to go ScrollIntoViewAlignment.Leading
                            //this.count > (firstIndex + loadcount)
                            if (scrollIntoViewAlignment == ScrollIntoViewAlignment.Leading)
                            {
                                var more = this.Items.Count - (gc.GroupHeaders[groupIndex].FirstIndex + loadcount);
                                if (gc.HasMoreItems && more < 0)
                                {
                                    await gc.LoadMoreItemsAsync((uint)Math.Abs(more));
                                }
                            }
                            progressRing.IsActive = false;
                            progressRing.Visibility = Visibility.Collapsed;
                            var groupFirstIndex = gc.GroupHeaders[groupIndex].FirstIndex;
                            ScrollIntoView(this.Items[groupFirstIndex], scrollIntoViewAlignment);
                            //already in viewport, maybe it will not change view 
                            if (groupDic.ContainsKey(gc.GroupHeaders[groupIndex]) && groupDic[gc.GroupHeaders[groupIndex]].Visibility == Visibility.Visible)
                            {
                                this.IsHitTestVisible = true;
                                isGotoGrouping = false;
                            }
                        }
                        else
                        {
                            this.IsHitTestVisible = true;
                            isGotoGrouping = false;
                            progressRing.IsActive = false;
                            progressRing.Visibility = Visibility.Collapsed;
                        }
    
                    }
                }
            }

     总结:

    这个控件做下来,基本上都是在计算计算计算。。当然也知道了一些Composition API的东西。

    其实Vistual的属性还有很多,在做这个控件的时候没有用到,以后用到了会继续分享的。 开源有益,源码GitHub地址

    UWP Composition API - GroupListView(二)

    Visual 元素有些基本的呈现相关属性,这些属性都能使用 Composition API 的动画 API 来演示动画。

    • Opacity 
      表示 Visual 的透明度。

    • Offset 
      表示 Visual 相对于其父 Visual 的位置偏移量。

    • Clip 
      表示 Visual 裁剪区域。

    • CenterPoint 
      表示 Visual 的中心点。

    • TransformMatrix 
      表示 Visual 的变换矩阵。

    • Size 
      表示 Visual 的尺寸大小。

    • Scale 
      表示 Visual 的缩放大小。

    • RotationAxis 
      表示 Visual 的旋转轴。

    • RotationAngle 
      表示 Visual 的旋转角度。

    有 4 个类派生自 Visual,他们分别对应了不同种类的 Visual,分别是:

      • ContainerVisual 
        表示容器 Visual,可能有子节点的 Visual,大部分的 XAML 可视元素基本都是该 Visual,其他的 Visual 都也是派生自该类。

      • EffectVisual 
        表示通过特效来呈现内容的 Visual,可以通过配合 Win2D 的支持 Composition 的 Effects 来呈现丰富多彩的内容。

      • ImageVisual 
        表示通过图片来呈现内容的 Visual,可以用于呈现图片。

      • SolidColorVisual
        表示一个纯色矩形的 Visual 元素

  • 相关阅读:
    yb课堂 vue里面的状态管理vuex 《四十》
    yb课堂 注册-登陆-个人模块 《三十九》
    MySQL 获取所有表名、所有表结构
    Window版 MySQL可视化工具 Navicat 面安装免激活绿色版
    yb课堂 视频详情页模块开发《三十八》
    yb课堂 首页home开发 《三十七》
    yb课堂 前端项目通用底部选项卡 CommonsFooter 《三十六》
    yb课堂 开发前端项目路由 《三十五》
    yb课堂 基于浏览器和node.js的http客户端Axios 《三十四》
    2020 团体程序设计天梯赛 个人题解 + 反思
  • 原文地址:https://www.cnblogs.com/FaDeKongJian/p/5629715.html
Copyright © 2011-2022 走看看