安卓下拉刷新開源庫對比

jopen 9年前發布 | 78K 次閱讀 Android

目前僅比對github上star數>1500的下拉刷新開源庫,在比較完成之后可能會加入其它有代表性的庫.

Repo

</tr> </thead>

</tr>

</tr>

</tr>

</tr>

</tr>

</tr> </tbody> </table>

拓展性

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

</tr> </thead>

</tr>

</tr>

</tr>

</tr>

</tr>

</tr> </tbody> </table>

易用性

Repo 自定義頂部視圖 支持的內容布局
Android-PullToRefresh 不支持,只能改代碼。
由于僅支持其中實現的LoadingLayout作為頂視圖,改代碼實現自定義工作量較大。
任意視圖,內置:GridView
ListView,HorizontalScrollView
ScrollView ,WebView
android-Ultra-Pull-To-Refresh 任意視圖。
通過繼承PtrUIHandler并調用
PtrFrameLayout.addPtrUIHandler()得到最大支持。
任意視圖
android-pulltorefresh 不支持,只能改代碼。
代碼僅一個ListView,耦合度太高,改動工作量較大。
無法擴展,自身為ListView
Phoenix 不支持,此控件特點就是頂部視圖及動畫。 任意視圖,只顯示最后一個嵌套的子視圖。
FlyRefresh 不支持,此控件特點就是頂部視圖及動畫。 任意視圖
SwipeRefreshLayout 不支持,固定為Material風格 任意視圖

</tr> </thead>

</tr>

</tr>

</tr>

</tr>

</tr>

</tr> </tbody> </table>

性能分析

通過捕捉如下圖中的操作持續1秒鐘的systrace進行性能分析:

trace_operation

注:由于開源庫Header大多無法直接放自定義頂部視圖,頭部視圖復雜程度不同,數據對比結果會有所偏差。

</blockquote>

1. Chris Banes's Ptr

滑動實現方式:觸摸造成的下拉均是View.scrollTo()實現的;在松手之后,View.post(Runnable)觸發Runnable執行回滾動畫,在滑回原處之前不斷post自己,并配合Interpolator執行scrollTo()進行滾動。

trace snapshot:

trace_chrisbanes

分析

作為Github上星星數最多的Android下拉刷新控件,從性能上看(渲染時間構成)幾乎沒有什么明顯的缺點。可惜的是作者已經不再維護,頂部視圖的擴展性比較差,并且gradle中也無法使用。在本次demo這類層級比較簡單的環境中,幾乎都達到了60fps,可以與后面的trace對比。

2. liaohuqiu's Ptr

滑動實現方式:觸摸造成的下拉均是View.offsetTopAndBottom()實現的;在松手之后,觸發Scroller.startScroll()計算回滾,使用View.post(Runnable)不停地監視Scroller的計算結果,從而實現視圖變化(此處依然是View.offsetTopAndBottom()完成視圖移動)。

trace snapshot:

trace_liaohuqiu

分析

這套開源庫可以說是自定義功能最強的組件了,你可以實現PtrUIHandler并將其add到PtrFrameLayout完美地與下拉刷新事件適配。美中不足的就是在下拉狀態變化的時候會有一陣measure時間。我查看了一下代碼,發現是PtrClassicFrameLayout的頂部視圖出了問題:

liaohuqiu_header

看!都是wrap_content,那么當里面的內容變化的時候,是會觸發View.requestLayout()的。不要小看這一個子視圖的小操作,一個requestLayout()大概是這么一個流程:View.requestLayout()->ViewParent.requestLayout()->...->ViewRootImpl.requestLayout()->ViewRootImpl.doTraversal()=>MEASURE(ViewGroup)=>MEASURE(ChildView of ViewGroup)

在層級復雜的時候(大部分互聯網產品由于復雜的產品需求嵌套都會比較多),它會層層向上調用,將measure時間放大至一個可觀的層級。下拉刷新界面的卡頓由此而來。

我修改了一下,將其全部變為固定高度、寬度,之后的trace如下:

trace_liaohuqiu_new

measure時間神奇的沒掉了吧:)

3. johannilsson's Ptr

滑動實現方式:初始時setSelection(1)隱藏頂部視圖(使用這個下拉刷新控件注意將滾動欄隱藏,否則會露餡)。在拉下來超過header view的measure高度之前,均是ListView自有的滾動;在下拉超過header measure高度之后,對header使用View.setPadding()讓header繼續下移。

trace snapshot:

trace_johan

分析

通過頂視圖調用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:

trace_yalantis

分析

此開源庫動畫效果非常柔和,且頂部視圖全部是通過draw去更新,不會造成第三個開源庫那樣的大開銷問題。可惜的是比較難以去自定義頂部視圖,不好在線上產品中使用,不過這個開源庫是一個好的練手與學習的對象。由于頂部動效實現開銷不大,它的性能同樣非常好。

