RecyclerView 體驗優化及入坑總結
本文所講RecyclerView 是來自support 庫 26 版本,本文主要來源於自身開發及組內同事遇到問題的經驗總結,作為知識沉澱記錄一下,以備日後檢視。
本文主要講解以下幾部分:
(1)RecyclerView 滑動體驗篇
1)橫向ViewPager 與內嵌橫向RecyclerView 之間的滑動衝突;
2)縱向RecycleView/ListView 與 橫向RecycleView 之間的滑動衝突;
3)橫向RecyclerView ItemView 滑動不停留在中間態;
4)記錄、恢復RecyclerView 滾動偏移位置;
(2)RecyclerView 入坑篇
1)RecyclerView 導致的記憶體洩漏(support 26 + 7.0以下機型);
2)RecyclerView呼叫notifyDataSetChanged 會閃爍;
3)RecycleView /ListView 設定itemView 為View.GONE 效果等同於View.Invisible;
(3)RecyclerView 效能提升篇
一、RecyclerView 滑動體驗篇
(1)ViewPager 與 橫向RecyclerView 之間的滑動衝突
目前,企鵝FM專案中,很多頁面使用ViewPager+ TabLayout (如首頁、詳情頁、搜尋結果頁等),而對應頁面很多時候會巢狀一個橫向RecycleView,用來展現更多的資訊,如下,在RecycleView中滑動到最後一個元素時,會同時帶動ViewPager滑動,這種體驗極差。
原因分析:
作為子View 的RecyclerView在滑到最後一個或第一個ItemView到導致ViewPager滑動,這一定是ViewPager在此刻對滑動事件進行了攔截,解決的最簡單辦法就是不讓ViewPager攔截橫向RecyclerView的滑動事件(即 ViewPager::onInterceptTouchEvent方法返回false),ViewPager::onInterceptTouchEvent中的Move 事件如下:
目前,有以下兩種方式使ViewPager 不去攔截橫向RecyclerView 滑動事件:
1)在RecyclerView 對應滑動事件分發中呼叫
getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewPager對其MOVE或者UP事件進行攔截,但是考慮的因素比較多,而且效果不是太好,故放棄這種方式。
2)修改某些方法,進入到上圖if判斷中
在滑動橫向RecyclerView 到兩端時,dx != 0 && !isGutterDrag(mLastMotionX, dx) 肯定滿足條件,那說明canScroll() (用來判斷一個View以及它的子View是否可以滑動)一定返回了false, 複寫canScroll()方法,打log,發現返回果然為false,驗證了自己的判斷。
解決辦法:複寫canScroll,當View 是橫向RecyclerView(LinearLayoutManager 包含GridLayoutManager)時,直接返回true即可解決問題,解決程式碼如下:
類似的衝突還有ViewPager 和HorizontalScrollView 等等,解決方式與上面類似。
(2)縱向RecyclerView/ListView 與 橫向RecyclerView 之間的滑動衝突
在有些時候因為產品需求,需要在縱向的RecyclerView/ListView內巢狀一個橫向的RecyclerView,當這個橫向RecyclerView的item 比高度較大的時候(企鵝FM書城排行榜模組),在橫向滑動時,容易導致整體向上滑,體驗效果較差,如下圖所示(網路盜圖) :
造成上述現象的原因是:外層縱向滑動的RecyclerView對 橫向滑動的RecyclerView 的滑動事件進行了攔截,如下圖2 所示,canScrollVertically 此刻為true,因此這裡僅僅只判斷了Math.abs(dy)>mTouchSlop(可以認為是一個滑動閥值,是一個定值8dp) ,並未判斷方向或角度,從而決定是否攔截。
解決辦法 :既然RecyclerView::onInterceptTouchEvent 內部沒有判斷滑動角度或方向,那我們就人為去判斷,在上面判讀的基礎上繼續判斷 Math.abs(dy) 和Math.abs(dx) 的大小,從而決定是否攔截:具體分析細節可參照 , 修復垂直滑動RecyclerView巢狀水平滑動RecyclerView水平滑動不靈敏問題
使用上述方法,可以很快解決上述滑動體驗問題,那是不是隻有上述一種解決方式了,答案是否定的,作為一名Android 開發者我們知道,除了上述方式攔截滑動事件外,我們還可以通過getParent().requestDisallowInterceptTouchEvent(true); 讓父RecyclerView不去攔截橫向滑動,如下是RecyclerView::onTouchEvent() ,內部已經實現了requestDisallowInterceptTouchEvent(true) 。
我們需要考慮的是,當我們橫向上或橫向下滑動時,需要 進入上圖中1的判斷 ,2的判斷還未滿足,此時內部橫向RecyclerView 會攔截內部itemView的滑動事件,進而執行自己的onTouchEvent事件,從而呼叫requestDisallowInterceptTouchEvent(true) ,讓外層RecyclerView不去攔截內部RecyclerView的橫向滑動事件,至此需要解決如何保證先進入1判斷而不進入2判斷。
解決辦法:通過調整TouchSlop值的大小
在開始我們已介紹RecyclerView 的預設TouchSlop 值是8dp,如果要先保證進入1判斷條件,必須調大TouchSlop值(反射獲取),經過調整TouchSlop (按倍數調整比較簡單,可以先知道一個大致範圍)驗證,當TouchSlop擴大1倍時就能滿足條件。
總結:上述兩種方式各有優缺點,方法1,對原生RecyclerView 侵入性較強(特別是對RecyclerView 進行多層封裝的情況下,影響比較大),優點是TouchSlop 值保持與系統一致,不會帶來其他未知問題;方法 2 ,修改方式簡單,入侵性小,缺點,需要調整TouchSlop 值,可能還會帶來其他問題。
(3)橫向RecyclerView ItemView 滑動不停留在中間態
如下圖所示,正在滑動的模組是書城——排行榜模組,排行榜模組主要由橫向RecyclerView 構成,內部包含兩個榜單形式,列舉前top3的內容,在(2)的基礎上解決了縱向RecyclerView 巢狀橫向RecyclerView 滑動問題外,還有有個小問題那就是,RecyclerView ItemView 滑動多少就停在那裡,這種效果不是我們想要的,我們想要的是滑到左邊就顯示第一個榜單,滑到右邊就顯示第二個榜單。
那有沒有好的辦法做到這一點了,官方考慮到這一點,針對RecyclerView 滑動情況,專門提供了SnapHelper類(PagerSnapHelper 和LinearSnapHelper ,詳細介紹介紹可參看Android中使用RecyclerView + SnapHelper實現類似ViewPager效果), 使用其他相當簡單,針對上述問題解決方式如下:
(4)記錄、恢復RecyclerView 滾動偏移位置
熟悉RecyclerView 快取的同學應該知道(後面在也會介紹RecyclerView快取機制),當RecyclerView中的itemView 滑出螢幕後會快取在mCacheView 中(預設快取最大數是2),因此當滑出螢幕超過2後,再滑回來,原來的位置資訊都會被重置,對於一般的RecyclerView 沒有什麼影響,但是如果內嵌了一個橫向RecyclerView (如下圖中分類模組位置) ,起初”懸疑推理“ 在一排第一個位置,向左滑動到其他位置後,再縱向滑動外層RecyclerView ,發現分類模組第一個又變成了”懸疑推理“ ,這個是產品不能接受的。
那如何修正上述問題了,RecyclerView 佈局 及位置相關資訊都是由對應LayoutManager決定,因此檢視對應LayoutManager::onSaveInstanceState() 如下所示,內部確實記錄了position及offset 值。
解決辦法步驟:
(1)在Adapter::onViewRecycled 中儲存對應LayoutManager的onSaveInstanceState ,同時記錄儲存下來
(2)在setData()資料給Adapter 時,恢復對應LayoutManager 之前儲存在資料資訊
(3)儲存記錄RecyclerView 後的效果
二、RecyclerView 入坑篇
(1)RecyclerView 導致的記憶體洩漏(support 26 + 7.0以下機型)
在進行4.0 版本迭代時,發現在之前的廣播聚合頁存在RecyclerView導致的記憶體洩漏,下圖為記憶體洩漏的引用鏈,引用物件可以追到GapWorker。這裡的RecyclerView是一個橫向的RecyclerView ,作為廣播聚合頁(ListView)的HeaderView。
由於廣播頁面是比較老的頁面,最近幾個版本也未發現此類洩漏,細細想一下,可能與RecyclerView 版本有關(4.0版本直接將support 庫由23.1升級到26.1版本),剛好這幾個版本,support 庫 修復了修復很多RecyclerView 的bug 及新增了許多新功能。通過AndroidXRef 查詢知(查詢結果如下),GapWorker 果然是在support 26 新增的。
檢視GapWorker ,裡面sGapWorker 是一個ThreadLocal 帶GapWorker 的物件,同時維持了一個RecyclerView 的List物件(通過add 和remove 方法進行)。
而GapWorker的add 和remove 方法分別在RecyclerView::onAttachedToWindow 和RecyclerView::onDetachedFromWindow 中呼叫,如下圖所示:
根據上面的引用鏈知,RecyclerView::onDetachedFromWindow 方法 沒有被主動呼叫,斷點驗證,在退出廣播頁面的時候也沒有呼叫(導致洩漏),按理說在滑動離屏的時候就應該呼叫的,難道和RecylerView 做為ListView 的HeaderView 有關,順著這條思路發現果然和上述使用方式有關。
之前組內同事chunyu遇到過:ListView 巢狀GridView時,GridView資料錯亂問題(7.0及其以上有問題),裡面剛好說明了7.0及其以上版本,官方修正了RecylerView 做為ListView 的HeaderView 情況,滑出螢幕,不呼叫onDetachedFromWindow()的原因,具體如下:
從分析中,可以獲取到兩個重要的資訊:1)GapWorker 是在support 26 以上才有的,且SDK_INT>=21,才會進行對應add 和remove 操作 ;2)在SDK_INT< 24(7.0) 時,不會主動呼叫View::dispatchDetachedFromWindow()。
因此,上述問題的解決辦是:在對應Fragment 的onDetach() 或 其他場景主要去呼叫上圖中的ViewGroup::removeDetachedView() (這裡需要使用反射),具體如下:
(2)RecyclerView呼叫notifyDataSetChanged 會閃爍
詳見我的另一篇文章:RecyclerView notifyDataSetChanged 導致圖片閃爍的真凶
(3)RecycleView /ListView 設定itemView 為View.GONE 效果等同於View.Invisible
解決辦法:將itemView 的寬高設定成 0 ,重新設定一下LayoutParams
三、RecyclerView 效能提升篇
說是RecyclerView效能提升篇有點誇大 ,這裡主要講講RecyclerView 使用小技巧
(1)setHasFixedSize(true)優化思想
(2)DiffUtil ()
(3)......
限於篇幅內容有點多,後續再補充..... ,有分析不對的地方歡迎指出 ,謝謝 ^_^!
相關引用資料
(1)修復垂直滑動RecyclerView巢狀水平滑動RecyclerView水平滑動不靈敏問題
相關文章
- JVM 優化經驗總結JVM優化
- WebView深度學習(二)之全面總結WebView遇到的坑及優化WebView深度學習優化
- RecyclerView總結View
- SQL 優化經驗總結34條SQL優化
- SQL優化經驗總結34條SQL優化
- MySQL 索引及查詢優化總結MySql索引優化
- RecyclerView使用體驗View
- Java集合類操作優化經驗總結Java優化
- SQL優化經驗總結34條(一)SQL優化
- SQL優化經驗總結34條(二)SQL優化
- 三年磨一劍,高德地圖體驗優化總結地圖優化
- PerfDog效能狗上手體驗及總結分析
- 遊戲開發效能優化經驗總結遊戲開發優化
- Flutter1.9升級體驗總結(幫大家填坑了)Flutter
- webpack優化總結Web優化
- APP優化總結APP優化
- 效能優化總結優化
- 元件庫webpack構建速度優化經驗總結元件Web優化
- 經驗總結:登入和優化IBM DB2的執行速度優化IBMDB2
- 【SQL優化】UNION替換OR效率測試及總結SQL優化
- APK體積優化的一些總結APK優化
- React 效能優化總結React優化
- SYBASE優化總結(zt)優化
- canvas效能優化總結Canvas優化
- React效能優化總結React優化
- 前端效能優化總結前端優化
- 斜率優化DP總結優化
- iOS 效能優化總結iOS優化
- JDBC優化策略總結JDBC優化
- Oracle SQL優化總結OracleSQL優化
- Oracle SQL優化 總結OracleSQL優化
- Redis資料匯入工具優化過程總結Redis優化
- RecyclerView使用封裝與優化View封裝優化
- Taro實踐 - 深度開發實踐體驗及總結
- CUDA總體優化策略優化
- ELK重難點總結和整體優化配置優化
- 【實驗】【索引壓縮】索引壓縮演示及優缺點總結索引
- 深入解析:Android卡頓檢測及優化專案實戰經驗總結,任君白嫖Android優化