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 ...
随机推荐
- HTML5进阶段内联标签汇总(小篇)
内联元素,与别人公用一行,但是设置宽高无效.其特点: ①和其他元素都在一行上: ②高,行高及外边距和内边距不可改变: ③宽度就是它的文字或图片的宽度,不可改变 ④内联元素只能容纳文本或者其他内联元素 ...
- JAVA动态代理模式(从现实生活角度理解代码原理)
所谓动态代理,即通过代理类:Proxy的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联. java动态代理主要是使用java.lang.reflect包中的两个 ...
- statusbarhidden stuff 状态栏的各种特性
plist 文件中的View controller-based status bar appearance 设置的是 在viewcontroller 中 对状态栏进行修改是否起作用. 设置状态栏隐藏和 ...
- UITextView回收或关闭键盘
iOS开发中,发现UITextView没有像UITextField中textFieldShouldReturn:这样的方法,那么要实现UITextView关闭键盘,就必须使用其他的方法,下面是可以使用 ...
- 【android 开 发 】 - Android studio 下 NDK Jni 开发 简单例子
Android 开发了一段时间,一方面 ,感觉不留下点什么.有点对不起自己, 另一方面,好记性不如烂笔头,为了往后可以回头来看看,就当做是笔记,便决定开始写博客.废话不多说 ! 今天想搞一搞 ndk ...
- Android屏幕适配总结
一.首先需要明白的几个概念 1.屏幕尺寸:也就是我们平常所说的某某手机几寸屏.比如苹果的4.7寸, 荣耀6的5.5寸.这里说的寸是英寸(1 英寸 = 2.54 厘米). 计算方法:屏幕尺寸=对角先尺寸 ...
- SQL Server 2012 The report server cannot open a connection to the report server database
案例环境: 操作系统版本: Windows Server 2012 R2 Standard 数据库版本 : SQL SERVER 2012 SP2 案例介绍: 今天进入一台新安装的SQL ...
- 【转】HiveQL:对数据定义的学习
1.Hive中的数据库: 它是表的一个目录或者命名空间,用来避免表命名冲突,我们通常使用数据库来将生产表组织成逻辑组. 基本命令: (1)创建一个数据库(如果不存在该数据库): create data ...
- 大数据系列(4)——Hadoop集群VSFTP和SecureCRT安装配置
前言 经过前三篇文章的介绍,已经通过VMware安装了Hadoop的集群环境,当然,我相信安装的过程肯定遇到或多或少的问题,这些都需要自己解决,解决的过程就是学习的过程,本篇的来介绍几个Hadoop环 ...
- oracle行转列与列转行
一.行转列 在有些应用场景中,如学生成绩.学生所有科目的成绩都保存到一张表里面,当我们需要以列表的形式显示出学生所对应的每一科目的成绩的时候,需要使用到行转列. 示例 -- 学生成绩表 create ...