在windows phone 中采用数据列表时为了保证用户体验常遇到加载数据的问题.这个问题普遍到只要你用到数据列表就要早晚面对这个问题. 很多人会说这个问题已经有解决方案. 其实真正问题并不在于如何实现列表数据动态加载? 而我们真正目标是如何使这种加载方式达到用户在操作时良好的用户体验. 基于用户体验合理性要高于功能本身的实现.

而这种合理性主要体现在什么时候需要加载数据? 什么时候需要数据本地缓存加速本地UI响应? 也是说我们出发点是基于产品用户体验.需要我们在列表动态加载上加以一定加载策略进行操作行为上的约束. 用来达到这个目的. 在WP平台上如果你留意.会发现每当遇到这样的涉及用户体验的问题时.我们也会通常会看看其他平台是做法.不妨也是一种开拓思路. 从Android 和IOS 平台角度来看. 几种常见加载数据的方式.

[方式1]: 自动下拉加载

这种方式比较常见.通常一个独立的数据列表中. 在我们第一次进来时列表加载最新数据.当用户需要获取更多或是更旧的数据时.用户向上滑动.当滚动到UI底部时自动加载更多的数据.特点是 自动加载 避免更多手动的操作. 在网络通畅情况 列表操作流畅. 确定是用户无法控制整个数据过程.

[方式2]:手动下拉加载

方式1采用的用户下拉到UI底部时自动加载.整个加载过程是用户是可不控.即 无法实现用户只在需要时才手动启用加载更多或更旧的数据方式.二方式2当用户滚动UI时可以选择是否加载更多数据.用户能够对整个数据加载过程进行控制.

[方式3]:UI提示加载

UI提示加载的方式和方式1 、2完全不同.当用户下拉时加载更多数据时. 会提示弹出一个UI提示层. 对加载进度进行提示. 在数据加载过程中整个LiveView时无法进行任何UI操作的.用户只能等待数据加载完成才能重新操作UI. 这点在很多Pc平台项目见到很多.

[方式4]:下拉刷新

当用户第一次进来时.列表中获取到最新数据时. 如果这个列表时随着时间点会发生数据动态变化时. 用户就希望在当前页面就能获取到最新的数据. 这个时候下拉刷新价值就体现出来了. 而不需要重新进入这个页面来获取最新数据.下拉刷新整个操作流程是. 用户在UI顶部区域下拉整个列表.当用户手势离开UI顶部区域时. 列表自动回到顶部.并开始加载最新的数据.更新到ListView中来. 在加载过程中用户依然可以随意操作当前UI数据.

如上四种方式时Android和Ios中比较常见的数据加载方式. 当然在Ios中还看到类似Pc端数据分页. 还包含采用一些自定义动画方式获取更好的加载体验. 抛开这些不谈.我们就从这些最基本的加载方式入手.来谈谈如何在Windows Phone 中数据列表中获得最好的加载体验.

我们目前需求时在一个竖屏中有一个ListBox. 希望用户通过手势操作方式能够实现操作获取到最新和更旧的数据.那我们从如上四种独立加载方式来看.结合四种方式优缺点.设计一下windows phone 数据列表加载策略 总结如下:

WP ListBox数据加载策略:

A:列表顶部区域支持下拉数据刷新.

B:当用户滑动操作时滚动列表到最底部时 可以加载更多旧的数据

C:当用户滑动操作时从列表底部滚动顶部时 依然支持可以加载最新的数据.

明确了我们需求既加载策略. 来尝试Windows Phone 单个独立类表尝试实现如上三个特点.

列表上下滑动加载

从上面三点加载策略来看. 我们首先来实现. 列表中上下滑动加载数据. 也就是当用户滚动UI底部时自动加载更旧的数据. 当用户滚动顶部自动加载最新的数据. 页面采用加载数据集合就采用常用ListBox来演示这个实例.

首先我们构建一个Project 命名为DynamicLoadData 在MainPage添加一个默认的ListBox控件:

   1:  <!--ContentPanel - place additional content here-->
   2:  <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
   3:      <ListBox x:Name="DynamicLoadData_LB"></ListBox>
   4:  </Grid>

众所周知.实现Listbox滑动加载数据.很多人都会采用网上一种比较通用的方式.即采用监听ListBox的MouseMove事件. 当手势操作列表上下滑动会触发该事件. 事件触发后. 通过检测ListBox.VerticalOffSet当前滚动条位置.再同ListBox.ScrollableHeight滚动条能达到最大位移两者之间的间距差. 来判断是否到达底部. 加载新的数据.

