还是先上效果图:
看完了上一篇UWP Composition API - GroupListView(一)的童鞋会问,这不是跟上一篇一样的吗??? 骗点击的??
No,No,其实相对上一个有更简单粗暴的方案,因为上篇是为了研究Composition API,所以含着泪都要做完(有没有被骗的赶脚)。。( ╯□╰ )
那是有没有简单点的方法呢?? 嗯,看到这篇,那答案肯定是Yes。
我再啰嗦下需求:
1.Group中的集合需要支持增量加载ISupportIncrementalLoading
2.支持UI Virtualization
这个简单的方案就是改ListViewItem的模板,其实我在UWP VirtualizedVariableSizedGridView 支持可虚拟化可变大小Item的View(二)有讲过ListViewItem有2套模板-官方地址
看一下我修改之后的模板:
<Style TargetType="local:GroupListViewItem" > <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" /> <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" /> <Setter Property="Background" Value="Transparent"/> <Setter Property="TabNavigation" Value="Local"/> <Setter Property="IsHoldingEnabled" Value="True"/> <Setter Property="Margin" Value="0,0,18,2"/> <Setter Property="HorizontalContentAlignment" Value="Left"/> <Setter Property="VerticalContentAlignment" Value="Top"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:GroupListViewItem"> <Border x:Name="OuterContainer"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="PointerOver"> <Storyboard> <DoubleAnimation Storyboard.TargetName="PointerOverBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectionBackground" Storyboard.TargetProperty="Fill"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedPointerOverBackgroundThemeBrush}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectedBorder" Storyboard.TargetProperty="Stroke"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedPointerOverBorderThemeBrush}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectedEarmark" Storyboard.TargetProperty="Fill"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedPointerOverBackgroundThemeBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <PointerDownThemeAnimation TargetName="ContentContainer" /> </Storyboard> </VisualState> <VisualState x:Name="PointerOverPressed"> <Storyboard> <PointerDownThemeAnimation TargetName="ContentContainer" /> <DoubleAnimation Storyboard.TargetName="PointerOverBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectionBackground" Storyboard.TargetProperty="Fill"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedPointerOverBackgroundThemeBrush}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectedBorder" Storyboard.TargetProperty="Stroke"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedPointerOverBorderThemeBrush}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectedEarmark" Storyboard.TargetProperty="Fill"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedPointerOverBackgroundThemeBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <DoubleAnimation Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Opacity" Duration="0" To="{ThemeResource ListViewItemDisabledThemeOpacity}" /> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Focused"> <Storyboard> <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisual" /> </Storyboard> </VisualState> <VisualState x:Name="Unfocused"/> <VisualState x:Name="PointerFocused"/> </VisualStateGroup> <VisualStateGroup x:Name="SelectionHintStates"> <VisualState x:Name="VerticalSelectionHint"> <Storyboard> <SwipeHintThemeAnimation TargetName="SelectionBackground" ToVerticalOffset="15" ToHorizontalOffset="0" /> <SwipeHintThemeAnimation TargetName="ContentBorder" ToVerticalOffset="15" ToHorizontalOffset="0" /> <SwipeHintThemeAnimation TargetName="SelectedBorder" ToVerticalOffset="15" ToHorizontalOffset="0" /> <SwipeHintThemeAnimation TargetName="SelectedCheckMark" ToVerticalOffset="15" ToHorizontalOffset="0" /> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="HintGlyph" Storyboard.TargetProperty="Opacity" Duration="0:0:0.500"> <DiscreteDoubleKeyFrame Value="0.5" KeyTime="0:0:0" /> <DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0.500" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="HorizontalSelectionHint"> <Storyboard> <SwipeHintThemeAnimation TargetName="SelectionBackground" ToHorizontalOffset="-23" ToVerticalOffset="0" /> <SwipeHintThemeAnimation TargetName="ContentBorder" ToHorizontalOffset="-23" ToVerticalOffset="0" /> <SwipeHintThemeAnimation TargetName="SelectedBorder" ToHorizontalOffset="-23" ToVerticalOffset="0" /> <SwipeHintThemeAnimation TargetName="SelectedCheckMark" ToHorizontalOffset="-23" ToVerticalOffset="0" /> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="HintGlyph" Storyboard.TargetProperty="Opacity" Duration="0:0:0.500"> <DiscreteDoubleKeyFrame Value="0.5" KeyTime="0:0:0" /> <DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0.500" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="NoSelectionHint" /> <VisualStateGroup.Transitions> <VisualTransition To="NoSelectionHint" GeneratedDuration="0:0:0.65"/> </VisualStateGroup.Transitions> </VisualStateGroup> <VisualStateGroup x:Name="SelectionStates"> <VisualState x:Name="Unselecting"> <Storyboard> <DoubleAnimation Storyboard.TargetName="HintGlyphBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> </Storyboard> </VisualState> <VisualState x:Name="Unselected"> <Storyboard> <DoubleAnimation Storyboard.TargetName="HintGlyphBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> </Storyboard> </VisualState> <VisualState x:Name="UnselectedPointerOver"> <Storyboard> <DoubleAnimation Storyboard.TargetName="HintGlyphBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="UnselectedSwiping"> <Storyboard> <DoubleAnimation Storyboard.TargetName="SelectingGlyph" Storyboard.TargetProperty="Opacity" Duration="0" To="0.5" /> <DoubleAnimation Storyboard.TargetName="HintGlyphBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> </Storyboard> </VisualState> <VisualState x:Name="Selecting"> <Storyboard> <DoubleAnimation Storyboard.TargetName="SelectionBackground" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="SelectedBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="SelectingGlyph" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="HintGlyphBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Selected"> <Storyboard> <DoubleAnimation Storyboard.TargetName="SelectionBackground" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="SelectedBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="SelectedCheckMark" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="SelectedSwiping"> <Storyboard> <DoubleAnimation Storyboard.TargetName="SelectionBackground" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="SelectedBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="SelectedCheckMark" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="SelectedUnfocused"> <Storyboard> <DoubleAnimation Storyboard.TargetName="SelectionBackground" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="SelectedBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="SelectedCheckMark" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="DragStates"> <VisualState x:Name="NotDragging" /> <VisualState x:Name="Dragging"> <Storyboard> <DoubleAnimation Storyboard.TargetName="InnerDragContent" Storyboard.TargetProperty="Opacity" Duration="0" To="{ThemeResource ListViewItemDragThemeOpacity}" /> <DragItemThemeAnimation TargetName="InnerDragContent" /> <FadeOutThemeAnimation TargetName="SelectedCheckMarkOuter" /> <FadeOutThemeAnimation TargetName="SelectedBorder" /> </Storyboard> </VisualState> <VisualState x:Name="DraggingTarget"> <Storyboard> <DropTargetItemThemeAnimation TargetName="OuterContainer" /> </Storyboard> </VisualState> <VisualState x:Name="MultipleDraggingPrimary"> <Storyboard> <!-- These two Opacity animations are required - the FadeInThemeAnimations on the same elements animate an internal Opacity. --> <DoubleAnimation Storyboard.TargetName="MultiArrangeOverlayBackground" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="MultiArrangeOverlayText" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> <DoubleAnimation Storyboard.TargetName="ContentBorder" Storyboard.TargetProperty="Opacity" Duration="0" To="{ThemeResource ListViewItemDragThemeOpacity}" /> <FadeInThemeAnimation TargetName="MultiArrangeOverlayBackground" /> <FadeInThemeAnimation TargetName="MultiArrangeOverlayText" /> <DragItemThemeAnimation TargetName="ContentBorder" /> <FadeOutThemeAnimation TargetName="SelectionBackground" /> <FadeOutThemeAnimation TargetName="SelectedCheckMarkOuter" /> <FadeOutThemeAnimation TargetName="SelectedBorder" /> <FadeOutThemeAnimation TargetName="PointerOverBorder" /> </Storyboard> </VisualState> <VisualState x:Name="MultipleDraggingSecondary"> <Storyboard> <FadeOutThemeAnimation TargetName="ContentContainer" /> </Storyboard> </VisualState> <VisualStateGroup.Transitions> <VisualTransition To="NotDragging" GeneratedDuration="0:0:0.2"/> </VisualStateGroup.Transitions> </VisualStateGroup> <VisualStateGroup x:Name="ReorderHintStates"> <VisualState x:Name="NoReorderHint"/> <VisualState x:Name="BottomReorderHint"> <Storyboard> <DragOverThemeAnimation TargetName="ReorderHintContent" ToOffset="{ThemeResource ListViewItemReorderHintThemeOffset}" Direction="Bottom" /> </Storyboard> </VisualState> <VisualState x:Name="TopReorderHint"> <Storyboard> <DragOverThemeAnimation TargetName="ReorderHintContent" ToOffset="{ThemeResource ListViewItemReorderHintThemeOffset}" Direction="Top" /> </Storyboard> </VisualState> <VisualState x:Name="RightReorderHint"> <Storyboard> <DragOverThemeAnimation TargetName="ReorderHintContent" ToOffset="{ThemeResource ListViewItemReorderHintThemeOffset}" Direction="Right" /> </Storyboard> </VisualState> <VisualState x:Name="LeftReorderHint"> <Storyboard> <DragOverThemeAnimation TargetName="ReorderHintContent" ToOffset="{ThemeResource ListViewItemReorderHintThemeOffset}" Direction="Left" /> </Storyboard> </VisualState> <VisualStateGroup.Transitions> <VisualTransition To="NoReorderHint" GeneratedDuration="0:0:0.2"/> </VisualStateGroup.Transitions> </VisualStateGroup> <VisualStateGroup x:Name="DataVirtualizationStates"> <VisualState x:Name="DataAvailable"/> <VisualState x:Name="DataPlaceholder"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextBlock" Storyboard.TargetProperty="Visibility" Duration="0"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderRect" Storyboard.TargetProperty="Visibility" Duration="0"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid x:Name="ReorderHintContent" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <ContentPresenter x:Name="headerPresenter" Grid.Row="0" ContentTransitions="{TemplateBinding ContentTransitions}" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> <Grid Grid.Row="1"> <Path x:Name="SelectingGlyph" Opacity="0" Data="F1 M133.1,17.9 L137.2,13.2 L144.6,19.6 L156.4,5.8 L161.2,9.9 L145.6,28.4 z" Fill="{ThemeResource ListViewItemCheckSelectingThemeBrush}" Height="13" Stretch="Fill" Width="15" HorizontalAlignment="Right" Margin="0,9.5,9.5,0" VerticalAlignment="Top" FlowDirection="LeftToRight"/> <Border x:Name="HintGlyphBorder" Height="40" Width="40" HorizontalAlignment="Right" VerticalAlignment="Top" Opacity="0" Margin="4"> <Path x:Name="HintGlyph" Opacity="0" Data="F1 M133.1,17.9 L137.2,13.2 L144.6,19.6 L156.4,5.8 L161.2,9.9 L145.6,28.4 z" Fill="{ThemeResource ListViewItemCheckHintThemeBrush}" Height="13" Stretch="Fill" Width="15" HorizontalAlignment="Right" Margin="0,5.5,5.5,0" VerticalAlignment="Top" FlowDirection="LeftToRight"/> </Border> <Border x:Name="ContentContainer"> <Grid x:Name="InnerDragContent"> <Rectangle x:Name="PointerOverBorder" IsHitTestVisible="False" Opacity="0" Fill="{ThemeResource ListViewItemPointerOverBackgroundThemeBrush}" Margin="1" /> <Rectangle x:Name="FocusVisual" IsHitTestVisible="False" Opacity="0" StrokeThickness="2" Stroke="{ThemeResource ListViewItemFocusBorderThemeBrush}" /> <Rectangle x:Name="SelectionBackground" Margin="4" Fill="{ThemeResource ListViewItemSelectedBackgroundThemeBrush}" Opacity="0" /> <Border x:Name="ContentBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Margin="4"> <Grid> <ContentPresenter x:Name="contentPresenter" ContentTransitions="{TemplateBinding ContentTransitions}" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}" /> <!-- The 'Xg' text simulates the amount of space one line of text will occupy. In the DataPlaceholder state, the Content is not loaded yet so we approximate the size of the item using placeholder text. --> <TextBlock x:Name="PlaceholderTextBlock" Opacity="0" Text="Xg" Foreground="{x:Null}" Margin="{TemplateBinding Padding}" IsHitTestVisible="False" AutomationProperties.AccessibilityView="Raw"/> <Rectangle x:Name="PlaceholderRect" Visibility="Collapsed" Fill="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}"/> <Rectangle x:Name="MultiArrangeOverlayBackground" IsHitTestVisible="False" Opacity="0" Fill="{ThemeResource ListViewItemDragBackgroundThemeBrush}" /> </Grid> </Border> <Rectangle x:Name="SelectedBorder" IsHitTestVisible="False" Opacity="0" Stroke="{ThemeResource ListViewItemSelectedBackgroundThemeBrush}" StrokeThickness="{ThemeResource ListViewItemSelectedBorderThemeThickness}" Margin="4" /> <Border x:Name="SelectedCheckMarkOuter" IsHitTestVisible="False" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="4"> <Grid x:Name="SelectedCheckMark" Opacity="0" Height="40" Width="40"> <Path x:Name="SelectedEarmark" Data="M0,0 L40,0 L40,40 z" Fill="{ThemeResource ListViewItemSelectedBackgroundThemeBrush}" Stretch="Fill"/> <Path Data="F1 M133.1,17.9 L137.2,13.2 L144.6,19.6 L156.4,5.8 L161.2,9.9 L145.6,28.4 z" Fill="{ThemeResource ListViewItemCheckThemeBrush}" Height="13" Stretch="Fill" Width="15" HorizontalAlignment="Right" Margin="0,5.5,5.5,0" VerticalAlignment="Top" FlowDirection="LeftToRight"/> </Grid> </Border> <TextBlock x:Name="MultiArrangeOverlayText" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.DragItemsCount}" Foreground="{ThemeResource ListViewItemDragForegroundThemeBrush}" FontFamily="{ThemeResource ContentControlThemeFontFamily}" FontSize="26.667" IsHitTestVisible="False" Opacity="0" TextWrapping="Wrap" TextTrimming="WordEllipsis" Margin="18,9,0,0" AutomationProperties.AccessibilityView="Raw"/> </Grid> </Border> </Grid> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
<Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <ContentPresenter x:Name="headerPresenter" Grid.Row="0" ContentTransitions="{TemplateBinding ContentTransitions}" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> <Grid Grid.Row="1">
注意上图,我把默认模板里面的内容放到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; } } }
当然不要忘记了在GroupListView1 里面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; } } } } } } } } } } }
查看一下GroupListView1 里面的代码,是不是感觉比之前GroupListView里面的代码简单更好理解(我的错。。。别要问我控件名字怎么这么随意 ,我想不更好的。。( ╯□╰ ))
当然这个GroupListView1里面也有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; }
加上之后:
好了,这个控件也讲完咯,真的没有了。
开源有益,源码GitHub地址。
其实有心的朋友也发现,原来ScrollViewer的超出界限动画,估计也是这样实现的吧??。我在网上也发现很多人问怎么才能禁掉它。
下图是UWP 图片剪切旋转工具,当时我就是拿ScrollViewer做的,当时找了半天也没找到禁止下面动画的方法。
找了一圈,ElementCompositeMode 这个属性有点像。微软的解释是:
没看懂。。试了下,没有用。
另外也没在ScrollViewer或者ScrollContentPresenter 上找到相关的属性。希望微软在之后能暴露相关属性,毕竟不是每个人都希望ScrollViewer有这种动画的。。
如果有童鞋已经知道怎么搞了,请留言下下,让更多的童鞋知道。万分感谢