安卓下拉刷新开源库对比

作者:desmond1121

目前仅比对github上star数>1500的下拉刷新开源库,在比较完成之后可能会加入其它有代表性的库.

Repo

Repo Owner Star (2015.12.5) version Snap shot
Android-PullToRefresh (作者已停止维护) chrisbanes 6014 latest
android-Ultra-Pull-To-Refresh liaohuqiu 3413 1.0.11
android-pulltorefresh (作者已停止维护) johannilsson 2414 latest
Phoenix Yalantis 1897 1.2.3
FlyRefresh race604 1843 2.0.0
SwipeRefreshLayout Android Support v4 (19.1.0 ↑) None latest

拓展性

Repo 自定义顶部视图 支持的内容布局
Android-PullToRefresh 不支持,只能改代码。 由于仅支持其中实现的LoadingLayout作为顶视图,改代码实现自定义工作量较大。 任意视图,内置:GridViewListView,HorizontalScrollViewScrollView ,WebView
android-Ultra-Pull-To-Refresh 任意视图。 通过继承PtrUIHandler并调用PtrFrameLayout.addPtrUIHandler()得到最大支持。 任意视图
android-pulltorefresh 不支持,只能改代码。 代码仅一个ListView,耦合度太高,改动工作量较大。 无法扩展,自身为ListView
Phoenix 不支持,此控件特点就是顶部视图及动画。 任意视图,只显示最后一个嵌套的子视图。
FlyRefresh 不支持,此控件特点就是顶部视图及动画。 任意视图
SwipeRefreshLayout 不支持,固定为Material风格 任意视图

易用性

Repo 可在gradle配置 上拉加载 自动加载 滑动阻尼配置
Android-PullToRefresh × × 移动比固定1/2
android-Ultra-Pull-To-Refresh ×
android-pulltorefresh × × × 移动比固定1/1.7
Phoenix × × 移动比固定1/2
FlyRefresh × × ×
SwipeRefreshLayout × × 移动比固定1/2

触屏事件分发

本节分析控件对于触屏事件的分发以及处理拖动的时机,具体拖动实现将在下一节中介绍。 此处添加进一个可以横滑的组件,并将所有组件中的ListView替换为自己实现的ClassicListView,重写控件dispatchTouchEventonTouchEvent来观察事件的处理传递。

1. Chris Banes’ ptr

触屏分发:

  • dispatchTouchEvent 没有处理。

  • onInterceptTouchEvent

    • DOWN 不拦截。若可以拉动,更新拉动状态(mIsBeingDragged)为false

    • MOVE 正在更新、被拉动状态下都会拦截(返回true);

    • UP/CANCEL 不拦截,更新被拉动状态为false。

  • onTouchEvent (此阶段处理UI拖动逻辑)

    • DOWN 此时可以拉动刷新时消耗该event(返回true),否则返回false

    • MOVE 被拉动时消耗该event(返回true),否则返回false

    • UP/CANCEL 被拉动时,消耗该event(返回true),否则返回false

分析: 在onTouchEvent阶段处理了UI移动逻辑,且dispatch阶段不处理分发逻辑。配合此处intercept的处理,有两种情况:

  • 事件被下层view消耗了(如正在进行横滑),则无法进到自身的onTouchEvent阶段,就无法进行下拉、上拉的拖动;

  • 在自身进行上拉、下拉拖动时,事件将被截断,无法分发到下层View。

触屏事件示例: 

2. Liaohuqiu’s ptr

触屏分发:

  • dispatchTouchEvent (此阶段处理UI拖动逻辑)

    • DOWN 手动调用super.dispatchTouchEvent()将事件传递下去,但之后直接返回true,保证后续能够处理到move、up、cancel事件;

    • MOVE 被拉动时直接返回true,不向下传递事件;没有被拉动、无法触发拉动时不处理,传递给下层view。若设置了disableWhenHorizontalMove,则在没有被拉动时的横滑操作直接传递给下层view;

    • UP/CANCEL 如果被拖动了,则直接返回true,截断了此次事件,并手动向下层传递一个cancel事件;否则直接传递给下层view。

  • onInterceptTouchEvent 没有处理

  • onTouchEvent 没有处理

分析: dispatch阶段直接处理了分发逻辑与UI移动逻辑。只要它自身或它的子view处理了事件,dispatch永远会被触发,且它down时永远返回true。那么可以说:只要满足能够下拉的情况(对于ListView,默认为第一项完全可见)时,下拉刷新动作一定会被触发。一旦拉动,会在updatePos里面向下层view传递一个cancel事件,下层将会不再处理此次事件序列(原因可见View.dispatchTouchEvent() -> InputEventConsistencyVerifier.onTouchEvent())。 所以如果内部有冲突的滑动事件处理机制(典型就是嵌套横滑),那么只要一进行刷新拉动,内部的事件处理马上就会被截断。与Chris Banes的下拉刷新处理机制(内部消耗事件时外部无法拉动)不一样。 触屏事件示例: 

