概述
RecyclerView有着極高的靈活性,能實現ListView、GridView的所有功能。在日常開發中,使用非常廣泛,如果使用不當將會影響到應用的整體性能,所以有必要了解一下如何更高效的使用。
數據處理與視圖綁定分離
RecyclerView的
bindViewHolder
方法是在UI線程進行的,如果在該方法進行耗時操作,將會影響滑動的流暢性。
優化前:
class Task { Date dateDue; String title; String description; // getters and setters here } class MyRecyclerView.Adapter extends RecyclerView.Adapter { static final TODAYS_DATE = new Date(); static final DATE_FORMAT = new SimpleDateFormat("MM dd, yyyy"); public onBindViewHolder(Task.ViewHolder tvh, int position) { Task task = getItem(position); if (TODAYS_DATE.compareTo(task.dateDue) > 0) { tvh.backgroundView.setColor(Color.GREEN); } else { tvh.backgroundView.setColor(Color.RED); } String dueDateFormatted = DATE_FORMAT.format(task.getDateDue()); tvh.dateTextView.setDate(dueDateFormatted); } }
上面的 onBindViewHolder
方法中進行了日期的比較和日期的格式化,這個是很耗時的,在 onBindViewHolder
方法中,應該只是將數據 set
到視圖中,而不應進行業務的處理。
優化後:
public class TaskViewModel { int overdueColor; String dateDue; } public onBindViewHolder(Task.ViewHolder tvh, int position) { TaskViewModel taskViewModel = getItem(position); tvh.backgroundView.setColor(taskViewModel.getOverdueColor()); tvh.dateTextView.setDate(taskViewModel.getDateDue()); }
數據優化
- 分頁加載遠端數據,對拉取的遠端數據進行緩存,提高二次加載速度;
- 對於新增或刪除數據通過
DiffUtil
,來進行局部數據刷新,而不是一味的全局刷新數據。
DiffUtil
是support包下新增的一個工具類,用來判斷新數據和舊數據的差別,從而進行局部刷新。
DiffUtil的使用,在原來調用 mAdapter.notifyDataSetChanged()
的地方:
// mAdapter.notifyDataSetChanged() DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(oldDatas, newDatas), true); diffResult.dispatchUpdatesTo(mAdapter);
DiffUtil最終是調用Adapter的下面幾個方法來進行局部刷新:
mAdapter.notifyItemRangeInserted(position, count); mAdapter.notifyItemRangeRemoved(position, count); mAdapter.notifyItemMoved(fromPosition, toPosition); mAdapter.notifyItemRangeChanged(position, count, payload);
佈局優化
減少過度繪製
減少佈局層級,可以考慮使用自定義View來減少層級,或者更合理的設置佈局來減少層級。
Note: 目前不推薦在RecyclerView中使用 ConstraintLayout
,在ConstraintLayout1.1.2版中,性能還是表現不佳,後續的版本可能這個問題就解決了,需要持續關注。
減少xml文件inflate時間
xml文件包括:layout、drawable的xml,xml文件inflate出ItemView是通過耗時的IO操作。可以使用代碼去生成佈局,即
newView()
的方式。這種方式是比較麻煩,但是在佈局太過複雜,或對性能要求比較高的時候可以使用。
減少View對象的創建
一個稍微複雜的 Item 會包含大量的 View,而大量的 View 的創建也會消耗大量時間,所以要儘可能簡化 ItemView;設計 ItemType 時,對多 ViewType 能夠共用的部分儘量設計成自定義 View,減少 View 的構造和嵌套。
設置高度固定
如果item高度是固定的話,可以使用
RecyclerView.setHasFixedSize(true);
來避免requestLayout浪費資源。
共用RecycledViewPool
在嵌套RecyclerView中,如果子RecyclerView具有相同的adapter,那麼可以設置
RecyclerView.setRecycledViewPool(pool)
來共用一個RecycledViewPool。
Note: 如果LayoutManager是LinearLayoutManager或其子類,需要手動開啓這個特性: layout.setRecycleChildrenOnDetach(true)
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); ... @Override public OuterAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView innerLLM = new RecyclerView(inflater.getContext()); LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL); innerLLM.setRecycleChildrenOnDetach(true); innerRv.setLayoutManager(innerLLM); innerRv.setRecycledViewPool(mSharedPool); return new OuterAdapter.ViewHolder(innerRv); }
RecyclerView數據預取
RecyclerView25.1.0及以上版本增加了
Prefetch
功能。 用於嵌套RecyclerView獲取最佳性能。 詳細分析:https://juejin.im/entry/58a3f4f62f301e0069908d8f
Note: 只適合橫向嵌套
// 在嵌套內部的LayoutManager中調用LinearLayoutManger的設置方法 // num的取值:如果列表剛剛展示4個半item,則設置爲5 innerLLM.setInitialItemsPrefetchCount(num);
加大RecyclerView的緩存
用空間換時間,來提高滾動的流暢性。
recyclerView.setItemViewCacheSize(20); recyclerView.setDrawingCacheEnabled(true); recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
增加RecyclerView預留的額外空間
額外空間:顯示範圍之外,應該額外緩存的空間
new LinearLayoutManager(this) { @Override protected int getExtraLayoutSpace(RecyclerView.State state) { return size; } };
減少ItemView監聽器的創建
對ItemView設置監聽器,不要對每個item都創建一個監聽器,而應該共用一個XxListener,然後根據
ID
來進行不同的操作,優化了對象的頻繁創建帶來的資源消耗。
優化滑動操作
設置
RecyclerView.addOnScrollListener();
來在滑動過程中停止加載的操作。
處理刷新閃爍
調用notifyDataSetChange時,適配器不知道整個數據集中的那些內容以及存在,再重新匹配ViewHolder時會花生閃爍。 設置adapter.setHasStableIds(true),並重寫getItemId()來給每個Item一個唯一的ID
回收資源
通過重寫
RecyclerView.onViewRecycled(holder)
來回收資源。
參考鏈接
- https://blankj.com/2018/09/29/optimize-recycler-view/
- https://blog.csdn.net/a8688555/article/details/79634295
- https://stackoverflow.com/questions/27188536/recyclerview-scrolling-performance
- https://stackoverflow.com/questions/27993627/optimizing-recyclerview-listview
- http://www.cnblogs.com/ldq2016/p/9039979.html
- https://juejin.im/entry/58a3f4f62f301e0069908d8f
- https://blog.csdn.net/qq_25867141/article/details/52769332
- https://www.cnblogs.com/dasusu/p/9159904.html
- https://www.cnblogs.com/dasusu/p/9255335.html