Windows phone应用开发[18]-下拉刷新
在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>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
众所周知.实现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: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
获取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: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
在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: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
如上通过判断判断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>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
在加载控件时. 我们需要获取到自定义控件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: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
当SrollViewer为Null订阅事件操作时.如果在不同SDK版本[WP7 Or WP8]执行过程发现订阅的ManipulationCompleted没有被触发. 可以采用如下方式来强制添加处理事件[在WP7 And WP8 均测试有效] :
1: ElementScrollViewer.AddHandler(ScrollViewer.ManipulationCompletedEvent,
2: new EventHandler<ManipulationCompletedEventArgs>(viewer_ManipulationCompleted), true);
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
在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: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
同样的逻辑.在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: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
这样整个下拉刷新的基本逻辑实现思路已经很明朗.可以完整重写整个ListBox实现.
当第一次进来加载数据:

下拉是效果:

刚松开效果:

这样下拉刷新结合ListBox本身上下滑动刷新基本实现我们如上三个需求.
源码下载[https://github.com/chenkai/LoadData]
Contact: @chenkaihome
Windows phone应用开发[18]-下拉刷新的更多相关文章
- 微信小程序开发之 下拉刷新,上拉加载更多
本文记载了如何在微信小程序里面实现下拉刷新,上拉加载更多 先开看一下界面 大致如此的界面吧. 这个Demo使用了微信的几个Api和事件,我先列出来. 1.wx.request (获取远程服务器的数据, ...
- iOS开发-UIRefreshControl下拉刷新
下拉刷新一直都是第三库的天下,有的第三库甚至支持上下左右刷新,UIRefreshControl是iOS6之后支持的一个刷新控件,不过由于功能单一,样式不能自定义,因此不能满足大众的需求,用法比较简单在 ...
- 微信小程序云开发-列表下拉刷新
一.json文件开启页面刷新 开启页面刷新.在页面的json文件里配置两处: "enablePullDownRefresh": true, //true代表开启页面下拉刷新 &qu ...
- Windows phone应用开发[22]-再谈下拉刷新
几周之前在博客更新一篇Windows phone应用开发[18]-下拉刷新 博文,有很多人在微博和博客评论中提到了很多问题.其实在实际项目中我基于这篇博文提出解决问题思路优化了这个解决方案.为了能够详 ...
- Android几种强大的下拉刷新库
BeautifulRefreshLayout 众多优秀的下拉刷新(除了我写的之外T_T) 说起下拉刷新,好像经历一段历史的洗礼... (1)在我刚学android的时候,用的是XListView,在g ...
- UWP开发入门(七)——下拉刷新
本篇意在给这几天Win10 Mobile负面新闻不断的某软洗地,想要证明实现一个简单的下拉刷新并不困难.UWP开发更大的困难在于懒惰,缺乏学习的意愿.而不是“某软连下拉刷新控件都没有”这样的想法. 之 ...
- windows phone 下拉刷新
在windows phone 中采用数据列表时为了保证用户体验常遇到加载数据的问题.这个问题普遍到只要你用到数据列表就要早晚面对这个问题. 很多人会说这个问题已经有解决方案. 其实真正问题并不在于如何 ...
- Android SwipeRefreshLayout 下拉刷新——Hi_博客 Android App 开发笔记
以前写下拉刷新 感觉好费劲,要判断ListView是否滚到顶部,还要加载头布局,还要控制 头布局的状态,等等一大堆.感觉麻烦死了.今天学习了SwipeRefreshLayout 的用法,来分享一下,有 ...
- Android开发学习之路-下拉刷新怎么做?
因为最近的开发涉及到了网络读取数据,那么自然少不了的就是下拉刷新的功能,搜索的方法一般是自己去自定义ListView或者RecyclerView来重写OnTouch或者OnScroll方法来实现手势的 ...
随机推荐
- 网络分析之Pgrouting(转载)
网上关于Pgrouting的使用介绍太简单了,这里想详细的总结一下Pgrouting的使用,其实主要参照官方文档:http://workshop.pgrouting.org/ 第一步:配置环境 关于P ...
- 数组Array,集合List与字符串String,整形int的get类方法。
比如 public static List<DownCountCmd> getDownCountCmdList() { return downCount; } List<DownCo ...
- Android Studio关于SVN的相关配置及从SVN检出项目
一.安装配置: 如图,安装时必须自定义选择 command line 否则不会安装的 安装完成后,打开 IDE 的 setting 配置面板: 如上图路径 Version Control 下的 Sub ...
- React Native环境配置和简单使用
# 前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会 ...
- web.xml 中的listener、 filter、servlet 加载顺序及其详解
在项目中总会遇到一些关于加载的优先级问题,近期也同样遇到过类似的,所以自己查找资料总结了下,下面有些是转载其他人的,毕竟人家写的不错,自己也就不重复造轮子了,只是略加点了自己的修饰. 首先可以肯定的是 ...
- Spring实现AOP的4种方式
了解AOP的相关术语:1.通知(Advice):通知定义了切面是什么以及何时使用.描述了切面要完成的工作和何时需要执行这个工作.2.连接点(Joinpoint):程序能够应用通知的一个“时机”,这些“ ...
- JavaScript(js)的replace问题的解决
我是前端的门外汉,js我用得比较少.今天意外发现js自带的replace “居然”只替换1处,而其它的许多许多语言都是替换全部的. 你可能会说,切,我早就知道.高手请绕道. 你可能会说,用js的正则就 ...
- MySQL SQL 注入
如果您通过网页获取用户输入的数据并将其插入一个MySQL数据库,那么就有可能发生SQL注入安全的问题. 本博文将为大家介绍如何防止SQL注入,并通过脚本来过滤SQL中注入的字符. 所谓SQL注入,就是 ...
- 【转】java NIO 相关知识
原文地址:http://www.iteye.com/magazines/132-Java-NIO Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的 ...
- jq attr()改变checkbox的checked无效!!!!
今天做项目发现用attr()改变checked,实现全选功能的时候发现,第一次点击有效,之后点击全选功能便实效. 一开始以为是自己写错了,在各种碰壁之后,才猛然发现,原来这是jq的一个小bug. 在j ...