但你会发现会存在一个问题. 在某些手势操作时 会突然发现Listbox已经滚动底部却没有执行加载数据的操作. 逻辑虽然正确但操作时却时灵时而不灵 其实这个问题根本原因是因为. ListBox.MouseMove事件是只有的你的手指触摸到屏幕上并且滑动屏幕才会触发.但只要你的手指离开屏幕. 类似在离开前用力下滑. 你会发现listbox已经到了底部却没有触发这个加载事件. 主要因为当前手势已经离开了屏幕 MouseMove事件就不会被触发.哪怕ListBox已经滚动到底部了.

同样我们也知道ListBox控件本身就内置了ScrollViewer. 同样的思路我们通过判断当前ListBox 的VerticalOffSet 和内置ScrollViewer实际滚动位置进行比较. 来判断当前滚动是到达顶部或底部.

首先获取ListBox中ScrollViewer控件:

   1:  public static List<T> GetVisualChildCollection<T>(object parent) where T : UIElement
   2:  {
   3:       List<T> visualCollection = new List<T>();
   4:       GetVisualChildCollection(parent as DependencyObject, visualCollection);
   5:       return visualCollection;
   6:  }
   7:   
   8:  public static void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection) where T : UIElement
   9:  {
  10:       int count = VisualTreeHelper.GetChildrenCount(parent);
  11:       for (int i = 0; i < count; i++)
  12:       {
  13:           DependencyObject child = VisualTreeHelper.GetChild(parent, i);
  14:           if (child is T)
  15:               visualCollection.Add(child as T);
  16:           else if (child != null)
  17:               GetVisualChildCollection(child, visualCollection);
  18:        }
  19:  }

获取ScrollViewer控件并订阅其垂直水平ValueChanged事件 实现如下:

   1:  private void RegisterScrollListBoxEvent()
   2:  {
   3:       List<ScrollBar> controlScrollBarList =GetVisualChildCollection<ScrollBar>(this.WholeCityPictureFllow_LB);
   4:       if (controlScrollBarList == null)
   5:            return;
   6:   
   7:       foreach (ScrollBar queryBar in controlScrollBarList)
   8:        {
   9:             if (queryBar.Orientation == System.Windows.Controls.Orientation.Vertical)
  10:                 queryBar.ValueChanged += queryBar_ValueChanged;
  11:        }
  12:  }

在ValueChange事件中判断其到达最顶部还是最底部:

   1:  void queryBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
   2:   {
   3:      ScrollBar scrollBar = (ScrollBar)sender;
   4:      object valueObj = scrollBar.GetValue(ScrollBar.ValueProperty);
   5:      object maxObj = scrollBar.GetValue(ScrollBar.MaximumProperty);
   6:      object minObj = scrollBar.GetValue(ScrollBar.MinimumProperty);
   7:   
   8:      if (valueObj != null && maxObj != null)
   9:      {
  10:         double value = (double)valueObj;
  11:         double max = (double)maxObj;
  12:         double min = (double)minObj;
  13:   
  14:          if (value >= max)
  15:          {
  16:            #region Load Old                    
  17:            #endregion
  18:          }
  19:   
  20:         if (value <= min)
  21:          {                  
  22:            #region Load New                        
  23:            #endregion                  
  24:          }
  25:      }
  26:  }

如上通过判断判断listbox当前位置和最大滚动区域Max和Min进行对比来判断当前滚动是否到顶或底部. 方法及其简单. 值得提到一点是. 我们到达顶部判断不需要额外处理. 有时我们UI元素比较丰富时. 我们希望保证下滑操作时不希望因为数据加载操作导致UI出现卡顿. 这里需要有两个需要额外控制一下. 如果你每次加载数据类似30条排版内容最好多出整个屏幕. 另外我们需要在下滑时触发加载时. 要把Max-100或是适当的值. 这样的做目的是用户向下滚动不用滚动底部才开始加载. 而是快到达到底部时就已经开始预加载数据. 在网络稳定情况下回操作UI列表更为流畅.

如上实际加载效果还需要微调才能达到最佳. 已经上下滑动加载.

so 在来重点说说 下拉刷新.

下拉刷新

说道下拉刷新.恐怕在Windows Phone上应用每天用的最频繁应该就是Sina微博了.和IOS上效果基本一致 效果如下:

当用户下拉时 数据列表顶部会显示 一个向下箭头和下拉刷新的文字提示. 紧接着提示松开自动刷新. 松开手势操作 列表回到顶部.自动开始加载最新数据.并更新数据到ListBox中来, 整个流程如上.首先来分析一下如何实现思路?

因Listbox基本所有我们需要操作事件和属性. 基于ListBox我们重写一个控件RefreshListBox.首先来看看顶部提示区域如何实现.

