UWP Composition API - GroupListView(一)
需求:
光看標(biāo)題大家肯定不知道是什么東西,先上效果圖:
這不就是ListView的Group效果嗎?? 看上去是的。但是請(qǐng)聽完需求.
1.Group中的集合需要支持增量加載ISupportIncrementalLoading
2.支持UI Virtualization
oh,no。ListView 自帶的Group都不支持這2個(gè)需求。好吧,只有靠自己擼Code了。。
實(shí)現(xiàn)前思考:
仔細(xì)想了下,其實(shí)要解決的主要問題有2個(gè)
數(shù)據(jù)源的處理 和 GroupHeader的UI的處理
1.數(shù)據(jù)源的處理?
?因?yàn)橹霸趯?UWP VirtualizedVariableSizedGridView 支持可虛擬化可變大小Item的View的時(shí)候已經(jīng)做過這種處理源的工作了,所以方案出來的比較快。
不管有幾個(gè)group,其實(shí)當(dāng)?shù)?個(gè)hasMore等false的時(shí)候,我們就可以加載第2個(gè)group里面的集合。
我為此寫了一個(gè)類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{//nextif (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是個(gè)接口。
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的屬性(注意請(qǐng)給FirstIndex和LastIndex賦值-1的初始值)
比如:在效果圖中,如果只有全部評(píng)論,沒有精彩評(píng)論,那么后面的導(dǎo)航的按鈕是應(yīng)該不現(xiàn)實(shí)的,所以我加了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));}}}數(shù)據(jù)源的處理還是比較簡(jiǎn)單的。
?2.GroupHeader的UI的處理
首先我想到的是加一個(gè)Grid,然后這些GroupHeader放在里面,通過ScrollViewer的ViewChanged來處理它們。
比較了下ListView的Group效果,Scrollbar是會(huì)擋住GroupHeader的,所以我把這個(gè)Grid放進(jìn)了ScrollViewer的模板里面。
GroupListView的模板,這里大家可以看到我加入了個(gè)ProgressRing,這個(gè)是后面做導(dǎo)航功能需要的,后面再講。
<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>下面就是實(shí)現(xiàn)對(duì)GroupHeader顯示的控制了。
很快代碼寫好了。。運(yùn)行起來效果還可以。。但是童鞋們說。。你這個(gè)跟Composition API 一毛錢關(guān)系都沒有啊。。
大家別急。。聽我說。。模擬器里面運(yùn)行還行,拿實(shí)體機(jī)器上運(yùn)行的時(shí)候,當(dāng)我快速向上或者向下滑動(dòng)的時(shí)候,GroupHeader會(huì)出現(xiàn)頓一頓的感覺,卡一下,不會(huì)有慣性的感覺。
看到這個(gè),我立馬明白了。。不管是ViewChanging或者ViewChanged事件,它們跟Manipulation都不是同步的。
看了上一盤?UWP Composition API - PullToRefresh的童鞋會(huì)說,好吧,隱藏的真深。
那我們還是用Composition API來建立GroupHeader和ScrollViewer之間的關(guān)系。
1.首先我想的是,當(dāng)進(jìn)入Viewport再用Composition API來建立關(guān)系,但是很快被我否決了。還是因?yàn)閂iewChanged這個(gè)事件是有慣性的原因,這樣沒法讓創(chuàng)建GroupHeader和ScrollViewer之間的關(guān)系的初始數(shù)據(jù)完全準(zhǔn)確。
就是說GroupHeader因?yàn)槌跏紨?shù)據(jù)不正確的情況會(huì)造成沒放在我想要的位置,只有當(dāng)慣性停止的時(shí)候獲取的位置信息才是準(zhǔn)確的。
在PrepareContainerForItemOverride中判斷是否GroupHeader 的那個(gè)Item已經(jīng)準(zhǔn)備添加到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方法里面去設(shè)置Header的狀態(tài)
internal void UpdateGroupHeaders(bool isIntermediate = true){var firstVisibleItemIndex = this.GetFirstVisibleIndex();foreach (var item in groupDic){//top headerif (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 headerelse{HandleGroupHeader(isIntermediate, item);}}} View Code這里我簡(jiǎn)單說下幾種狀態(tài):
1. 在ItemsPanel里面
1)全部在Viewport里面
動(dòng)畫開啟,Clip設(shè)置為Null
2)部分在Viewport里面
動(dòng)畫開啟,并且設(shè)置Clip
3)沒有在viewport里面
動(dòng)畫開啟,Visible 設(shè)置為Collapsed
2. 沒有在ItemsPanel里面
動(dòng)畫停止。
關(guān)于GroupHeader初始狀態(tài)的設(shè)置,這里是最坑的,遇到很多問題。
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 frameexpression.SetReferenceParameter("ScrollViewerManipProps", scrollViewerManipProps);}visual.StartAnimation("Offset.Y", expression);IsActive = true;//Windows.UI.Xaml.Media.CompositionTarget.Rendering -= OnCompositionTargetRendering;//Windows.UI.Xaml.Media.CompositionTarget.Rendering += OnCompositionTargetRendering;}注釋掉了的代碼是處理:
當(dāng)GroupHeader進(jìn)入Viewport的時(shí)候才啟動(dòng)動(dòng)畫,離開之后就關(guān)閉動(dòng)畫,表達(dá)式就是一個(gè)限制,這個(gè)就不講了。
expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y + VerticalOffset");可以看到我給表達(dá)式加了一個(gè)VericalOffset。。嗯。其實(shí)Visual的Offset是表示 Visual 相對(duì)于其父 Visual 的位置偏移量。
舉2個(gè)例子,整個(gè)Viewport的高度是500,現(xiàn)在滾動(dòng)條的VericalOffset是100。
1.如果我想把Header(header高度為50)放到Viewport的最下面(Header剛好全部進(jìn)入Viewport),那么初始的參數(shù)應(yīng)該是哪些呢?
Header.Margin = new Thickness(450);
Header.Clip=null;
expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +100");
這樣向上滾ScrollViewerManipProps.Translation.Y(-450),Header 就會(huì)滾Viewport的頂部。
2.如果我想把Header(header高度為50)放到Viewport的最下面(Header剛好一半全部進(jìn)入Viewport),那么初始的參數(shù)應(yīng)該是哪些呢?
Header.Margin = new Thickness(475);
Header.Clip=new RectangleGeometry() { Rect = new Rect(0, 0, this.ActualWidth, 25) };
expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +100");
當(dāng)向上或者向下滾動(dòng)的時(shí)候,記得更新Clip值就可以了。
說到為什么要加Clip,因?yàn)槿绻愕目丶皇钦麄€(gè)Page大小的時(shí)候,這個(gè)Header會(huì)顯示到控件外部去,大家應(yīng)該都是懂得。
這里說下這個(gè)里面碰到一個(gè)問題。當(dāng)GroupHeader Viewport之外的時(shí)候(在Grid之外的,Margin大于Grid的高度)創(chuàng)建動(dòng)畫,會(huì)發(fā)現(xiàn)你怎么修改Header屬性都是沒有效果的。
最終結(jié)果的是不會(huì)在屏幕上顯示任何東西。
實(shí)驗(yàn)了下用Canvas發(fā)現(xiàn)就可以了,但是Grid卻不行,是不是可以認(rèn)為Visual在創(chuàng)建的時(shí)候如果對(duì)象不在它父容器的Size范圍之內(nèi),創(chuàng)建出來都是看不見的??
這個(gè)希望懂得童鞋能留言告訴一下。
把ScrollViewer模板里面的Grid換成Canvas就好了。。
剩下的都是一些計(jì)算,計(jì)算位置,計(jì)算大小變化。
最后就是GoToGroup方法,當(dāng)跳轉(zhuǎn)的Group沒有l(wèi)oad出來的時(shí)候(也就是FirstIndex還沒有值得時(shí)候),我們就Load,Load,Load,直到
它有值,這個(gè)可能是個(gè)長的時(shí)間過程,所以加了ProgressRing,找到Index,最后用ListView的API來跳轉(zhuǎn)就好了。
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 topvar 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;}}}}?總結(jié):
這個(gè)控件做下來,基本上都是在計(jì)算計(jì)算計(jì)算。。當(dāng)然也知道了一些Composition API的東西。
其實(shí)Vistual的屬性還有很多,在做這個(gè)控件的時(shí)候沒有用到,以后用到了會(huì)繼續(xù)分享的。?開源有益,源碼GitHub地址。
UWP Composition API - GroupListView(二)
Visual?元素有些基本的呈現(xiàn)相關(guān)屬性,這些屬性都能使用 Composition API 的動(dòng)畫 API 來演示動(dòng)畫。
-
Opacity?
表示 Visual 的透明度。 -
Offset?
表示 Visual 相對(duì)于其父 Visual 的位置偏移量。 -
Clip?
表示 Visual 裁剪區(qū)域。 -
CenterPoint?
表示 Visual 的中心點(diǎn)。 -
TransformMatrix?
表示 Visual 的變換矩陣。 -
Size?
表示 Visual 的尺寸大小。 -
Scale?
表示 Visual 的縮放大小。 -
RotationAxis?
表示 Visual 的旋轉(zhuǎn)軸。 -
RotationAngle?
表示 Visual 的旋轉(zhuǎn)角度。
有 4 個(gè)類派生自 Visual,他們分別對(duì)應(yīng)了不同種類的 Visual,分別是:
-
ContainerVisual?
表示容器 Visual,可能有子節(jié)點(diǎn)的 Visual,大部分的 XAML 可視元素基本都是該 Visual,其他的 Visual 都也是派生自該類。 -
EffectVisual?
表示通過特效來呈現(xiàn)內(nèi)容的 Visual,可以通過配合 Win2D 的支持 Composition 的 Effects 來呈現(xiàn)豐富多彩的內(nèi)容。 -
ImageVisual?
表示通過圖片來呈現(xiàn)內(nèi)容的 Visual,可以用于呈現(xiàn)圖片。 -
SolidColorVisual
表示一個(gè)純色矩形的 Visual 元素
-
轉(zhuǎn)載于:https://www.cnblogs.com/FaDeKongJian/p/5629715.html
總結(jié)
以上是生活随笔為你收集整理的UWP Composition API - GroupListView(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 源码解析--count
- 下一篇: webform 转 MVC 飞一般的感觉