zoukankan      html  css  js  c++  java
  • (UWP开发)更为合理的一种ListView下拉刷新(PullToRefresh)实现方法

    最近在做的一个项目需要用到下拉刷新,但是参考了现在网络上比较普遍的方法,觉得都不太好,因为要在外部套上一个SrollViewer,容易出现滚动错误。于是刚开始的时候就把思路定到了ListView内部的ScrollViewer上。

    最初的想法是在ScrollViewer的Manipulation相关事件上下手,确实做好了,效果也不错,如图:

    当时得意满满的看着自己的作品,心里是说不出的激动啊,结果放在手机上想试试触屏设备的效果,结果发现好坑爹:在触屏设备上,手指在ListView的上下滑动默认是移动其滚动条,但是改变了ManipulationModes之后,手指在屏幕上滑动就不能够使ListView滚动了(一脸懵逼)。无奈啊,只能放弃这个方法了。

    之后又在想新的途径来实现同样的效果,但是发现都没有很好的办法,于是我的思路又绕回了起点:还是像网络上的方法一样,先把刷新的Header事先隐藏起来,当ScrollViewer滚动出Header之后开始刷新。只不过这个ScrollViewer不是外部嵌套的,是在ListView内部的。实现后的效果如下:

    好吧,下面言归正传,讲讲详细的实现方法:

    1.首先是在Xmal中,我们改一下ListView的Template。我在ScrollViewer最外层放了一个Grid,定义了两行。第一行放的就是Header,也就是刷新的时候会一直转的那个区域。第二行是ItemPresenter,显示ListView的内容。其中值得注意的是,第一行的高度是30。

    代码如下:

     1                         <ControlTemplate TargetType="ListView">
     2                             <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
     3                                 <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}">
     4                                     <Grid>
     5                                         <Grid.RowDefinitions>
     6                                             <RowDefinition Height="auto"></RowDefinition>
     7                                             <RowDefinition Height="*"></RowDefinition>
     8                                         </Grid.RowDefinitions>
     9                                         <Grid x:Name="refresh_ring" Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">
    10                                             <Ellipse Height="30" Width="30" Fill="{Binding Source={StaticResource APPTheme},Path=APP_Color_Brush}" Opacity="0.7" StrokeThickness="0"></Ellipse>
    11                                             <ProgressRing IsActive="True" Foreground="{Binding Source={StaticResource APPTheme},Path=Foreground_Color_Brush}"></ProgressRing>
    12                                         </Grid>
    13                                         <ItemsPresenter Grid.Row="1" FooterTransitions="{TemplateBinding FooterTransitions}" FooterTemplate="{TemplateBinding FooterTemplate}" Footer="{TemplateBinding Footer}" HeaderTemplate="{TemplateBinding HeaderTemplate}" Header="{TemplateBinding Header}" HeaderTransitions="{TemplateBinding HeaderTransitions}"/>
    14                                     </Grid>
    15                                 </ScrollViewer>
    16                             </Border>
    17                         </ControlTemplate>

    2.在ListView的Loaded事件中拿到其内部的SrollViewer,以及订阅相关事件。

    1         private void ListView_Loaded(object sender, RoutedEventArgs e)
    2         {
    3             Get_Child((DependencyObject)sender);
    4             lastest_listview_sc.ViewChanging += Lastest_listview_sc_ViewChanging;
    5             lastest_listview_sc.ViewChanged += Lastest_listview_sc_ViewChanged;
    6         }

    其中这个Get_Child是一个实现遍历可视化树的方法,我们这里传递的参数sender就是ListView本身。Get_Child代码如下:

     1         private void Get_Child(DependencyObject o)
     2         {
     3             try
     4             {
     5                 int count = VisualTreeHelper.GetChildrenCount(o);
     6                 if (count > 0)
     7                 {
     8                     for (int i = 0; i < count; i++)
     9                     {
    10                         var child = VisualTreeHelper.GetChild(o, count - 1);
    11                         if (child is ScrollViewer)
    12                         {
    13                             lastest_listview_sc = child as ScrollViewer;
    14                         }
    15                         else
    16                         {
    17                             Get_Child(VisualTreeHelper.GetChild(o, count - 1), n);
    18                         }
    19                     }
    20                 }
    21             }
    22             catch (Exception ex)
    23             {
    24                 return;
    25             }
    26         }

    3.这里就是接下来的重点了。相信自己动手实现过下拉刷新的同学们都能够遇见过这个问题:如果用户只是普通的滚动ListView查看内容,手指往下滑的时候(内容是往下)ScrollViewer会由于惯性偏移量滑到最上部,或者其他的类似操作也会滑动到最上部,但是此时用户并不想要刷新。因此,我们就要在Lastest_listview_sc_ViewChanging方法内部实时监听ScrollViewer的滚动状态。如果此时ScrollViewer是由于惯性偏移量在滚动并且已经滚动超过了指定条件,我们就要让SrollViewer自己滚回30的位置,也就是判错。另外在触屏设备和非触屏设备上面,我们判错的指定条件最好不要一样(因为电脑用的是鼠标滚轮手机Surface之类的用的是手指,要是设定成一样的你会发现可能你怎么样都无法滑到最上面。。。)

    因此上面提到的问题我们要在Lastest_listview_sc_ViewChanging方法中解决。其中的形参e的属性IsInertial可以用来获取操作是否具有惯性元素。Lastest_listview_sc_ViewChanging代码如下:

     1         private async void Lastest_listview_sc_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e) //下拉刷新判错 自动加载
     2         {
     3             if (App.DeviceInfo.Device_type != Model.DeviceType.PC)
     4             {
     5                 if (lastest_listview_sc.VerticalOffset <= 10.0)
     6                 {
     7                     if (e.IsInertial)
     8                     {
     9                         lastest_listview_sc.ChangeView(null, 30.0, null);
    10                     }
    11                 }
    12             }
    13             else
    14             {
    15                 if (lastest_listview_sc.VerticalOffset <= 50.0 && lastest_listview_sc.VerticalOffset > 30.0)
    16                 {
    17                     if (e.IsInertial)
    18                     {
    19                         lastest_listview_sc.ChangeView(null, 30.0, null);
    20                     }
    21                 }
    22             }
    23         }

    4.现在我们要用到Lastest_listview_sc_ViewChanged这个方法。其中的形参e的属性IsIntermediate可以告诉我们引发ViewChanged事件的基础操作是否完成,如果完成的话我们就可以来判断是否需要刷新了。代码如下:

    1         private void Lastest_listview_sc_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
    2         {
    3             if (!e.IsIntermediate)
    4             {
    5                 Lastest_Refresh();
    6             }
    7         }

    5.最后一步了,就是实现我们的Lastest_Refresh方法,在这个方法中,我们需要判断ScrollViewer是否滚动到了最顶端,如果是的话,就可以开始执行刷新代码。刷新结束之后再将ScrollViewer往下滚30就OK了。代码如下:

     1         private async void Lastest_Refresh()
     2         {
     3             if (lastest_listview_sc.VerticalOffset == 0.0)
     4             {
     5                 //刷新相关代码
     6                 await Task.Delay(1000);
     7 
     8                 //移动回顶部
     9                 lastest_listview_sc.ChangeView(null, 30, null);
    10             }
    11         }

    至此,下拉刷新的过程到结束。当然,大家看到我的代码还是比较复杂,也不够简洁,毕竟我还是个大一学生。拿出来是跟大家交流的嘛,如果有什么更好的方法或者建议欢迎评论,谢谢。感激不尽!

    ——2016/08/01编辑——

    忘了写一个点,就是进入页面后我们要先把刷新部分的Header隐藏起来,要不然Header就会一直出现。。。可以在页面的OnNavigate方法最后添加,代码如下:

    1                 await Task.Delay(500);
    2                 series_listview_sc.ChangeView(null, 30, null);

    第一行的意义是如果直接执行的话可能会没有效果,所以要先Delay。

    然后还要感谢网友提出一个很严重的BUG,就是如果ListView内部item过少,Header可能会无法被隐藏掉,这个时候这套刷新逻辑就没有用了。。。希望大家有更好的方法的话可以分享一下,谢谢!

  • 相关阅读:
    git常用操作命令
    如何编写高质量代码
    Chrome调试工具简单介绍
    使用eclipse+tomcat搭建本地环境
    chrome设置--disable-web-security解决跨域
    利用Maven管理工程项目本地启动报错及解决方案
    用户输入验证【提升篇】
    简单【用户输入验证】
    【消息框】的返回值
    【消息框】的4种显示形式
  • 原文地址:https://www.cnblogs.com/RodChong/p/5723946.html
Copyright © 2011-2022 走看看