Windows phone应用开发[22]-再谈下拉刷新

几周以前在博客更新一篇Windows phone应用开发[18]-下拉刷新 博文,有不少人在微博和博客评论中提到了不少问题.其实在实际项目中我基于这篇博文提出解决问题思路优化了这个解决方案.为了可以详细系统解决和说明补充这个问题.以为单独开一篇博文来解答.在评论中提到的一些问题.css

在原来的源码中有人提到:html

#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的滑动更流畅.git

有人提到下拉时没有自动刷新效果效果.为了详细说明这个问题.首先来看看上篇博客中提到关于下拉刷新源码的实现.找到源码中继承ListBox的类RefreshBox.在该类实现中重写了OnApplyTemplate方法.在该方法中能够看到:github

   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:   }

首先在OnApplyTemplate()方法中能够看到作了以下几件事:windows

A: 添加ScrollViewer 关于MouseMove 和ManipulationComplated 两个事件订阅 【ScrollViewer非空时取消】app

B:获取ListBox中ScrollViewer对象ide

C:获取顶部刷新提示Element 的引用对象测试

D:初始化控制顶部刷新提示VisualState 状态优化

其实到这里 须要额外说明一下实现下拉刷新的原理.从源码中能够看出. 在下拉时会首先触发MouseMove 事件. MouseMove事件主要做用是用来经过下拉的距离来控制下拉刷新状态[下拉、松手刷新]两种状态切换提示. 下拉刷新并非下拉后会当即刷新.而是用户松手后列表回到顶部才开始刷新数据.等用户手势操做离开了屏幕就会自动触发ManipulationComplated 事件.你能够看到在Complated事件中:this

   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:          }

经过判断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>

在后台代码中添加对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:              }         

 

从代码逻辑可见.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:          }

其实以拿到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 类

相关文章
相关标签/搜索