UWP Composition API - GroupListView(一)

简介: 原文:UWP Composition API - GroupListView(一)需求: 光看标题大家肯定不知道是什么东西,先上效果图: 这不就是ListView的Group效果吗?? 看上去是的。
原文: UWP Composition API - GroupListView(一)

需求:

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

这不就是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 元素

目录
相关文章
|
1月前
|
缓存 JavaScript 算法
活用 Composition API 核心函数,打造卓越应用(下)
活用 Composition API 核心函数,打造卓越应用(下)
|
1月前
|
存储 JavaScript API
活用 Composition API 核心函数,打造卓越应用(上)
活用 Composition API 核心函数,打造卓越应用(上)
|
1月前
|
JavaScript 前端开发 API
深入浅出:Vue 3 Composition API 的魅力与实践
【2月更文挑战第13天】 本文将探索 Vue 3 的核心特性之一——Composition API。通过对比 Options API,本文旨在揭示 Composition API 如何提高代码的组织性和可复用性,并通过实际案例展示其在现代前端开发中的应用。不同于传统的技术文章摘要,我们将通过一个具体的开发场景,引领读者步入 Composition API 的世界,展现它如何优雅地解决复杂组件逻辑的管理问题,从而激发读者探索和运用 Vue 3 新特性的热情。
19 1
|
1月前
|
JavaScript 前端开发 API
深入浅出:Vue 3 Composition API 的魅力
【2月更文挑战第13天】 在前端开发的世界里,Vue.js 一直占据着重要的地位。随着 Vue 3 的推出,Composition API 成为了开发者热议的焦点。本文将从一个独特的视角探讨 Composition API 的核心优势,通过对比 Options API,解析其如何优化代码组织和提升项目的可维护性。我们将通过实际案例,深入理解 Composition API 的使用方法和最佳实践,帮助开发者更好地把握这一新工具,激发前端开发的无限可能。
|
1月前
|
JavaScript API
vue 3.0 所采用的 Composition Api 和 vue 2.0 使用的 Option Api 区别
vue 3.0 所采用的 Composition Api 和 vue 2.0 使用的 Option Api 区别
29 0
|
17天前
|
JavaScript API
Composition Api 与 Options Api 有什么区别?
Composition Api 与 Options Api 有什么区别?
12 0
|
1月前
|
JavaScript 前端开发 API
深入浅出Vue 3 Composition API:重塑前端开发范式
【2月更文挑战第12天】 本文旨在深入探讨Vue 3中的Composition API,一种全新的组件和逻辑复用方式。相较于传统的Options API,Composition API提供了更为灵活和高效的代码组织机制。通过实例和对比分析,我们将揭示其如何优化代码结构,提升项目的可维护性和扩展性。文章不仅为初学者铺平进入Vue 3世界的道路,也为有经验的开发者提供了深度思考的视角,探索前端开发的新范式。
28 2
|
1月前
|
JavaScript 前端开发 API
深入浅出:Vue 3 Composition API 的革新之旅
【2月更文挑战第11天】本文将带你深入探索 Vue 3 中的 Composition API,一项革命性的特性,旨在提高代码的组织性和可复用性。我们将通过实际案例,对比传统的 Options API,深入理解 Composition API 如何优化组件逻辑的组织和重用,从而让前端开发变得更加高效和灵活。文章不仅仅是技术指南,更是对前端开发模式思考的一次探索之旅。
|
2月前
|
JavaScript API
vue 3.0 所采用的 Composition Api 和 vue 2.0 使用的 Option Api 区别
vue 3.0 所采用的 Composition Api 和 vue 2.0 使用的 Option Api 区别
16 0
|
2月前
|
JavaScript 安全 API
深入理解Vue 3中的Composition API
深入理解Vue 3中的Composition API
41 3

热门文章

最新文章