3.其他库

基本的做法就是如上两种,由于ListView一定会消耗事件,如果是嵌套视图的话必须重写onInterceptTouchEvent+onTouchEvent或者直接重写dispatchTouchEvent才能够保证正确接收并处理到触摸事件。两种方法的特点已经在上面分别列出,下面简单列出余下库的做法:

  • Johannilsson’s ptr 没有嵌套,直接处理onTouchEvent

  • Yalantis’s ptr 嵌套视图,处理类似Chris banes’ ptr;

  • race604’s ptr 嵌套视图,处理类似Chris banes’ ptr;

  • SwipeRefreshLayout 嵌套视图,处理类似Liaohuqiu’s ptr。

性能分析

通过捕捉如下图中的操作持续1秒钟的systrace进行性能分析:  > 注:由于开源库Header大多无法直接放自定义顶部视图,头部视图复杂程度不同,数据对比结果会有所偏差。

1. Chris Banes’s Ptr

滑动实现方式:触摸造成的下拉均是View.scrollTo()实现的;在松手之后,View.post(Runnable)触发Runnable执行回滚动画,在滑回原处之前不断post自己,并配合Interpolator执行scrollTo()进行滚动。 trace snapshot:  分析: 作为Github上星星数最多的Android下拉刷新控件,从性能上看(渲染时间构成)几乎没有什么明显的缺点。可惜的是作者已经不再维护,顶部视图的扩展性比较差,并且gradle中也无法使用。在本次demo这类层级比较简单的环境中,几乎都达到了60fps,可以与后面的trace对比。

2. liaohuqiu’s Ptr

滑动实现方式:触摸造成的下拉均是View.offsetTopAndBottom()实现的;在松手之后,触发Scroller.startScroll()计算回滚,使用View.post(Runnable)不停地监视Scroller的计算结果,从而实现视图变化(此处依然是View.offsetTopAndBottom()完成视图移动)。 trace snapshot: 分析: 这套开源库可以说是自定义功能最强的组件了,你可以实现PtrUIHandler并将其add到PtrFrameLayout完美地与下拉刷新事件适配。美中不足的就是在下拉状态变化的时候会有一阵measure时间。我查看了一下代码,发现是PtrClassicFrameLayout(作者实现的集成默认下拉视图的layout)的顶部视图出了问题:  看!都是wrap_content,那么当里面的内容变化的时候,是会触发View.requestLayout()的。不要小看这一个子视图的小操作,一个requestLayout()大概是这么一个流程:View.requestLayout()->ViewParent.requestLayout()->…->ViewRootImpl.requestLayout()->ViewRootImpl.doTraversal()=>MEASURE(ViewGroup)=>MEASURE(ChildView of ViewGroup) 在层级复杂的时候(大部分互联网产品由于复杂的产品需求嵌套都会比较多),它会层层向上调用,将measure时间放大至一个可观的层级。下拉刷新界面的卡顿由此而来。 我修改了一下,将其全部变为固定高度、宽度,之后的trace如下:  measure时间神奇的没掉了吧:)

3. johannilsson’s Ptr

滑动实现方式:初始时setSelection(1)隐藏顶部视图(使用这个下拉刷新控件注意将滚动栏隐藏,否则会露馅)。在拉下来超过header view的measure高度之前,均是ListView自有的滚动;在下拉超过header measure高度之后,对header使用View.setPadding()让header继续下移。 trace snapshot:  分析: 通过顶视图调用View.setPadding()来实现的滑动,在下拉距离超过header高度后,会造成不断的requestLayout()!这就解释了为什么图中UI线程的蓝色块时间(measure时间)很明显。当你在视图层级比较复杂的app中使用它时,下拉动作所造成的开销会非常明显,卡顿是必然结果。

4. Yalantis’s Ptr

