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")

 

posted @ 2015-09-04 17:03  Jerry Tong  阅读(173)  评论(0编辑  收藏  举报