其实ListBox的Template实现基于ScrollViewer控件中放置ItemsPresenter. ItemsPresenter是用来在项目控件模板中指定在 ItemsControl 定义的 ItemsPanel 要添加的控件的可视化树.那么我们只需要在一个Grid把提示区域放在ItemsPresenter上面就可以在下拉是看到整个提示区域. 类似这样自定义ListBox的模板:

   1:  <ControlTemplate TargetType="local:RefreshBox">
   2:      <ScrollViewer x:Name="ScrollViewer" ...>
   3:          <Grid>
   4:              <Grid Margin="0,-90,0,30" Height="60" VerticalAlignment="Top" x:Name="ReleaseElement">
   5:                  <!-- Tip Area Here -->
   6:              </Grid>
   7:          </Grid>
   8:          <ItemsPresenter/>
   9:      </ScrollViewer>
  10:  </ControlTemplate>

在加载控件时. 我们需要获取到自定义控件RefreshListBox内置滑动ScrollViewer并订阅其MouseMove和ManipulationCompleted事件. 并拿到提示区域ReleaseElement对象的引用. 重写OnApplyTemplate方法:

   1:  public override void OnApplyTemplate()
   2:  {
   3:      base.OnApplyTemplate();
   4:      if (ElementScrollViewer != null)
   5:      {
   6:          ElementScrollViewer.MouseMove -= viewer_MouseMove;
   7:          ElementScrollViewer.ManipulationCompleted -= viewer_ManipulationCompleted;
   8:      }
   9:   
  10:      ElementScrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer;
  11:      if (ElementScrollViewer != null)
  12:      {            
  13:          ElementScrollViewer.MouseMove += viewer_MouseMove;
  14:          ElementScrollViewer.ManipulationCompleted += viewer_ManipulationCompleted;
  15:      }
  16:   
  17:      ElementRelease = GetTemplateChild("ReleaseElement") as UIElement;                
  18:      ChangeVisualState(false);
  19:  }

当SrollViewer为Null订阅事件操作时.如果在不同SDK版本[WP7 Or WP8]执行过程发现订阅的ManipulationCompleted没有被触发. 可以采用如下方式来强制添加处理事件[在WP7 And WP8 均测试有效] :

   1:  ElementScrollViewer.AddHandler(ScrollViewer.ManipulationCompletedEvent, 
   2:                                 new EventHandler<ManipulationCompletedEventArgs>(viewer_ManipulationCompleted), true);               

在MouseMove事件中.通过判断ListBox的VerticalOffset 当它等于0;既在顶部.当下拉超过一定距离是开始提示下拉刷新更新RealseElement元素中提示信息:

   1:  private void viewer_MouseMove(object sender, MouseEventArgs e)
   2:  {
   3:      if (VerticalOffset == 0)
   4:      {
   5:          var p = this.TransformToVisual(ElementRelease).Transform(new Point());
   6:          if (p.Y < -VerticalPullToRefreshDistance) //Passed thresdhold : In pulling state area
   7:          {            
   8:              //TODO: Update layout//visual states
   9:          }
  10:          else //Is not pulling
  11:          {
  12:              //TODO: Update layout/visual states
  13:          }
  14:      }
  15:  }

同样的逻辑.在ManipulationCompleted事件中当用户完成手势操作时触发.如果当前ListBox VerticalOffset 等于0 也就是位于顶部时. 松开时手势时 listBox回到顶部并开始加载最新列表数据并更新列表:

   1:  private void viewer_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
   2:  {
   3:      var p = this.TransformToVisual(ElementRelease).Transform(new Point());
   4:      if (p.Y < -VerticalPullToRefreshDistance)
   5:      {
   6:          //TODO: Raise Polled to refresh event
   7:      }
   8:  }

这样整个下拉刷新的基本逻辑实现思路已经很明朗.可以完整重写整个ListBox实现.

当第一次进来加载数据:

下拉是效果:

刚松开效果:

这样下拉刷新结合ListBox本身上下滑动刷新基本实现我们如上三个需求.