滑动实现方式:通过View.topAndBottomOffset()移动视图,在松手之后启动一个Animation执行回滚动画,内容视图的移动也使用View.offsetTopAndBottom()实现。为了保证子内容视图的底部padding在移动之后与布局文件中的padding属性一致,它额外调用了View.setPadding()实时设置padding。 顶部动效实现方式:Drawabledraw()中,为Canvas中设置“太阳”偏移量及背景缩放。 trace snapshot:  分析: 此开源库动画效果非常柔和,且顶部视图全部是通过draw去更新,不会造成第三个开源库那样的大开销问题。可惜的是比较难以去自定义顶部视图,不好在线上产品中使用,不过这个开源库是一个好的练手与学习的对象。由于顶部动效实现开销不大,它的性能同样非常好。 它在松手后回滚时调用的View.setPadding()可能会造成measure开销比较大,于是我特地测了一下松手回滚的trace,一看确实measure时间非常可观:  确实它如果要保证展示内容视图的padding与布局文件中一致,是必须这么做的(调用View.setPadding()),因为通过View.offsetTopAndBottom()向下移动子视图时,子视图的内容整个移动下来,在视觉上会影响它设置好的底部padding。但是很有意思,它向下移动的时候没有这么设置,拉下来的时候底部padding就没了。回滚动画的时候才设了padding,就显得没那么必要了。我在demo中也进行了实践,确实是这样的:  我暂时也没想到什么方法可以更好地处理子视图padding问题。但实际上,由于这个库是一个嵌套视图,并且只会有一个内容视图显示出来,可以尝试放弃对子视图padding的处理。如果需要,可以使用父视图的padding来代替,这样是最完美的效果。子视图再怎么移动,也会被父视图已经设好的padding局限住。由此一来padding就不会被影响,同时提高了性能。不过这样一来牺牲了子视图padding的设置,在使用的时候可以根据需要各取所需。 我粗略的做了一点点改动,将它的setPadding()注释掉了。不过由于该库的一些其他实现逻辑,导致会有一些问题,此处仅看性能上的变化,改动后松手回滚trace,已经没有了measure时间:

5. race604’s Ptr

滑动实现方式:View.topAndBottomOffset() 顶部动效实现方式:

  • 飞机滑动 ObjectAnimator.

  • 山体移动、树木弯曲 通过移动距离计算山体偏移、树木轮廓,得出Path后进行draw.

trace snapshot:  分析:每次拖动都会重新计算背景”山体”与”树木”的Path,造成了draw时间过长。效果不错,也是一个好的学习对象,相比Yalantis的下拉刷新性能上就差一些了,它的draw中的计算量太多。使用起来疑似有bug:拖动到顶部,无法再往上拖动,并且会出现拖动异常。

6. SwipeRefreshLayout

滑动实现方式:内容固定,仅有顶部动效。 顶部动效实现方式:

  • 上下移动 View.bringToFront() + View.offsetTopAndBottom().

  • 动效 通过移动偏移量计算弧形曲线的角度、三角形的位置,使用drawArcdrawTriangle将他们画到Canvas上。

trace snapshot:  分析:官方的下拉刷新组件,动画十分美观简洁,API构造清晰明了。但是为什么每次的移动都会有一段明显的measure时间呢?我研究了一下代码,发现罪魁祸首是View.bringToFront(),它在每一次滑动的时候都会对顶部动效视图调用这个函数。仔细追朔这个函数源码,它会走到下面这段代码中: ViewGroup.java

1

2

3

4

5

6

7

8

9

10

11

public void bringChildToFront(View child) {

final int index = indexOfChild(child);

if (index >= 0) {

removeFromArray(index);

addInArray(child, mChildrenCount);

child.mParent = this;

requestLayout();

invalidate();

}

}

看,它是会触发View.requestLayout()的!这个函数会造成的后果我们在之前已经解释了,它会造成大量的UI线程开销。实际上我认为这个函数是没有调用的必要的,SwipeRefreshLayout明明在重写onLayout()的时候,header会被layout到child之上,没有必要再bringToFront()。 于是我copy了一份代码,将这一行注了(对应代码ptr-source-lib/src/main/java/com/android/support/SwipeRefreshLayout.java),再次编译,measure时间确实没掉了,对功能毫无影响,性能却有了很大优化:  这样一来就不会每一次拉动,都会触发measure。若有同学知道这个bringToFront()在其中有其他我未探测到的功效,请issue指点:)

总结

Repo 性能 拓展性 综合建议
Android-PullToRefresh ★★★★★ ★★★ 由于作者不再维护,无法在gradle中配置,顶部视图难以拓展,不建议放入工程中使用
android-Ultra-Pull-To-Refresh ★★★★★ ★★★★★ 如之前分析,PtrClassicFrameLayout性能有缺陷;建议使用PtrFrameLayout,性能较好。这套库自定义能力很强,建议使用。
android-pulltorefresh 实现方式上有缺陷,拓展性也很差。优点就是代码非常简单,只能作为反面例子。
Phoenix ★★★★ ★★ 效果非常好,性能不错,可惜比较难拓展顶部视图,为了适配布局padding造成了性能损失,有优化空间。可以作为学习与练手的对象。
FlyRefresh ★★★★ ★★ 效果很新颖,可惜的是顶部视图计算动效上开销太大,优化空间较少,可以作为学习与练手的对象。
SwipeRefreshLayout ★★★ ★★ 官方出品,更新有保障,但是如上分析,其实性能上还是有点缺陷的,拓展性比较差,不建议放入工程中使用。

