几周之前在博客更新一篇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 ?

Listbox – ScrollViewer performance improvement for Mango and how it impacts your existing application?

ScrollViewer.ManipulationMode 属性

MatrixTransform 类

Windows phone应用开发[22]-再谈下拉刷新的更多相关文章

  1. iOS开发 XML解析和下拉刷新,上拉加载更多

    iOS开发 XML解析和下拉刷新,上拉加载更多 1.XML格式 <?xml version="1.0" encoding="utf-8" ?> 表示 ...

  2. android开发游记:SpringView 下拉刷新的高效解决方式,定制你自己风格的拖拽页面

    关于下拉刷新/上拉载入很多其它的解决方式网上已经有非常多了,浏览了眼下主流的下拉控件比方PullToRefresh库等.第一:大多数实现库都难以进行动画和样式的自己定义. 第二:不能非常好的兼容多种滚 ...

  3. Android开发学习之路-下拉刷新怎么做?

    因为最近的开发涉及到了网络读取数据,那么自然少不了的就是下拉刷新的功能,搜索的方法一般是自己去自定义ListView或者RecyclerView来重写OnTouch或者OnScroll方法来实现手势的 ...

  4. Android开发学习之路-下拉刷新以及GridView的使用

    GridView是类似于ListView的控件,只是GridView可以使用多个列来呈现内容,而ListView是以行为单位,所以用法上是差不多的. 主布局文件,因为要做下拉刷新,所以加了一个Prog ...

  5. 指令汇B新闻客户端开发(三) 下拉刷新

    现在我们继续这个新闻客户端的开发,今天分享的是下拉刷新的实现,我们都知道下拉刷新是一个应用很常见也很实用的功能.我这个应用是通过拉ListView来实现刷新的,先看一张刷新的原理图 从图中可知,手指移 ...

  6. 微信小程序开发——微信小程序下拉刷新真机无法弹回

    开发工具中下拉之后页面回弹有一定的延迟,这个时间也有点久.真机测试,下拉后连回弹都没有,这个问题要解决,就得在下拉函数里加上停止下拉刷新的API,如下: /** * 下拉刷新 */ onPullDow ...

  7. 前端提升生产力系列三(vant3 vue3 移动端H5下拉刷新,上拉加载组件的封装)

    | 在日常的移动端开发中,经常会遇到列表的展示,以及数据量变多的情况下还会有上拉和下拉的操作.进入新公司后发现移动端好多列表,但是在看代码的时候发现,每个列表都是单独的代码,没有任何的封装,都是通过v ...

  8. listview下拉刷新和上拉加载更多的多种实现方案

    listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明. 方案一:添加头布局和脚布局        android系统为listview提供了addfootview ...

  9. android124 zhihuibeijing 新闻中心-新闻 -北京页签 下拉刷新

    缓存工具类:以url为key,json数据为value, package com.itheima.zhbj52.utils; import com.itheima.zhbj52.global.Glob ...

随机推荐

  1. SharePoint Conference 2014 Keynote

    让我们来看看今年 SharePoint Conference 2014 的重点都是些什么内容.虽然 BI 那个视频很有趣儿,但是 keynote 可能更重要一些,所以,先研究 keynote. 概括来 ...

  2. O365(世纪互联)SharePoint 之站点个性化

    前言 上一篇文章中,我们简单介绍了如何使用O365中SharePoint Online文档库,SharePoint Online的优点就是提供给我们很多非常方便开箱即用的功能,让我们快速的搭建站点,方 ...

  3. UIScrollView出现位移问题

    啦啦啦啦啦~~~ UINavigationController和UIScrollView一起使用时会导致UIScrollView位置偏移 情况:UINavigationController的视图上的第 ...

  4. Android Activity生命周期与启动模式

    Activity的完整生命周期如下图: Activity的加载模式有四种: standard: 标准模式,默认的加载模式,每次通过这种模式启动目标Acitivity,都创建一个新的实例,并将该Acti ...

  5. 《javascript权威指南》读书笔记——第一篇

    <javascript权威指南>读书笔记——第一篇 金刚 javascript js javascript权威指南 由于最近想系统学习下javascript,所以开始在kindle上看这本 ...

  6. SqlServer数据冗余的问题和解决

    1问题: 1>造成了存储空间的浪费. 2>更新异常.删除异常. 所以一般情况不允许在表中出现数据冗余. 2怎么解决? 把原来表中的数据拆分成多个表来存储. 当把表中的信息拆分成多个表来存储 ...

  7. Oracle 12.1.0.2 New Feature翻译学习【In-Memory column store内存列存储】【原创】

    翻译没有追求信达雅,不是为了学英语翻译,是为了快速了解新特性,如有语义理解错误可以指正.欢迎加微信12735770或QQ12735770探讨oracle技术问题:) In-Memory Column ...

  8. C++基础——模拟事务 (1)COMMAND模式

    =================================版权声明================================= 版权声明:原创文章 禁止转载  请通过右侧公告中的“联系邮 ...

  9. 有关stm32的问题,程序里面的u8、u16这些是什么意思啊

    u8 是 unsigned charu16 是 unsigned shortu32 是 unsigned int

  10. Redis-cli命令最新总结

    资料来源: http://redisdoc.com/ http://redis.io/commands 连接操作相关的命令 默认直接连接  远程连接-h 192.168.1.20 -p 6379 pi ...