Windows Runtime ListView 下拉刷新功能实现
概述
在Windows Runtime下,由于ListView控件没有提供任何有关此功能的方法事件,但是很多场景需要用到这方面的功能,所以在此记录下我的实现过程。
基本思路:
1.修改ListView控件模板,加入我们需要在下拉刷新时显示的提示文字或图片。同时加入一个位置指示元素,用于对下拉的高度进行定位。
2.后台代码中加入一个定时器,用于检测是否发生下拉动作。
修改ListView控件模板
ListView控件模板修改如下,修改部分代码已做标记。
对于下拉过程中的显示效果,可以根据各自的需要进行修改。
<Style x:Key="SupportedPullDownListViewStyle" TargetType="ListView"> <Setter Property="IsTabStop" Value="False"/> <Setter Property="Padding" Value="0"/> <Setter Property="TabNavigation" Value="Once"/> <Setter Property="IsSwipeEnabled" Value="True"/> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> <Setter Property="VerticalContentAlignment" Value="Top"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled"/> <Setter Property="ScrollViewer.VerticalScrollMode" Value="Enabled"/> <Setter Property="ScrollViewer.ZoomMode" Value="Disabled"/> <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False"/> <Setter Property="ScrollViewer.BringIntoViewOnFocusChange" Value="True"/> <Setter Property="ItemContainerTransitions"> <Setter.Value> <TransitionCollection> <AddDeleteThemeTransition/> <ReorderThemeTransition/> </TransitionCollection> </Setter.Value> </Setter> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <ItemsStackPanel Orientation="Vertical"/> </ItemsPanelTemplate> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListView"> <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"> <VisualStateManager.VisualStateGroups> <!-- 定义下拉时的三种VisualState --> <!-- CommonState: 未发生下拉动作 --> <!-- PullDownState: 发生下拉动作,释放后不进行刷新 --> <!-- PullDownHoldedState: 发生下拉动作,并且保持了一定时间,释放后立即刷新 --> <VisualStateGroup x:Name="PullDownStates"> <VisualState x:Name="CommonState"></VisualState> <VisualState x:Name="PullDownState"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PullDownArrow" Storyboard.TargetProperty="UIElement.Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PullDownText" Storyboard.TargetProperty="UIElement.Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="PullDownHoldedState"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PullDownArrow" Storyboard.TargetProperty="UIElement.Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> </ObjectAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PullDownArrow" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"> <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="-180"/> </DoubleAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PullDownedText" Storyboard.TargetProperty="UIElement.Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> <!-- 定义下拉VisualState结束 --> </VisualStateManager.VisualStateGroups> <Grid x:Name="PullDownGridContainer"> <!-- 定义下拉时显示的文字及箭头图形 --> <StackPanel Orientation="Horizontal" Height="30" Margin="0,15" VerticalAlignment="Top" HorizontalAlignment="Center"> <Path Data="M 0,3 L 2,5 L 2,0 M 2,5 L 4,3" Stroke="#FF514E4E" StrokeThickness="1" Stretch="Uniform" Height="20" Visibility="Collapsed" x:Name="PullDownArrow" RenderTransformOrigin="0.5,0.5"> <Path.RenderTransform> <RotateTransform Angle="0"/> </Path.RenderTransform> </Path> <TextBlock Margin="10,0,0,0" Foreground="#FF514E4E" x:Name="PullDownText" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="18" Visibility="Collapsed" FontWeight="Light"> 下拉刷新 </TextBlock> <TextBlock Margin="10,0,0,0" x:Name="PullDownedText" Foreground="#FF514E4E" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="18" Visibility="Collapsed" FontWeight="Light"> 释放立即刷新 </TextBlock> </StackPanel> <!-- 定义结束 --> <ScrollViewer x:Name="ScrollViewer" 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}"> <StackPanel> <!-- 定义下拉位置指示元素 --> <Grid x:Name="PullDownGridIndicator"> </Grid> <!-- 定义结束 --> <ItemsPresenter FooterTransitions="{TemplateBinding FooterTransitions}" FooterTemplate="{TemplateBinding FooterTemplate}" Footer="{TemplateBinding Footer}" HeaderTemplate="{TemplateBinding HeaderTemplate}" Header="{TemplateBinding Header}" HeaderTransitions="{TemplateBinding HeaderTransitions}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </StackPanel> </ScrollViewer> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
下拉状态状态转换
由于在Xaml文件中定义了ListView的三种VisualState,所以需要在后台代码中实现这三种状态的切换。
可以看到,VisualState属性表示ListView当前的状态,在切换到PullDownState状态时,同时记录下切换的时间。该时间用于之后判断是否切换到PullDownHoldedState状态。
/// <summary> /// 发生下拉动作的时间 /// </summary> private DateTime PullDownStartTime { get; set; } private enum PullDownVisualState { CommonState, PullDownState, PullDownHoldedState, } private PullDownVisualState _VisualState = PullDownVisualState.CommonState; private PullDownVisualState VisualState { get { return _VisualState; } set { if (_VisualState != value) { _VisualState = value; if (_VisualState == PullDownVisualState.CommonState) { VisualStateManager.GoToState(SupportedPullDownListView, "CommonState", false); } else if (_VisualState == PullDownVisualState.PullDownState) { PullDownStartTime = DateTime.Now; VisualStateManager.GoToState(SupportedPullDownListView, "PullDownState", false); } else if (_VisualState == PullDownVisualState.PullDownHoldedState) { VisualStateManager.GoToState(SupportedPullDownListView, "PullDownHoldedState", false); } } } }
下拉定时器
在ListView滚动过程中,由于没有现成的事件可以调用,因此需要定义一个定时器用于监视滚动位置。
PullDownTimer = new DispatcherTimer(); PullDownTimer.Interval = TimeSpan.FromMilliseconds(100); PullDownTimer.Tick += PullDownTimer_Tick; PullDownTimer.Start();
当定时超时时,进行位置检查。
若发生下拉或释放动作,则进行状态切换。
private async void PullDownTimer_Tick(object sender, object e) { // 获取位置指示元素相对于其容器的位置 var elementBounds = PullDownIndicator.TransformToVisual(PullDownContainer).TransformBounds(new Rect(0.0, 0.0, PullDownContainer.ActualWidth, PullDownContainer.ActualHeight)); if (elementBounds.Top > 60) // 此处的60代表下拉的阈值,可根据实际情况调整该值 { if (VisualState == PullDownVisualState.CommonState) { VisualState = PullDownVisualState.PullDownState; } else if (VisualState == PullDownVisualState.PullDownState) { if (DateTime.Now - PullDownStartTime > TimeSpan.FromSeconds(1)) // 此处设定下拉保持时间为1秒,即下拉状态保持1秒后释放才有效,该值可根据实际情况调整 { VisualState = PullDownVisualState.PullDownHoldedState; } } } else { if (VisualState == PullDownVisualState.PullDownHoldedState) { // 执行刷新代码 } VisualState = PullDownVisualState.CommonState; } }
附,获取ListView控件模板中的元素
建立帮助类,添加扩展方法
public static class VisualTreeHelperExtension { public static T GetFirstDescendantOfType<T>(this DependencyObject start) where T : DependencyObject { return start.GetDescendantsOfType<T>().FirstOrDefault(); } public static IEnumerable<T> GetDescendantsOfType<T>(this DependencyObject start) where T : DependencyObject { return start.GetDescendants().OfType<T>(); } public static IEnumerable<DependencyObject> GetDescendants(this DependencyObject start) { if (start == null) { yield break; } var queue = new Queue<DependencyObject>(); queue.Enqueue(start); while (queue.Count > 0) { var parent = queue.Dequeue(); var popup = parent as Popup; if (popup != null) { if (popup.Child != null) { queue.Enqueue(popup.Child); yield return popup.Child; } } else { var count = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < count; i++) { var child = VisualTreeHelper.GetChild(parent, i); yield return child; queue.Enqueue(child); } } } } }
例如需要获取位置指示元素,调用如下,其中ListView控件名为SupportedPullDownListView
SupportedPullDownListView.GetDescendantsOfType<Grid>().FirstOrDefault(x => x.Name == "PullDownGridIndicator")