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 ...
随机推荐
- iOS RunLoop简介
一.什么是RunLoop? RunLoop是运行循环,每个Cocoa应用程序都由一个处于阻塞状态的do/while循环驱动,当有事件发生时,就把事件分派给合适的监听器,如此反复直到循环停止.处理分派的 ...
- iOS 点击TextField不弹出软键盘的解决方案
开发中遇到: 在模拟器里面,textfield可以通过电脑键盘输入,可是怎么也不会自动弹出模拟器软键盘 解决方案: 切换一下键盘,command+shift+k,Xcode6.3 中只能是一种输入源
- iOS之自动调节输入文本框的高度
//自动调节输入文本框的高度 - (void)textViewDidChange:(UITableView *)textView{ float height; if ([[[UIDevice curr ...
- iOS APP上架过程常见问题
1.生产证书失效 2.上图中的蓝色选中部分的Provising Profile文件页需要导入,(调试证书(Developer).发布证书(distribution).还有Provising Profi ...
- Android开发的小技巧,在Android Studio中使用Designtime Layout Attributes
在编写xml文件时,为了预览效果,经常会使用默认填上一些内容,比如TextView时,随便写上一个text <TextView ... android:text="Name:" ...
- iOS程序破解——获取.ipa程序包
原文在此 首先肯定不是获取自己的ipa包. 为什么要获取ipa包呢?比如,在仿写一些程序时,避免不了获取它的图片素材等等,那么最快也是最有效的方式就是获取原程序的ipa包.更或者,你想要逆向分析某一款 ...
- IOS开发之Bug--遇到一个类型不确定的bug
下面的问题不大,是我在开发中遇到的问题: 然后我就google搜一下这个报错 . 下面就解决了:
- MVC学习系列5--Layout布局页和RenderSection的使用
我们开发网站项目的时候,都会遇到这样的问题:就是页面怎么统一风格,有一致的外观,在之前ASP.NET的时代,我们有两种选择,一个是使用MasterPage页,一个是手动,自己在每个页面写CSS样式,但 ...
- 【转】Hadoop FS Shell命令
FS Shell 调用文件系统(FS)Shell命令应使用 bin/hadoop fs <args> 的形式. 所有的的FS shell命令使用URI路径作为参数.URI格式是scheme ...
- Linux 使用fdisk添加新分区
Linux系统由于数据累计增长.前期存储规划不合理等诸多因素,出现存储不够用的情况时,此时就需要扩展逻辑分区或添加新的逻辑分区.下面介绍一下通过使用fdsik添加新的逻辑分区. 首先使用df命令检查文 ...