本例中用到的代码可以到github上找到。

转载于:https://blog.51cto.com/4397014/2163347

Android下拉刷新开源库对比(转)相关推荐

  1. android下拉刷新和上拉加载的一个简单库

    介绍一个android下拉刷新.上拉加载的库: https://github.com/chrisbanes/Android-PullToRefresh 使用方式,创建好一个Android项目,导入li ...

  2. 【原创】窥视懒人的秘密---android下拉刷新开启手势的新纪元

    小飒的成长史原创作品:窥视懒人的秘密---android下拉刷新开启手势的新纪元转载请注明出处 **************************************************** ...

  3. Android下拉刷新和上拉加载更多

    Android下拉刷新和上拉加载更多 下拉刷新 通过android系统提供的组件:SwipeRefreshLayout 一.基本使用 1 xml中 添加 SwipeRefreshLayout 组件 该 ...

  4. Android 下拉刷新上拉载入 多种应用场景 超级大放送(上)

    转载请标明原文地址:http://blog.csdn.net/yalinfendou/article/details/47707017 关于Android下拉刷新上拉载入,网上的Demo太多太多了,这 ...

  5. Android下拉刷新的实现(一) - SwipeRefreshLayout篇

    Android下拉刷新的实现(一) - SwipeRefreshLayout篇

  6. Android下拉刷新

    早期下拉刷新主要是用XListView,这个类是继承ListView的,使用起来有局限性,并且在git上作者已经不维护了,之后有人自己写了一个PullToRefreshLayout,不用再继承自Lis ...

  7. Android 下拉刷新上拉加载 多种应用场景 超级大放送(上)

    转载请标明原文地址:http://blog.csdn.net/yalinfendou/article/details/47707017 关于Android下拉刷新上拉加载,网上的Demo太多太多了,这 ...

  8. Android下拉刷新-SwipeRefreshLayout

    现在市面上新闻类的App基本上都有下拉刷新,算是一个标配吧,网上关于下拉刷新的博客也有很多,实现方式可以使用开源的PullToRefresh,自定义ListView,或者可以直接使用LineLayOu ...

  9. Android下拉刷新效果实现

    本文主要包括以下内容 自定义实现pulltorefreshView 使用google官方SwipeRefreshLayout 下拉刷新大致原理 判断当前是否在最上面而且是向下滑的,如果是的话,则加载数 ...

最新文章

  1. cross--向量或矩阵的叉乘
  2. 手把手 docker 从零搭建 jenkins 服务器
  3. 相关疑惑解决,java线程虚假唤醒等等问题
  4. android 开发 矩形截屏插件,Android 上如何实现矩形区域截屏
  5. bootstrap table移动端_bootstrap介绍
  6. linux下网络编程设置非阻塞,UNIX网络编程 非阻塞connect的实现
  7. (转)如何在maven的pom.xml中添加本地jar包
  8. web root下放置图片_Apache HTTP存在提权漏洞,威胁共享Web主机安全性
  9. 01_ExtJS_HelloWorld
  10. 2016年回顾2017年目标之流水账
  11. 在线教育系统源码 知识付费系统源码 网络直播源码
  12. 网页教程:为网站的Web Robot 设计路标
  13. js 公共库 cdn 推荐
  14. 2000坐标系xy坐标几位_2000国家大地坐标系
  15. PHP存储微信昵称特殊符号过滤方法
  16. Tallest buildings/skyscrapers in the world
  17. 多少人,一边疯狂跳槽,一边疯狂后悔
  18. 3行代码,搞定AI自动抠图
  19. 自行更换内存条的操作规范
  20. openstack创建实例报Build of instance d401db9e-xxxx-97c5d7685592 aborted: Unknown auth type: None

热门文章

  1. 如何创建一张表mysql_如何创建一张规范的MySQL表
  2. ipynb和py文件一样吗_文件描述符了解一下
  3. Android LayoutAnimation使用及扩展
  4. Linux查询系统运行的时间
  5. 河南省高中毕业会考计算机考试,河南高中会考等级划分标准
  6. python定义私有变量的方法_Python面向对象程序设计之私有变量,私有方法原理与用法分析...
  7. application.properties/yml文件读取
  8. 计算机网络日,《计算机网络原理》教学日历[汇编].pdf
  9. cesium鼠标控制键盘_用 Python 控制 鼠标和键盘,花式操作
  10. 现代操作系统:第二章 进程和线程