它的在松手后回滾動畫時調用的View.setPadding()可能會造成measure開銷比較大,于是我特地測了一下松手回滾的trace,一看確實measure時間非常可觀:

trace_yalantis_scroll_back

確實它如果要保證展示內容視圖的padding與布局文件中一致,是必須這么做的(調用View.setPadding()),因為通過View.offsetTopAndBottom()向下移動視圖會影響底部的padding。但是很有意思,它向下移動的時候沒有這么設置,拉下來的時候底部padding就沒了。回滾動畫的時候才設了padding,就顯得沒那么必要了。我在demo中也進行了實踐,確實是這樣的:

yalantis_padding

實際上,由于這個庫是一個嵌套視圖,可以嘗試在父視圖的onLayout中進行處理由于位置變化帶來的padding影響,這么處理,只是在layout階段處理,不會造成measure的大量開銷。

我粗略的做了一點點改動,只在父視圖中處理padding而不是在子視圖里面做,就可以在上拉、下拉的時候保持padding不變,并且性能有了很大提高。不過好像邏輯就有點問題,還需要再做改動,已經跟作者提出issue。

改動后松手回滾trace,已經沒有了measure時間:

yalantis_back_trace_new

改動后的樣子,上下拉動時padding不會變動。不過每次回滾的時候頂部會多往里面滾一點,還需作者針對issue完善:

yalantis_padding_new

5. race604's Ptr

滑動實現方式:View.topAndBottomOffset()

頂部動效實現方式:

  • 飛機滑動 ObjectAnimator.
  • 山體移動、樹木彎曲 通過移動距離計算山體偏移、樹木輪廓,得出Path后進行draw.
  • </ul>

    trace snapshot:

    trace_flyrefresh

    分析:每次拖動都會重新計算背景"山體"與"樹木"的Path,造成了draw時間過長。效果不錯,也是一個好的學習對象,相比Yalantis的下拉刷新性能上就差一些了,它的draw中的計算量太多。使用起來疑似有bug:拖動到頂部,無法再往上拖動,并且會出現拖動異常。

    6. SwipeRefreshLayout

    滑動實現方式:內容固定,僅有頂部動效。

    頂部動效實現方式:

    • 上下移動 View.bringToFront() + View.offsetTopAndBottom().
    • 動效 通過移動偏移量計算弧形曲線的角度、三角形的位置,使用drawArc, drawTriangle將他們畫到Canvas上。
    • </ul>

      trace snapshot:

      trace_swipe

      分析:官方的下拉刷新組件,動畫十分美觀簡潔,API構造清晰明了。但是為什么每次的移動都會有一段明顯的measure時間呢?我研究了一下代碼,發現罪魁禍首是View.bringToFront(),它在每一次滑動的時候都會對頂部動效視圖調用這個函數。仔細追朔這個函數源碼,它會走到下面這段代碼中:

      ViewGroup.java

       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時間確實沒掉了,對功能毫無影響,性能卻有了很大優化:

      trace_swipe

      這樣一來就不會每一次拉動,都會觸發measure。若有同學知道這個bringToFront()在其中有其他我未探測到的功效,請issue指點:)

      總結

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

</tr> </thead>

</tr>

</tr>

</tr>

</tr>

</tr>

</tr> </tbody> </table>

附錄-知識點參考

  1. 為你的應用加速 - 安卓優化指南
  2. 使用Systrace分析UI性能
  3. Systrace-文檔
  4. </ol> 來自:https://github.com/desmond1121/Android-Ptr-Comparison

     本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
Repo 性能 拓展性 綜合建議
Android-PullToRefresh ★★★★★ ★★★ 由于作者不再維護,無法在gradle中配置,頂部視圖難以拓展,不建議放入工程中使用
android-Ultra-Pull-To-Refresh ★★★★★ ★★★★★ 如之前分析,PtrClassicFrameLayout性能有缺陷;建議使用PtrFrameLayout,性能較好。這套庫自定義能力很強,建議使用。
android-pulltorefresh 實現方式上有缺陷,拓展性也很差。優點就是代碼非常簡單,只能作為反面例子。
Phoenix ★★★★ ★★ 效果非常好,性能不錯,可惜比較難拓展頂部視圖,為了適配布局padding造成了性能損失,有優化空間。可以作為學習與練手的對象。
FlyRefresh ★★★★ ★★ 效果很新穎,可惜的是頂部視圖計算動效上開銷太大,優化空間較少,可以作為學習與練手的對象。
SwipeRefreshLayout ★★★ ★★ 官方出品,更新有保障,但是如上分析,其實性能上還是有點缺陷的,拓展性比較差,不建議放入工程中使用。