还是先上效果图:
看完了上一篇的童鞋会问,这不是跟上一篇一样的吗??? 骗点击的??
No,No,其实相对上一个有更简单粗暴的方案,因为上篇是为了研究Composition API,所以含着泪都要做完(有没有被骗的赶脚)。。( ╯□╰ )
那是有没有简单点的方法呢?? 嗯,看到这篇,那答案肯定是Yes。
我再啰嗦下需求:
1.Group中的集合需要支持增量加载ISupportIncrementalLoading
2.支持UI Virtualization
这个简单的方案就是改ListViewItem的模板,其实我在有讲过ListViewItem有2套模板-
看一下我修改之后的模板:
注意上图,我把默认模板里面的内容放到Grid.Row=1的Gird里面了,然后加了一个HeaderPresenter在上面。
是不是思路清晰了,就是说如果这个Item是Group的第一个,我们就给HeaderPresenter设置Header和HeaderTemplate。
这里我们需要继承ListViewItem,增加Header和HeaderTemplate 2个属性。
[TemplatePart(Name = "headerPresenter", Type = typeof(ContentPresenter))] public class GroupListViewItem : ListViewItem { ContentPresenter headerPresenter; public object Header { get { return (object)GetValue(HeaderProperty); } set { SetValue(HeaderProperty, value); } } // Using a DependencyProperty as the backing store for Header. This enables animation, styling, binding, etc... public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(object), typeof(GroupListViewItem), new PropertyMetadata(null, new PropertyChangedCallback(OnHeaderChanged))); private static void OnHeaderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as GroupListViewItem).SetHeader(); } public DataTemplate HeaderTemplate { get { return (DataTemplate)GetValue(HeaderTemplateProperty); } set { SetValue(HeaderTemplateProperty, value); } } // Using a DependencyProperty as the backing store for HeaderTemplate. This enables animation, styling, binding, etc... public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(GroupListViewItem), new PropertyMetadata(null)); public GroupListViewItem() { this.DefaultStyleKey = typeof(GroupListViewItem); } protected override void OnApplyTemplate() { base.OnApplyTemplate(); headerPresenter = GetTemplateChild("headerPresenter") as ContentPresenter; if (headerPresenter != null) { headerPresenter.RegisterPropertyChangedCallback(ContentPresenter.ContentProperty, new DependencyPropertyChangedCallback(OnHeaderPresenterContentChanged)); } else { Debug.Assert(false, "headerpresenter is missing."); } } private void OnHeaderPresenterContentChanged(DependencyObject sender, DependencyProperty dp) { if (headerPresenter.Content != Header) { headerPresenter.Content = Header; } } protected override Size ArrangeOverride(Size finalSize) { if (headerPresenter != null) { headerPresenter.Margin = new Thickness(-this.Margin.Left, -this.Margin.Top, -this.Margin.Right, this.Margin.Bottom); } return base.ArrangeOverride(finalSize); } public void ClearHeader() { Header = null; ClearValue(GroupListViewItem.HeaderTemplateProperty); } public void SetHeader() { if (headerPresenter != null) { headerPresenter.Content = Header; } } }
当然不要忘记了在里面override 下面2个方法。
protected override bool IsItemItsOwnContainerOverride(object item) { return item is GroupListViewItem; } protected override DependencyObject GetContainerForItemOverride() { return new GroupListViewItem(); }
其他就很简单了,只需要处理下置顶的Header就好了,注意红色字的部分。
private void UpdateGroupHeaders() { if (groupCollection != null) { var firstVisibleItemIndex = this.GetFirstVisibleIndex(); if (firstVisibleItemIndex < 0) { return; } foreach (var item in groupCollection.GroupHeaders) { if (item.FirstIndex == -1) { continue; } if (item.FirstIndex <= firstVisibleItemIndex && (firstVisibleItemIndex <= item.LastIndex || item.LastIndex == -1)) { currentTopGroupHeader.Visibility = Visibility.Visible; currentTopGroupHeader.Margin = new Thickness(0); currentTopGroupHeader.Clip = null; currentTopGroupHeader.DataContext = item; } else { ListViewItem listViewItem = ContainerFromIndex(item.FirstIndex) as ListViewItem; if (listViewItem == null && item.LastIndex != -1) { listViewItem = ContainerFromIndex(item.LastIndex) as ListViewItem; } if (listViewItem != null) { //handle moving header { //unloaded if (listViewItem.ActualHeight == 0 || listViewItem.ActualWidth == 0) { listViewItem.Loaded += ListViewItem_Loaded; } else { GeneralTransform gt = listViewItem.TransformToVisual(this); var rect = gt.TransformBounds(new Rect(0, 0, listViewItem.ActualWidth, listViewItem.ActualHeight)); //add delta,so that it does not look like suddenly if (rect.Bottom < 0 || rect.Top > this.ActualHeight) { } //in view port else { if (currentTopGroupHeader != null) { var delta = currentTopGroupHeader.ActualHeight - (rect.Top); if (delta > 0) { currentTopGroupHeader.Margin = new Thickness(0, -delta, 0, 0); currentTopGroupHeader.Clip = new RectangleGeometry() { Rect = new Rect(0, delta, currentTopGroupHeader.ActualWidth, currentTopGroupHeader.ActualHeight) }; if (delta >= currentTopGroupHeader.ActualHeight) { currentTopGroupHeader.Visibility = Visibility.Visible; currentTopGroupHeader.Margin = new Thickness(0); currentTopGroupHeader.Clip = null; currentTopGroupHeader.DataContext = item; } } } } } } } } } } }
查看一下里面的代码,是不是感觉比之前里面的代码简单更好理解(我的错。。。别要问我控件名字怎么这么随意 ,我想不更好的。。( ╯□╰ ))
当然这个里面也有Composition API,这样你们就不会说我是标题党了。。
因为置顶的Header是新加的东东,它不是ScrollViewer的ScrollContentPresenter(在这里我猜想,ScrollContentPresenter是做了Composition 动画的),它不会顺着ScrollViewer向下拖动的时候向下移动,它依然在它自己的位置上,有木有觉得它很孤独。。
如下图:
那么我们把它和ScrollViewer联系起来就好了。。让它和ScrollViewer一起动就好了。注意记得限定下它的值。
private void CreateVisual() { visual = ElementCompositionPreview.GetElementVisual(currentTopGroupHeader); var scrollViewerManipProps = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scrollViewer); Compositor compositor = scrollViewerManipProps.Compositor; expression = compositor.CreateExpressionAnimation("max(0,ScrollViewerManipProps.Translation.Y)"); // 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); //Windows.UI.Xaml.Media.CompositionTarget.Rendering += OnCompositionTargetRendering; }
加上之后:
好了,这个控件也讲完咯,真的没有了。
开源有益,。
其实有心的朋友也发现,原来ScrollViewer的超出界限动画,估计也是这样实现的吧??。我在网上也发现很多人问怎么才能禁掉它。
下图是,当时我就是拿ScrollViewer做的,当时找了半天也没找到禁止下面动画的方法。
找了一圈,ElementCompositeMode 这个属性有点像。微软的解释是:
没看懂。。试了下,没有用。
另外也没在ScrollViewer或者ScrollContentPresenter 上找到相关的属性。希望微软在之后能暴露相关属性,毕竟不是每个人都希望ScrollViewer有这种动画的。。
如果有童鞋已经知道怎么搞了,请留言下下,让更多的童鞋知道。万分感谢