源码下载[https://github.com/chenkai/LoadData]

windows phone 下拉刷新的更多相关文章

  1. Windows phone应用开发[22]-再谈下拉刷新

    几周之前在博客更新一篇Windows phone应用开发[18]-下拉刷新 博文,有很多人在微博和博客评论中提到了很多问题.其实在实际项目中我基于这篇博文提出解决问题思路优化了这个解决方案.为了能够详 ...

  2. Windows phone应用开发[18]-下拉刷新

    在windows phone 中采用数据列表时为了保证用户体验常遇到加载数据的问题.这个问题普遍到只要你用到数据列表就要早晚面对这个问题. 很多人会说这个问题已经有解决方案. 其实真正问题并不在于如何 ...

  3. Xamarin. Android实现下拉刷新功能

    PS:发现文章被其他网站或者博客抓取后发表为原创了,给图片加了个水印 下拉刷新功能在安卓和iOS中非常常见,一般实现这样的功能都是直接使用第三方的库,网上能找到很多这样的开源库.然而在Xamarin. ...

  4. UWP 下拉刷新控件(PullToRefreshControl)

    最近项目里面有下拉刷新的需求,自己做了一个,效果还不错. <Style TargetType="local:PullToRefreshControl"> <Set ...

  5. 在win8中如何实现下拉刷新的功能

      现在我以listview为例来讲述下拉刷新的功能! 在xaml中设置listview一定要设置一个这样的属性,IsSwipeEnabled=false,然后再listview控件的前面要布局下拉刷 ...

  6. 在微信小程序中,如何实现下拉刷新(模拟刷新)

    一.在app.json中启动刷新, 在Windows 中, 添加  "enablePullDownRefresh":"true" 二.在需要刷新的页面中写(若是 ...

  7. UWP开发入门(七)——下拉刷新

    本篇意在给这几天Win10 Mobile负面新闻不断的某软洗地,想要证明实现一个简单的下拉刷新并不困难.UWP开发更大的困难在于懒惰,缺乏学习的意愿.而不是“某软连下拉刷新控件都没有”这样的想法. 之 ...

  8. C#构造方法(函数) C#方法重载 C#字段和属性 MUI实现上拉加载和下拉刷新 SVN常用功能介绍(二) SVN常用功能介绍(一) ASP.NET常用内置对象之——Server sql server——子查询 C#接口 字符串的本质 AJAX原生JavaScript写法

    C#构造方法(函数)   一.概括 1.通常创建一个对象的方法如图: 通过  Student tom = new Student(); 创建tom对象,这种创建实例的形式被称为构造方法. 简述:用来初 ...

  9. 使用jquery结合ajax做下拉刷新页面,上拉加载页面,俗称分页

    jquery结合iscroll.js做下拉刷新页面,上拉加载页面 先上代码,里面都有注释这就不一一说明了 <!DOCTYPE html> <html lang="en&qu ...

随机推荐

  1. Web开发中 MTV模式与MVC模式的区别 联系 概念

    MTV 与 MVC模式的区别 联系 概念: MTV: 所谓MTV指的就是: M:model (模型),指的是ORM模型. T:template (模板),一般Python都是使用模板渲染的方式来把HT ...

  2. <JavaScript> 关于闭包和this对象

    1.this指向windows是如何得出的 var name = "The Window"; var object = { name : "My Object" ...

  3. LC 955. Delete Columns to Make Sorted II

    We are given an array A of N lowercase letter strings, all of the same length. Now, we may choose an ...

  4. centos下使用virtualenv建立python虚拟环境

    在centos使用python3的virtualenv,先用yum install python3 安装后pip3也已经安装好了,pip3 install virtualenv, 在系统中新建一个空文 ...

  5. 4.git的基本命令

    版本库 index 暂存区,HEAD 将来1.0,2.0的指向  多次add,一次commit 每次commit一次,head就指向了最新的版本.head是回退版本的时候会用到 一般有开发的分支,ma ...

  6. 数据集成、变换、归约及相关MATLAB工具箱函数

    数据预处理的主要内容包括数据清洗.数据集成.数据变换和数据规约,在数据挖掘的过程中,数据预处理工作量占到了整个过程的60%.数据清洗在上一篇博客中写过,这里主要写后面三部分. 数据集成 数据挖掘需要的 ...

  7. JKD1.8新特性

    1.Optional类 Optional是jdk1.8引入的类型,Optional是一个容器对象,它包括了我们需要的对象,使用isPresent方法判断所包 含对象是否为空,isPresent方法返回 ...

  8. souce and bash 的区别

    对于一些环境变量的配置文件,如想使更改后立即生效,多用 souce +file 执行后即可.如/etc/profile 里加了配置, source 和  bash 的区别: source filena ...

  9. WIN10家庭版添加"本地安全策略"

    新建文本文件 输入一下命令 @echo off pushd "%~dp0" dir /b C:\Windows\servicing\Packages\Microsoft-Windo ...

  10. 攻防世界WEB新手练习

    0x01 view_source 0x02 get_post 这道题就是最基础的get和post请求的发送 flag:cyberpeace{b1e763710ff23f2acf16c2358d3132 ...