Windows phone应用开发[22]-再谈下拉刷新
几周之前在博客更新一篇Windows phone应用开发[18]-下拉刷新 博文,有很多人在微博和博客评论中提到了很多问题.其实在实际项目中我基于这篇博文提出解决问题思路优化了这个解决方案.为了能够详细系统解决和说明补充这个问题.觉得单独开一篇博文来解答.在评论中提到的一些问题.
在原来的源码中有人提到:
#11楼 灬番茄2013-10-06 14:53
@chenkai
p.Y值一直是你设置的默认值,所以if (p.Y < -VerticalPullToRefreshDistance)这个判断一直是进不去的。
我阅读了另外一篇下拉刷新的文章http://www.cnblogs.com/wuzhsh/archive/2012/09/04/2670307.html,里面提到ScrollViewer的ManipulationMode属性设为Conrtrol(必需),默认是System。然后我也在你的源码里添加了这句ElementScrollViewer.ManipulationMode = ManipulationMode.Control; 才实现了下拉刷新。至于原理却没搞清楚,MSDN文档里也只是说System比Control的滑动更流畅.
有人提到下拉时没有自动刷新效果效果.为了详细说明这个问题.首先来看看上篇博客中提到关于下拉刷新源码的实现.找到源码中继承ListBox的类RefreshBox.在该类实现中重写了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: ElementScrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer;
10:
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; }首先在OnApplyTemplate()方法中可以看到做了如下几件事:
A: 添加ScrollViewer 关于MouseMove 和ManipulationComplated 两个事件订阅 【ScrollViewer非空时取消】
B:获取ListBox中ScrollViewer对象
C:获取顶部刷新提示Element 的引用对象
D:初始化控制顶部刷新提示VisualState 状态
其实到这里 需要额外说明一下实现下拉刷新的原理.从源码中可以看出. 在下拉时会首先触发MouseMove 事件. MouseMove事件主要作用是用来通过下拉的距离来控制下拉刷新状态[下拉、松手刷新]两种状态切换提示. 下拉刷新并不是下拉后会立即刷新.而是用户松手后列表回到顶部才开始刷新数据.等用户手势操作离开了屏幕就会自动触发ManipulationComplated 事件.你可以看到在Complated事件中:
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: if (PullRefresh != null)
7: PullRefresh(this, EventArgs.Empty);
8: isPulling = false;
9: ChangeVisualState(true);
10: }
11: }
.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; }通过判断ElementRealse也就是下拉刷新顶部提示部分下拉的距离来触发事件PullRefresh来刷新新的数据. 其中VerticalPullToRefreshDistance属性是用来判断当下啦到多少距离时才触发刷新事件.可以定义控件时预设.在回到上文.来回答为何在下拉时没有触发刷新事件?
p.y对象的值为何一直为90? 那是因为在刚开始定义ElementRealse对象时对顶部Manger Top值就是90, 那为何在下拉结束时 这个对应的X值没有跟随滑动操作变化? 其实这个问题和SCrollView的ManipulationMode属性有关系. 首先我们可以在OnApplyTemplate方法可以看到没有设置MainpulationMode属性的值. 而MainpilationMode属性在默认情况下是设置为System的.也就是指定系统来处理ListBox的平滑滚动的.ScrollViewer并没有拖到顶部或底部的事件,而且当ScrollViewer的ManipulationMode为System的时候,是不能获取到ScrollViewer滚动条的当前位置.也就是无法动态在ManipulationComplated 事件来获取ElementRealse距离顶部的距离.这也就是为何p.y的值一直是初始化90 而不随着滑动操作发生改变的原因.
那在具体点? 为何设置MainpulationMode属性为System 后就无法获取ScrollViewer滚动条的位置? System和Control不同在于.两者的变换(Transform)方式不一样,当ManipulationMode为System的时候,ScrollViewer的变换方式是MatrixTransform[系统矩阵变换处理滑动],所以无法获取ScaleY或者TranslateY等属性。通过这个MatrixTransform也没有办法直接拿到当前ScrollViewer的上下滚动、压缩状态。而置为Control时,变换方式就成了CompositeTransform,通过CompositeTransform就可以得到ScrollViewer的TranslateY值(当到达顶部的时候,TranslateY变为正值,其余时候为负值,超过底部时,绝对值大于ScrollViewer内容长度),然后在ScrollViewer的操作事件ManipulationStarted、ManipulationDetla或ManipulationCompleted中,获取ScrollViewer的变换方式,得到TranslateY值,最后判断是否到达顶部或底部,决定是否要进行处理.
可以看到两者之间的本质原理上不同.这也就能够解释为何. 当ScrollViewer 的ManipulationMode属性 默认为System时无法即时获取下拉ElementRealse 的X的值了.也就是说用目前下拉刷新必须设置ManipulationMode属性为Control. 但你测试后发现. 下拉刷新逻辑能够正常触发刷新事件.但是整个滑动过程会明显感觉卡了很多[需要声明的是ListBox不存在虚拟化的问题].没有设置为System系统处理方式平滑流畅. 那如何来解决设置设置ManipulationMode属性为Control 滑动会卡顿的问题? 或是有没有一个能够获得System处理滑动一样平滑体验同时又能够判断ScrollViewer当前的位置状态的解决方案.
经过一番周折在MSDN Blog上找到了一个能够实现如上两点解决方案:
Windows Phone Mango change, Listbox: How to detect compression(end of scroll) states ?
首先来说说这个解决方案的实现.当然我们实现ListBox上平滑处理发现系统ManipulationMode属性为system 矩阵处理方式滑动体验很流畅.那如何来判断在设置为System时获取ScrollViewer的状态呢? 答案是采用VisualState.
要实现采用Visual State来获取SCrollViewer当前位置.只需要现在Xaml文件添加如下代码[只截取其中Visual State 全部代码见源码]:
1: <VisualStateManager.VisualStateGroups>
2: <VisualStateGroup x:Name="ScrollStates">
3: <VisualStateGroup.Transitions>
4: <VisualTransition GeneratedDuration="00:00:00.5"/>
5: </VisualStateGroup.Transitions>
6: <VisualState x:Name="Scrolling">
7: <Storyboard>
8: <DoubleAnimation Storyboard.TargetName="VerticalScrollBar"
9: Storyboard.TargetProperty="Opacity" To="1" Duration="0"/>
10: <DoubleAnimation Storyboard.TargetName="HorizontalScrollBar"
11: Storyboard.TargetProperty="Opacity" To="1" Duration="0"/>
12: </Storyboard>
13: </VisualState>
14: <VisualState x:Name="NotScrolling">
15: </VisualState>
16: </VisualStateGroup>
17: <VisualStateGroup x:Name="VerticalCompression">
18: <VisualState x:Name="NoVerticalCompression"/>
19: <VisualState x:Name="CompressionTop"/>
20: <VisualState x:Name="CompressionBottom"/>
21: </VisualStateGroup>
22: <VisualStateGroup x:Name="HorizontalCompression">
23: <VisualState x:Name="NoHorizontalCompression"/>
24: <VisualState x:Name="CompressionLeft"/>
25: <VisualState x:Name="CompressionRight"/>
26: </VisualStateGroup>
27: </VisualStateManager.VisualStateGroups>
.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; }
.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状态的变化事件订阅.
1: sv = (ScrollViewer)FindElementRecursive(MainListBox, typeof(ScrollViewer));
2: if (sv != null)
3: {
5: FrameworkElement element = VisualTreeHelper.GetChild(sv, 0) as FrameworkElement;
6: if (element != null)
7: {
8: VisualStateGroup group = FindVisualState(element, "ScrollStates");
9: if (group != null)
10: group.CurrentStateChanging += new EventHandler<VisualStateChangedEventArgs>(group_CurrentStateChanging);
11:
12: VisualStateGroup vgroup = FindVisualState(element, "VerticalCompression");
13: VisualStateGroup hgroup = FindVisualState(element, "HorizontalCompression");
14: if (vgroup != null)
15: vgroup.CurrentStateChanging += new EventHandler<VisualStateChangedEventArgs>(vgroup_CurrentStateChanging);
16:
17: if (hgroup != null)
18: hgroup.CurrentStateChanging += new EventHandler<VisualStateChangedEventArgs>(hgroup_CurrentStateChanging);
19: }
20: }
.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; }
从代码逻辑可见.Xaml文件重写了整个ScrollViewer的样式并添加两组Vistaul State Group状态的标识. 后代代码通过订阅ScrollViewer垂直和水平滑动的状态开始事件CurrentStateChanging.在事件对应的通过如下方式进行判断当前ScrollViewer的状态:
1: private void vgroup_CurrentStateChanging(object sender, VisualStateChangedEventArgs e)
2: {
3: if (e.NewState.Name == "CompressionTop")
4: {
5: #region Goto Top
6: #endregion
7: }
8: else if (e.NewState.Name == "CompressionBottom")
9: {
10: #region Goto Bottom
11: #endregion
12: }
13: else if (e.NewState.Name == "NoVerticalCompression")
14: {
15: #region No Vertical Compression
16: #endregion
17: }
18: }
.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; }
其实以拿到VerticalCompression和HorizontalCompression两种VisualStateGroup,可以用来检测ListBox的上下左右方向的压缩状态。这种解决方案的做法是运用VisualState检测ScrollViewer滚动状态,来判断SCrollViewer是到了顶部还是底部 以及是否滚动中状态.只有在滚动停止时,即NotScrolling状态,检测滚动偏移(Offset),如果偏移加上滚动前位置超过了控件内容总长度(非可视长度),就进行刷新或者其他相应的处理。
其实基于这个方案.结合第一个方法稍微改造一下ScrollViewer 的Vistual State 即可达到平滑处理滑动下拉刷新提示操作.这里就不做过多赘述了.
源码下载[https://github.com/chenkai/ListBoxVisualStatesDemo/tree/master/ListBoxVisualStatesDemo]
Contact ME [@chenkaihome]
参考资料:
Windows Phone Mango change, Listbox: How to detect compression(end of scroll) states ?
ScrollViewer.ManipulationMode 属性
Windows phone应用开发[22]-再谈下拉刷新的更多相关文章
- iOS开发 XML解析和下拉刷新,上拉加载更多
iOS开发 XML解析和下拉刷新,上拉加载更多 1.XML格式 <?xml version="1.0" encoding="utf-8" ?> 表示 ...
- android开发游记:SpringView 下拉刷新的高效解决方式,定制你自己风格的拖拽页面
关于下拉刷新/上拉载入很多其它的解决方式网上已经有非常多了,浏览了眼下主流的下拉控件比方PullToRefresh库等.第一:大多数实现库都难以进行动画和样式的自己定义. 第二:不能非常好的兼容多种滚 ...
- Android开发学习之路-下拉刷新怎么做?
因为最近的开发涉及到了网络读取数据,那么自然少不了的就是下拉刷新的功能,搜索的方法一般是自己去自定义ListView或者RecyclerView来重写OnTouch或者OnScroll方法来实现手势的 ...
- Android开发学习之路-下拉刷新以及GridView的使用
GridView是类似于ListView的控件,只是GridView可以使用多个列来呈现内容,而ListView是以行为单位,所以用法上是差不多的. 主布局文件,因为要做下拉刷新,所以加了一个Prog ...
- 指令汇B新闻客户端开发(三) 下拉刷新
现在我们继续这个新闻客户端的开发,今天分享的是下拉刷新的实现,我们都知道下拉刷新是一个应用很常见也很实用的功能.我这个应用是通过拉ListView来实现刷新的,先看一张刷新的原理图 从图中可知,手指移 ...
- 微信小程序开发——微信小程序下拉刷新真机无法弹回
开发工具中下拉之后页面回弹有一定的延迟,这个时间也有点久.真机测试,下拉后连回弹都没有,这个问题要解决,就得在下拉函数里加上停止下拉刷新的API,如下: /** * 下拉刷新 */ onPullDow ...
- 前端提升生产力系列三(vant3 vue3 移动端H5下拉刷新,上拉加载组件的封装)
| 在日常的移动端开发中,经常会遇到列表的展示,以及数据量变多的情况下还会有上拉和下拉的操作.进入新公司后发现移动端好多列表,但是在看代码的时候发现,每个列表都是单独的代码,没有任何的封装,都是通过v ...
- listview下拉刷新和上拉加载更多的多种实现方案
listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明. 方案一:添加头布局和脚布局 android系统为listview提供了addfootview ...
- android124 zhihuibeijing 新闻中心-新闻 -北京页签 下拉刷新
缓存工具类:以url为key,json数据为value, package com.itheima.zhbj52.utils; import com.itheima.zhbj52.global.Glob ...
随机推荐
- Javascript实现格式化输出
前两天看面试题,其中有一道要实现js的格式化输出,具体给出的是: Javascript实现格式化输出,比如输入999999999,输出为999,999,999 我的实现方式是 function for ...
- 【转载】实用的Javascript获取网页屏幕可见区域高度
本文转载原地址:这里 document.body.clientWidth ==> BODY对象宽度 document.body.clientHeight ==> BODY对象高度 docu ...
- 天津政府应急系统之GIS一张图(arcgis api for flex)讲解(三)显示地图坐标系模块
config.xml文件的配置如下: <widget left="3" bottom="3" config="widgets/Coordinat ...
- AJAX初探,XMLHttpRequest介绍
AJAX初探,XMLHttpRequest介绍 AJAX AJAX = Asynchronous JavaScript and XML. 异步的JavaScript和XML. AJ ...
- iOS开发之Socket通信实战--Request请求数据包编码模块
实际上在iOS很多应用开发中,大部分用的网络通信都是http/https协议,除非有特殊的需求会用到Socket网络协议进行网络数 据传输,这时候在iOS客户端就需要很好的第三方CocoaAsyncS ...
- TableViewCell重影问题
UITableView继承自UIScrollview,是苹果为我们封装好的一个基于scroll的控件.上面主要是一个个的UITableViewCell,可以让UITableViewCell响应一些点击 ...
- mysqldump: Got error: 1142: SELECT, LOCK TABLES command denied to user 'root'@'localhost' for table 'accounts' when using LOCK TABLES
AutoMySQLBackup备份时,出现mysqldump: Got error: 1142: SELECT, LOCK TABLES command denied to user 'root'@' ...
- SQL SERVER中什么情况会导致索引查找变成索引扫描
SQL Server 中什么情况会导致其执行计划从索引查找(Index Seek)变成索引扫描(Index Scan)呢? 下面从几个方面结合上下文具体场景做了下测试.总结.归纳. 1:隐式转换会导致 ...
- asp.net signalR 专题—— 第四篇 模拟RPC模式的Hub操作
在之前的文章中,我们使用的都是持久连接,但是使用持久连接的话,这种模拟socket的形式使用起来还是很不方便的,比如只有一个唯一的 OnReceived方法来处理业务逻辑,如下图: protected ...
- JVM之ParNew收集器
新生代收集器,CMS默认搭配,Serial的多线程版本. -XX:UseParNewGC:指定使用ParNew收集器. -XX:ParalletGCThreads:指定限制垃圾收收集的线程数量. 默认 ...