标签:
接触Android开发有一段时间,经常会遇到数据加载形式的设计,现在最常见的是“下拉刷新”、“上拉加载”以及“滚动到底部自动加载”,这些往往是基于ListView、GridView和ExpandableListView。此文主要简单讨论这些加载形式的设计实现。
不妨先去理解基本的思想(大量图参考自网络,引用地址放在文章最后):
? 滑动动画设计
左图为传统的下拉动画,右图方案随着SwipeRefreshLayout的出现也大量流行。
? 有效拉伸距离
表示下拉刷新之后列表第一个item可以被允许下拉最大距离,经调研开源库,大部分采用选择屏幕高度的1/2作为阈值。
? 伸缩速率
在下拉时,较好的体验是:列表下滚的速度要小于手指触摸点下拉的速度。因为没有严格的标准,速率比值为50%-80%较为合理(调研的库列于文章最后)。
? 数据更新
最后是数据更新时机和策略的选择,在请求初识数据列表、刷新、以及加载更多等时候都要进行数据的更新。加载策略一般为异步任务:Handler和AsyncTask,选用后者的为多。
具体的实现方式可以有多种,以两种为例:
如图所示,充分使用布局的动态性来完成,采用线性布局,Header View 和 Footer View。宽、高分别为match_parent、wrap_content,相应Padding设置为负。Content View宽高分别为match_parent。在用户滑动时,监听滑动事件,顺势改变Content View的padding值,实现下拉、上拉的展示。这种实现方式有个很明显的缺点:不够流畅,完全依赖View的重绘。
这种方式本质上这种方法使用了ListView的HeaderView和FooterView,下拉通过设计HeaderView的高度来展示,同理,上拉时增加FooterView的高度,这样在形式上就完成了整个界面的响应。具体以上拉和到底部自动加载为例。当ListView滚动到底部时自动触发加载操作。同时,根据用户触摸事件判断来判断是否在执行上拉加载更多,如下
在onScroll事件中判断,是否达到了加载条件,加载条件也较为简单:判断1、是否到达底部2、目前没有处于加载状态;3、当前正在进行上拉操作,在这样的情况下可以执行数据加载。
那么在数据加载时候需要对isLoading进行更新,不在加载时说明FooterView已经达到了最大高度或是最小高度(上拉完毕和并没有上拉两种情况),那么就对其进行消除。isLoading刚刚被设置为True时,则是上拉事件刚刚触发时,所以需要对FooterView进行添加。下拉事件是对HeaderView同样的控制机制。isLoading是一个控制状态的变量,在整个流程中转化状态显得至关重要,在情形稍微复杂点时,我们需要添加其他一些变量和状态进行控制,也就形成状态机。
? ViewGroup+Scroller
?在初始时通过滚动,使得该组件在Y轴方向上滚动HeaderView的高度的距离,这样HeaderView就被隐藏掉了。
?当组件被滚动到顶端时,如果用户继续下拉,那么拦截触摸事件,然后通过Scroller来滚动y轴的偏移量,实现逐步的显示HeaderView,从而到达下拉的效果,当用户滑动到最底部时会触发加载更多的操作。
这种策略可以抛弃对ListView的HeaderView、FooterView的依赖,实现上更为灵活。
https://github.com/Maxwin-z/XListView-Android.git
值得一提的是,我最喜欢的虎扑Android客户端也是使用了该库。
XListView思路基本上是和ListView类似,充分利用ListView的HeaderView和FooterView,通过高度的调整来展示动态变化。下述源码中,我们上拉加载为例,上拉操作被捕捉,当发现最后可见的Position是mTotalItemCount-1时,说明已经上拉到了列表底部,deltaY<0表示用户仍然在上拉,在此对FooterView的高度进行Update,update方法是对height进行+delta,所以此时delta是负值,传入时候应该传-delta。
XListView虽然简单好用,但是也继承了ListView的缺点:依赖ListView自带的HeaderView和FooterView,只能解决ListView的问题,对于GridView和ExpandableListView等并没有支持。
PullToRefresh是比较经典的下拉刷新组件,大概有这么经典:
具体它的特色可以看git上作者给出的:
从实现上来看,其实现方式是用了ViewGroup来包装内层的ContentView,通过事件控制来展示,所以其支持性非常好。基本思路为我们介绍的实现方法三。具体来看如下:
主要通过两个变量来控制流程:mMode && mCurrentMode,mMode表示总的支持方式,如果设计为PULL_FROM_START(0x1),则表示整个app只支持下拉,BOTH(0x3)表示上拉下拉都支持,是一个总的开关,选择后只能在设置里面用户手动更改。而mCurrentMode是在具体控制分支中当前处在的状态,是控制状态机走向最为关键的变量。如用户正在执行下拉操作:PULL_FROM_START(0x1),此时有可能mMode的用户选择的是BOTH(0x3)而并非一定要是PULL_FROM_START(0x1),mCurrentMode是不停变化的。如在PullToRefreshBase:
在PullToRefreshAdapterViewBase中:
mCurrentMode = (mMode != Mode.BOTH) ? mMode : Mode.PULL_FROM_START;
在方法三中我们也提到了,使用ViewGroup的话需要使用Android事件分发机制来完成设计,PullToRefresh正有这样的体现
PullToRefreshBase监听到了用户的上拉下拉事件,对mCurrentMode进行修改以保证状态转化正确。这里返回值是mIsBeingDragged,如果为true表明onInterceptTouchEvent已经可以处理,也就是在滑动ListView 。
在PullToRefreshBase的onTouchEvent() 函数中,先保存最后滑到的位置,并调用pullEvent函数。
在pullEvent函数中,发现用户拉动的条件已经满足了滑动条件(itemDimension<Math.abd(newScrollValue)),则将应用状态转化为刷新状态,启用刷新函数。
newScrollValue和itemDimension的计算如下 (以上拉为例),通过计算滑动的距离,让其除以阈值FRICTION(FRICTION即为开始提到的伸缩速率,这里作者是设置FRICTION为2,即列表滑动速率是用户实际滑动速率的50%,以实现拖拽效果)。
当发现滑动过程已经满足刷新或者加载条件时,则将返回值mIsBeingDragged设置为false,让ListView 停止阻塞上拉下拉事件,而将处理交给ViewGoup,也就是开始滑动整体内容(包涵HeaderView、ContentView、FooterView等,这里的HeaderView和FooterView并不是ListView自带的,而是自定义View)。
这里简单列下流程:
标签:
原文地址:http://blog.csdn.net/clumsypanda/article/details/51444461