Android下拉刷新开源库对比(转)
安卓下拉刷新開源庫對比
作者:desmond1121?
目前僅比對github上star數>1500的下拉刷新開源庫,在比較完成之后可能會加入其它有代表性的庫.
Repo
| 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 |
拓展性
| Android-PullToRefresh | 不支持,只能改代碼。 由于僅支持其中實現的LoadingLayout作為頂視圖,改代碼實現自定義工作量較大。 | 任意視圖,內置:GridViewListView,HorizontalScrollViewScrollView?,WebView |
| android-Ultra-Pull-To-Refresh | 任意視圖。 通過繼承PtrUIHandler并調用PtrFrameLayout.addPtrUIHandler()得到最大支持。 | 任意視圖 |
| android-pulltorefresh | 不支持,只能改代碼。 代碼僅一個ListView,耦合度太高,改動工作量較大。 | 無法擴展,自身為ListView |
| Phoenix | 不支持,此控件特點就是頂部視圖及動畫。 | 任意視圖,只顯示最后一個嵌套的子視圖。 |
| FlyRefresh | 不支持,此控件特點就是頂部視圖及動畫。 | 任意視圖 |
| SwipeRefreshLayout | 不支持,固定為Material風格 | 任意視圖 |
易用性
| Android-PullToRefresh | × | √ | × | 移動比固定1/2 |
| android-Ultra-Pull-To-Refresh | √ | × | √ | √ |
| android-pulltorefresh | × | × | × | 移動比固定1/1.7 |
| Phoenix | √ | × | × | 移動比固定1/2 |
| FlyRefresh | √ | × | × | × |
| SwipeRefreshLayout | √ | × | × | 移動比固定1/2 |
觸屏事件分發
本節分析控件對于觸屏事件的分發以及處理拖動的時機,具體拖動實現將在下一節中介紹。 此處添加進一個可以橫滑的組件,并將所有組件中的ListView替換為自己實現的ClassicListView,重寫控件dispatchTouchEvent,?onTouchEvent來觀察事件的處理傳遞。
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下拉刷新控件,從性能上看(渲染時間構成)幾乎沒有什么明顯的缺點??上У氖亲髡咭呀洸辉倬S護,頂部視圖的擴展性比較差,并且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。 頂部動效實現方式:Drawable的draw()中,為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().
動效?通過移動偏移量計算弧形曲線的角度、三角形的位置,使用drawArc,?drawTriangle將他們畫到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指點:)
總結
| Android-PullToRefresh | ★★★★★ | ★★★ | 由于作者不再維護,無法在gradle中配置,頂部視圖難以拓展,不建議放入工程中使用 |
| android-Ultra-Pull-To-Refresh | ★★★★★ | ★★★★★ | 如之前分析,PtrClassicFrameLayout性能有缺陷;建議使用PtrFrameLayout,性能較好。這套庫自定義能力很強,建議使用。 |
| android-pulltorefresh | ★ | ★ | 實現方式上有缺陷,拓展性也很差。優點就是代碼非常簡單,只能作為反面例子。 |
| Phoenix | ★★★★ | ★★ | 效果非常好,性能不錯,可惜比較難拓展頂部視圖,為了適配布局padding造成了性能損失,有優化空間??梢宰鳛閷W習與練手的對象。 |
| FlyRefresh | ★★★★ | ★★ | 效果很新穎,可惜的是頂部視圖計算動效上開銷太大,優化空間較少,可以作為學習與練手的對象。 |
| SwipeRefreshLayout | ★★★ | ★★ | 官方出品,更新有保障,但是如上分析,其實性能上還是有點缺陷的,拓展性比較差,不建議放入工程中使用。 |
本例中用到的代碼可以到github上找到。
轉載于:https://blog.51cto.com/4397014/2163347
總結
以上是生活随笔為你收集整理的Android下拉刷新开源库对比(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CTF-i春秋网鼎杯第一场misc部分w
- 下一篇: 对话框Flags的设置值