摘要:缺點自動裝箱的存在意味著每一次插入都會有額外的對象創建。對象本身是一層額外需要被創建以及被垃圾回收的對象。相較于我們舍棄了和類型的放棄了并依賴于二分法查找。
目錄介紹
25.0.0.0 請說一下RecyclerView?adapter的作用是什么,幾個方法是做什么用的?如何理解adapter訂閱者模式?
25.0.0.1 ViewHolder的作用是什么?如何理解ViewHolder的復用?什么時候停止調用onCreateViewHolder?
25.0.0.2 ViewHolder封裝如何對findViewById優化?ViewHolder中為何使用SparseArray替代HashMap存儲viewId?
25.0.0.3 LayoutManager作用是什么?LayoutManager樣式有哪些?setLayoutManager源碼里做了什么?
25.0.0.4 SnapHelper主要是做什么用的?SnapHelper是怎么實現支持RecyclerView的對齊方式?
25.0.0.5 SpanSizeLookup的作用是干什么的?SpanSizeLookup如何使用?SpanSizeLookup實現原理如何理解?
25.0.0.6 ItemDecoration的用途是什么?自定義ItemDecoration有哪些重寫方法?分析一下addItemDecoration()源碼?
25.0.0.7 上拉加載更多的功能是如何做的?添加滾動監聽事件需要注意什么問題?網格布局上拉加載如何優化?
25.0.0.8 RecyclerView繪制原理如何理解?性能優化本質是什么?RecyclerView繪制原理過程大概是怎樣的?
25.0.0.9 RecyclerView的Recyler是如何實現ViewHolder的緩存?如何理解recyclerView三級緩存是如何實現的?
25.0.1.0 屏幕滑動(狀態是item狀態可見,不可見,即將可見變化)時三級緩存是如何理解的?adapter中的幾個方法是如何變化?
25.0.1.1 SnapHelper有哪些重要的方法,其作用就是是什么?LinearSnapHelper中是如何實現滾動停止的?
25.0.1.2 LinearSnapHelper代碼中calculateDistanceToFinalSnap作用是什么?那么out[0]和out[1]分別指什么?
25.0.1.3 如何實現可以設置分割線的顏色,寬度,以及到左右兩邊的寬度間距的自定義分割線,說一下思路?
25.0.1.4 如何實現復雜type首頁需求?如果不封裝會出現什么問題和弊端?如何提高代碼的簡便性和高效性?
25.0.1.5 關于item條目點擊事件在onCreateViewHolder中寫和在onBindViewHolder中寫有何區別?如何優化?
25.0.1.6 RecyclerView滑動卡頓原因有哪些?如何解決嵌套布局滑動沖突?如何解決RecyclerView實現畫廊卡頓?
25.0.1.7 RecyclerView常見的優化有哪些?實際開發中都是怎么做的,優化前后對比性能上有何提升?
25.0.1.8 如何解決RecyclerView嵌套RecyclerView條目自動上滾的Bug?如何解決ScrollView嵌套RecyclerView滑動沖突?
25.0.1.9 如何處理ViewPager嵌套水平RecyclerView橫向滑動到底后不滑動ViewPager?如何解決RecyclerView使用Glide加載圖片導致圖片錯亂問題?
00.RecyclerView復雜封裝庫
幾乎融合了該系列博客中絕大部分的知識點,歡迎一遍看博客一遍實踐,一步步從簡單實現功能強大的庫
01.RecyclerView
RecycleView的結構,RecyclerView簡單用法介紹
02.Adapter
RecyclerView.Adapter扮演的角色,一般常用的重寫方法說明,數據變更通知之觀察者模式,查看.notifyChanged();源碼
03.ViewHolder
ViewHolder的作用,如何理解對于ViewHolder對象的數量“夠用”之后就停止調用onCreateViewHolder方法,ViewHolder簡單封裝
04.LayoutManager
LayoutManager作用是什么?setLayoutManager源碼分析
05.SnapHelper
SnapHelper作用,什么是Fling操作 ,SnapHelper類重要的方法,
06.ItemTouchHelper
07.SpanSizeLookup
SpanSizeLookup如何使用,同時包含列表,2列的網格,3列的網格如何優雅實現?
08.ItemDecoration
ItemDecoration的用途,addItemDecoration()源碼分析
09.RecycledViewPool
RecyclerViewPool用于多個RecyclerView之間共享View。
10.ItemAnimator
官方有一個默認Item動畫類DafaultItemAnimator,其中DefaultItemAnimator繼承了SimpleItemAnimator,在繼承了RecyclerView.ItemAnimator,它是如何實現動畫呢?
11.RecyclerView上拉加載
添加recyclerView的滑動事件,上拉加載分頁數據,設置上拉加載的底部footer布局,顯示和隱藏footer布局
12.RecyclerView緩存原理
RecyclerView做性能優化要說復雜也復雜,比如說布局優化,緩存,預加載,復用池,刷新數據等等
13.SnapHelper源碼分析
SnapHelper旨在支持RecyclerView的對齊方式,也就是通過計算對齊RecyclerView中TargetView 的指定點或者容器中的任何像素點。
16.自定義SnapHelper
自定義SnapHelper
18.ItemTouchHelper 實現交互動畫
需要自定義類實現ItemTouchHelper.Callback類
19.自定義ItemDecoration分割線
需要自定義類實現RecyclerView.ItemDecoration類,并選擇重寫合適方法
21.RecyclerView優化處理
RecyclerView滑動卡頓原因有哪些?如何解決嵌套布局滑動沖突?如何解決RecyclerView實現畫廊卡頓?
22.RecyclerView問題匯總
getLayoutPosition()和getAdapterPosition()的區別
23.RecyclerView滑動沖突
01.如何判斷RecyclerView控件滑動到頂部和底部
02.RecyclerView嵌套RecyclerView 條目自動上滾的Bug
03.ScrollView嵌套RecyclerView滑動沖突
04.ViewPager嵌套水平RecyclerView橫向滑動到底后不滑動ViewPager
05.RecyclerView嵌套RecyclerView的滑動沖突問題
06.RecyclerView使用Glide加載圖片導致圖片錯亂問題解決
24.ScrollView嵌套RecyclerView問題
要實現在NestedScrollView中嵌入一個或多個RecyclerView,會出現滑動沖突,焦點搶占,顯示不全等。如何處理?
25.RecyclerView封裝庫和綜合案例
自定義支持上拉加載更多【加載中,加載失敗[比如沒有更多數據],加載異常[無網絡],加載成功等多種狀態】,下拉刷新,可以實現復雜的狀態頁面,支持自由切換狀態【加載中,加載成功,加載失敗,沒網絡等狀態】的控件,拓展功能[支持長按拖拽,側滑刪除]可以選擇性添加。具體使用方法,可以直接參考demo案例。
25.0.0.0 請說一下RecyclerView?adapter的作用是什么,幾個方法是做什么用的?如何理解adapter訂閱者模式?
關于RecyclerView,大家都已經很熟悉了,用途十分廣泛,大概結構如下所示
RecyclerView.Adapter - 處理數據集合并負責綁定視圖
ViewHolder - 持有所有的用于綁定數據或者需要操作的View
LayoutManager - 負責擺放視圖等相關操作
ItemDecoration - 負責繪制Item附近的分割線
ItemAnimator - 為Item的一般操作添加動畫效果,如,增刪條目等
如圖所示,直觀展示結構
adapter的作用是什么
RecyclerView.Adapter扮演的角色
一是,根據不同ViewType創建與之相應的的Item-Layout
二是,訪問數據集合并將數據綁定到正確的View上
幾個方法是做什么用的
一般常用的重寫方法有以下這么幾個:博客
public VH onCreateViewHolder(ViewGroup parent, int viewType) 創建Item視圖,并返回相應的ViewHolder public void onBindViewHolder(VH holder, int position) 綁定數據到正確的Item視圖上。 public int getItemCount() 返回該Adapter所持有的Itme數量 public int getItemViewType(int position) 用來獲取當前項Item(position參數)是哪種類型的布局
如何理解adapter訂閱者模式
當時據集合發生改變時,我們通過調用.notifyDataSetChanged(),來刷新列表,因為這樣做會觸發列表的重繪。
注意這里需要理解什么是訂閱者模式……
a.首先看.notifyDataSetChanged()源碼
public final void notifyDataSetChanged() { mObservable.notifyChanged(); }
b.接著查看.notifyChanged();源碼
被觀察者AdapterDataObservable,內部持有觀察者AdapterDataObserver集合
static class AdapterDataObservable extends Observable{ public boolean hasObservers() { return !mObservers.isEmpty(); } public void notifyChanged() { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } public void notifyItemRangeChanged(int positionStart, int itemCount) { notifyItemRangeChanged(positionStart, itemCount, null); } public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); } } public void notifyItemRangeInserted(int positionStart, int itemCount) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeInserted(positionStart, itemCount); } } }
觀察者AdapterDataObserver,具體實現為RecyclerViewDataObserver,當數據源發生變更時,及時響應界面變化
public static abstract class AdapterDataObserver { public void onChanged() { // Do nothing } public void onItemRangeChanged(int positionStart, int itemCount) { // do nothing } public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { onItemRangeChanged(positionStart, itemCount); } }
c.接著查看setAdapter()源碼中的setAdapterInternal(adapter, false, true)方法
setAdapter源碼博客
public void setAdapter(Adapter adapter) { // bail out if layout is frozen setLayoutFrozen(false); setAdapterInternal(adapter, false, true); requestLayout(); }
setAdapterInternal(adapter, false, true)源碼
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mObserver); mAdapter.onDetachedFromRecyclerView(this); } if (!compatibleWithPrevious || removeAndRecycleViews) { removeAndRecycleViews(); } mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; mAdapter = adapter; if (adapter != null) { //注冊一個觀察者RecyclerViewDataObserver adapter.registerAdapterDataObserver(mObserver); adapter.onAttachedToRecyclerView(this); } if (mLayout != null) { mLayout.onAdapterChanged(oldAdapter, mAdapter); } mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); mState.mStructureChanged = true; markKnownViewsInvalid(); }
d.notify……方法被調用,刷新數據
當數據變更時,調用notify**方法時,Adapter內部的被觀察者會遍歷通知已經注冊的觀察者的對應方法,這時界面就會響應變更。博客
25.0.0.1 ViewHolder的作用是什么?如何理解ViewHolder的復用?什么時候停止調用onCreateViewHolder?
ViewHolder作用大概有這些:
adapter應當擁有ViewHolder的子類,并且ViewHolder內部應當存儲一些子view,避免時間代價很大的findViewById操作
其RecyclerView內部定義的ViewHolder類包含很多復雜的屬性,內部使用場景也有很多,而我們經常使用的也就是onCreateViewHolder()方法和onBindViewHolder()方法,onCreateViewHolder()方法在RecyclerView需要一個新類型。item的ViewHolder時調用來創建一個ViewHolder,而onBindViewHolder()方法則當RecyclerView需要在特定位置的item展示數據時調用。博客
如何理解ViewHolder的復用
在復寫RecyclerView.Adapter的時候,需要我們復寫兩個方法:博客
onCreateViewHolder
onBindViewHolder
這兩個方法從字面上看就是創建ViewHolder和綁定ViewHolder的意思
復用機制是怎樣的?
模擬場景:只有一種ViewType,上下滑動的時候需要的ViewHolder種類是只有一種,但是需要的ViewHolder對象數量并不止一個。所以在后面創建了9個ViewHolder之后,需要的數量夠了,無論怎么滑動,都只需要復用以前創建的對象就行了。那么逗比程序員們思考一下,為什么會出現這種情況呢
看到了下面log之后,第一反應是在這個ViewHolder對象的數量“夠用”之后就停止調用onCreateViewHolder方法,但是onBindViewHolder方法每次都會調用的
查看一下createViewHolder源代碼
發現這里并沒有限制
public final VH createViewHolder(ViewGroup parent, int viewType) { TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; TraceCompat.endSection(); return holder; }
對于ViewHolder對象的數量“夠用”之后就停止調用onCreateViewHolder方法,可以查看
獲取為給定位置初始化的視圖。博客
此方法應由{@link LayoutManager}實現使用,以獲取視圖來表示來自{@LinkAdapter}的數據。
如果共享池可用于正確的視圖類型,則回收程序可以重用共享池中的廢視圖或分離視圖。如果適配器沒有指示給定位置上的數據已更改,則回收程序將嘗試發回一個以前為該數據初始化的報廢視圖,而不進行重新綁定。
public View getViewForPosition(int position) { return getViewForPosition(position, false); } View getViewForPosition(int position, boolean dryRun) { return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; } @Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) { //代碼省略了,有需要的小伙伴可以自己看看,這里面邏輯實在太復雜呢 }
ViewHolder封裝如何對findViewById優化?
class MyViewHolder extends RecyclerView.ViewHolder { private SparseArrayviewSparseArray; private TextView tvTitle; MyViewHolder(final View itemView) { super(itemView); if(viewSparseArray==null){ viewSparseArray = new SparseArray<>(); } tvTitle = (TextView) viewSparseArray.get(R.id.tv_title); if (tvTitle == null) { tvTitle = itemView.findViewById(R.id.tv_title); viewSparseArray.put(R.id.tv_title, tvTitle); } } }
為何使用SparseArray替代HashMap存儲viewId
HashMap
基本上就是一個 HashMap.Entry 的數組(Entry 是 HashMap 的一個內部類)。更準確來說,Entry 類中包含以下字段:
一個非基本數據類型的 key
一個非基本數據類型的 value
保存對象的哈希值
指向下一個 Entry 的指針
當有鍵值對插入時,HashMap 會發生什么 ?
首先,鍵的哈希值被計算出來,然后這個值會賦給 Entry 類中對應的 hashCode 變量。
然后,使用這個哈希值找到它將要被存入的數組中“桶”的索引。
如果該位置的“桶”中已經有一個元素,那么新的元素會被插入到“桶”的頭部,next 指向上一個元素——本質上使“桶”形成鏈表。
現在,當你用 key 去查詢值時,時間復雜度是 O(1)。雖然時間上 HashMap 更快,但同時它也花費了更多的內存空間。
缺點:
自動裝箱的存在意味著每一次插入都會有額外的對象創建。這跟垃圾回收機制一樣也會影響到內存的利用。
HashMap.Entry 對象本身是一層額外需要被創建以及被垃圾回收的對象。
“桶” 在 HashMap 每次被壓縮或擴容的時候都會被重新安排。這個操作會隨著對象數量的增長而變得開銷極大
在Android中,當涉及到快速響應的應用時,內存至關重要,因為持續地分發和釋放內存會出發垃圾回收機制,這會拖慢應用運行。垃圾回收機制會影響應用性能表現,垃圾回收時間段內,應用程序是不會運行的,最終應用使用上就顯得卡頓。
SparseArray博客
它里面也用了兩個數組。一個int[] mKeys和Object[] mValues。從名字都可以看得出來一個用來存儲key一個用來保存value的。
當保存一對鍵值對的時候:
key(不是它的hashcode)保存在mKeys[]的下一個可用的位置上。所以不會再對key自動裝箱了。
value保存在mValues[]的下一個位置上,value還是要自動裝箱的,如果它是基本類型。
查找的時候:
查找key還是用的二分法查找。也就是說它的時間復雜度還是O(logN)
知道了key的index,也就可以用key的index來從mValues中檢索出value。
相較于HashMap,我們舍棄了Entry和Object類型的key,放棄了HashCode并依賴于二分法查找。在添加和刪除操作的時候有更好的性能開銷。
25.0.0.3 LayoutManager作用是什么?LayoutManager樣式有哪些?setLayoutManager源碼里做了什么?
LayoutManager作用是什么?
LayoutManager的職責是擺放Item的位置,并且負責決定何時回收和重用Item。博客
RecyclerView 允許自定義規則去放置子 view,這個規則的控制者就是 LayoutManager。一個 RecyclerView 如果想展示內容,就必須設置一個 LayoutManager
LayoutManager樣式有哪些?
LinearLayoutManager 水平或者垂直的Item視圖。
GridLayoutManager 網格Item視圖。
StaggeredGridLayoutManager 交錯的網格Item視圖。
setLayoutManager(LayoutManager layout)源碼
分析:當之前設置過 LayoutManager 時,移除之前的視圖,并緩存視圖在 Recycler 中,將新的 mLayout 對象與 RecyclerView 綁定,更新緩存 View 的數量。最后去調用 requestLayout ,重新請求 measure、layout、draw。
public void setLayoutManager(LayoutManager layout) { if (layout == mLayout) { return; } // 停止滑動 stopScroll(); if (mLayout != null) { // 如果有動畫,則停止所有的動畫 if (mItemAnimator != null) { mItemAnimator.endAnimations(); } // 移除并回收視圖 mLayout.removeAndRecycleAllViews(mRecycler); // 回收廢棄視圖 mLayout.removeAndRecycleScrapInt(mRecycler); //清除mRecycler mRecycler.clear(); if (mIsAttached) { mLayout.dispatchDetachedFromWindow(this, mRecycler); } mLayout.setRecyclerView(null); mLayout = null; } else { mRecycler.clear(); } mChildHelper.removeAllViewsUnfiltered(); mLayout = layout; if (layout != null) { if (layout.mRecyclerView != null) { throw new IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView: " + layout.mRecyclerView); } mLayout.setRecyclerView(this); if (mIsAttached) { mLayout.dispatchAttachedToWindow(this); } } //更新新的緩存數據 mRecycler.updateViewCacheSize(); //重新請求 View 的測量、布局、繪制 requestLayout(); }25.0.0.4 SnapHelper主要是做什么用的?SnapHelper是怎么實現支持RecyclerView的對齊方式?
SnapHelper主要是做什么用的
在某些場景下,卡片列表滑動瀏覽[有的叫輪播圖],希望當滑動停止時可以將當前卡片停留在屏幕某個位置,比如停在左邊,以吸引用戶的焦點。那么可以使用RecyclerView + Snaphelper來實現
SnapHelper是怎么實現支持RecyclerView的對齊方式
SnapHelper旨在支持RecyclerView的對齊方式,也就是通過計算對齊RecyclerView中TargetView 的指定點或者容器中的任何像素點。博客
SnapHelper類重要的方法
attachToRecyclerView: 將SnapHelper attach 到指定的RecyclerView 上。
calculateDistanceToFinalSnap:復寫這個方法計算對齊到TargetView或容器指定點的距離,這是一個抽象方法,由子類自己實現,返回的是一個長度為2的int 數組out,out[0]是x方向對齊要移動的距離,out[1]是y方向對齊要移動的距離。
calculateScrollDistance: 根據每個方向給定的速度估算滑動的距離,用于Fling 操作。
findSnapView:提供一個指定的目標View 來對齊,抽象方法,需要子類實現
findTargetSnapPosition:提供一個用于對齊的Adapter 目標position,抽象方法,需要子類自己實現。
onFling:根據給定的x和 y 軸上的速度處理Fling。
什么是Fling操作
手指在屏幕上滑動 RecyclerView然后松手,RecyclerView中的內容會順著慣性繼續往手指滑動的方向繼續滾動直到停止,這個過程叫做 Fling 。 Fling 操作從手指離開屏幕瞬間被觸發,在滾動停止時結束。
LinearSnapHelper類分析
LinearSnapHelper 使當前Item居中顯示,常用場景是橫向的RecyclerView,類似ViewPager效果,但是又可以快速滑動(滑動多頁)。博客
最簡單的使用就是,如下代碼
幾行代碼就可以用RecyclerView實現一個類似ViewPager的效果,并且效果還不錯。可以快速滑動多頁,當前頁劇中顯示,并且顯示前一頁和后一頁的部分。
LinearSnapHelper snapHelper = new LinearSnapHelper(); snapHelper.attachToRecyclerView(mRecyclerView);
PagerSnapHelper類分析
PagerSnapHelper看名字可能就能猜到,使RecyclerView像ViewPager一樣的效果,每次只能滑動一頁(LinearSnapHelper支持快速滑動), PagerSnapHelper也是Item居中對齊。
最簡單的使用就是,如下代碼
PagerSnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(mRecyclerView);25.0.0.5 SpanSizeLookup的作用是干什么的?SpanSizeLookup如何使用?SpanSizeLookup實現原理如何理解?
SpanSizeLookup的作用是干什么的?
RecyclerView 可以通過 GridLayoutManager 實現網格布局, 但是很少有人知道GridLayoutManager 還可以用來設置網格中指定Item的列數,類似于合并單元格的功能,而所有的這些我們僅僅只需通過定義一個RecycleView列表就可以完成,要實現指定某個item所占列數的功能我們需要用到GridLayoutManager.SpanSizeLookup這個類,該類是一個抽象類,里面包含了一個getSpanSize(int position)的抽象方法,該方法的返回值就是指定position所占的列數
SpanSizeLookup如何使用?
先是定義了一個6列的網格布局,然后通過GridLayoutManager.SpanSizeLookup這個類來動態的指定某個item應該占多少列。博客
比如getSpanSize返回6,就表示當前position索引處的item占用6列,那么顯示就只會展示一個ItemView【占用6列】。
比如getSpanSize返回3,就表示當前position索引處的item占用3列
GridLayoutManager manager = new GridLayoutManager(this, 6); manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { SpanModel model = mDataList.get(position); if (model.getType() == 1) { return 6; } else if(model.getType() == 2){ return 3; }else if (model.getType() == 3){ return 2; }else if (model.getType() == 4){ return 2; } else { return 1; } } });25.0.0.6 ItemDecoration的用途是什么?自定義ItemDecoration有哪些重寫方法?分析一下addItemDecoration()源碼?
ItemDecoration的用途是什么?
通過設置recyclerView.addItemDecoration(new DividerDecoration(this));來改變Item之間的偏移量或者對Item進行裝飾。
當然,你也可以對RecyclerView設置多個ItemDecoration,列表展示的時候會遍歷所有的ItemDecoration并調用里面的繪制方法,對Item進行裝飾。博客
自定義ItemDecoration有哪些重寫方法
該抽象類常見的方法如下所示:博客
public void onDraw(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之前調用,所以這有可能被Item的內容所遮擋 public void onDrawOver(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之后調用,因此裝飾將浮于Item之上 public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 與padding或margin類似,LayoutManager在測量階段會調用該方法,計算出每一個Item的正確尺寸并設置偏移量。
分析一下addItemDecoration()源碼?
a.通過下面代碼可知,mItemDecorations是一個ArrayList,我們將ItemDecoration也就是分割線對象,添加到其中。
可以看到,當通過這個方法添加分割線后,會指定添加分割線在集合中的索引,然后再重新請求 View 的測量、布局、(繪制)。注意: requestLayout會調用onMeasure和onLayout,不一定調用onDraw!
關于View自定義控件源碼分析,可以參考我的其他博客:https://github.com/yangchong2...
public void addItemDecoration(ItemDecoration decor) { addItemDecoration(decor, -1); } //主要看這個方法,我的GitHub:https://github.com/yangchong211/YCBlogs public void addItemDecoration(ItemDecoration decor, int index) { if (mLayout != null) { mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" + " layout"); } if (mItemDecorations.isEmpty()) { setWillNotDraw(false); } if (index < 0) { mItemDecorations.add(decor); } else { // 指定添加分割線在集合中的索引 mItemDecorations.add(index, decor); } markItemDecorInsetsDirty(); // 重新請求 View 的測量、布局、繪制 requestLayout(); }
總結概括博客
可以看到在 View 的以上兩個方法中,分別調用了 ItemDecoration 對象的 onDraw onDrawOver 方法。
這兩個抽象方法,由我們繼承 ItemDecoration 來自己實現,他們區別就是 onDraw 在 item view 繪制之前調用,onDrawOver 在 item view 繪制之后調用。
所以繪制順序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。
25.0.0.7 上拉加載更多的功能是如何做的?添加滾動監聽事件需要注意什么問題?網格布局上拉加載如何優化?
上拉加載更多的功能是如何做的?
01.添加recyclerView的滑動事件
首先給recyclerView添加滑動監聽事件。那么我們知道,上拉加載時,需要具備兩個條件。第一個是監聽滑動到最后一個item,第二個是滑動到最后一個并且是向上滑動。
設置滑動監聽器,RecyclerView自帶的ScrollListener,獲取最后一個完全顯示的itemPosition,然后判斷是否滑動到了最后一個item,
02.上拉加載分頁數據
然后開始調用更新上拉加載更多數據的方法。注意這里的刷新數據,可以直接用notifyItemRangeInserted方法,不要用notifyDataSetChanged方法。
03.設置上拉加載的底部footer布局
在adapter中,可以上拉加載時處理footerView的邏輯
在getItemViewType方法中設置最后一個Item為FooterView
在onCreateViewHolder方法中根據viewType來加載不同的布局
最后在onBindViewHolder方法中設置一下加載的狀態顯示就可以
由于多了一個FooterView,所以要記得在getItemCount方法的返回值中加上1。
04.顯示和隱藏footer布局
一般情況下,滑動底部最后一個item,然后顯示footer上拉加載布局,然后讓其加載500毫秒,最后加載出下一頁數據后再隱藏起來。博客
網格布局上拉加載如何優化
如果是網格布局,那么上拉刷新的view則不是居中顯示,到加載更多的進度條顯示在了一個Item上,如果想要正常顯示的話,進度條需要橫跨兩個Item,這該怎么辦呢?
在adapter中的onAttachedToRecyclerView方法中處理網格布局情況,代碼如下所示,主要邏輯是如果當前是footer的位置,那么該item占據2個單元格,正常情況下占據1個單元格。
@Override public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager instanceof GridLayoutManager) { final GridLayoutManager gridManager = ((GridLayoutManager) manager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { // 如果當前是footer的位置,那么該item占據2個單元格,正常情況下占據1個單元格 return getItemViewType(position) == footType ? gridManager.getSpanCount() : 1; } }); } }
那么如何實現自動進行上拉刷新?
設置滑動監聽,判斷是否滑動到底部,也就是最后一條數據,當滑動到最后時就開始加載下一頁數據,并且顯示加載下一頁loading。當加載數據成功后,則直接隱藏該布局。
那么如何實現手動上拉刷新呢?
在上面步驟的基礎上進行修改,當滑動到最后一個數據時,展示上拉加載更多布局。然后設置它的點擊事件,點擊之后開始加載下一頁數據,當加載完成后,則直接隱藏該布局。
25.0.0.8 RecyclerView繪制原理如何理解?性能優化本質是什么?RecyclerView繪制原理過程大概是怎樣的?
RecyclerView繪制原理如何理解?
性能優化本質是什么?
RecyclerView做性能優化要說復雜也復雜,比如說布局優化,緩存,預加載,復用池,刷新數據等等。
其優化的點很多,在這些看似獨立的點之間,其實存在一個樞紐:Adapter。因為所有的ViewHolder的創建和內容的綁定都需要經過Adapter的兩個函數onCreateViewHolder和onBindViewHolder。
因此性能優化的本質就是要減少這兩個函數的調用時間和調用的次數。博客
如果我們想對RecyclerView做性能優化,必須清楚的了解到我們的每一步操作背后,onCreateViewHolder和onBindViewHolder調用了多少次。
RecyclerView繪制原理過程大概是怎樣的?
簡化問題
RecyclerView 以LinearLayoutManager為例 忽略ItemDecoration 忽略ItemAnimator 忽略Measure過程 假設RecyclerView的width和height是確定的 Recycler 忽略mViewCacheExtension
繪制過程
類的職責介紹
LayoutManager:接管RecyclerView的Measure,Layout,Draw的過程
Recycler:緩存池
Adapter:ViewHolder的生成器和內容綁定器。博客
繪制過程簡介
RecyclerView.requestLayout開始發生繪制,忽略Measure的過程
在Layout的過程會通過LayoutManager.fill去將RecyclerView填滿
LayoutManager.fill會調用LayoutManager.layoutChunk去生成一個具體的ViewHolder
然后LayoutManager就會調用Recycler.getViewForPosition向Recycler去要ViewHolder
Recycler首先去一級緩存(Cache)里面查找是否命中,如果命中直接返回。如果一級緩存沒有找到,則去三級緩存查找,如果三級緩存找到了則調用Adapter.bindViewHolder來綁定內容,然后返回。如果三級緩存沒有找到,那么就通過Adapter.createViewHolder創建一個ViewHolder,然后調用Adapter.bindViewHolder綁定其內容,然后返回為Recycler。
一直重復步驟3-5,知道創建的ViewHolder填滿了整個RecyclerView為止。
25.0.0.9 RecyclerView的Recyler是如何實現ViewHolder的緩存?如何理解recyclerView三級緩存是如何實現的?
RecyclerView的Recyler是如何實現ViewHolder的緩存?
首先看看代碼
public final class Recycler { final ArrayListmAttachedScrap = new ArrayList<>(); ArrayList mChangedScrap = null; final ArrayList mCachedViews = new ArrayList (); private final List mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; int mViewCacheMax = DEFAULT_CACHE_SIZE; RecycledViewPool mRecyclerPool; private ViewCacheExtension mViewCacheExtension; static final int DEFAULT_CACHE_SIZE = 2; }
RecyclerView在Recyler里面實現ViewHolder的緩存,Recycler里面的實現緩存的主要包含以下5個對象:
ArrayList mAttachedScrap:未與RecyclerView分離的ViewHolder列表,如果仍依賴于 RecyclerView (比如已經滑動出可視范圍,但還沒有被移除掉),但已經被標記移除的 ItemView 集合會被添加到 mAttachedScrap 中
按照id和position來查找ViewHolder
ArrayList mChangedScrap:表示數據已經改變的viewHolder列表,存儲 notifXXX 方法時需要改變的 ViewHolder,匹配機制按照position和id進行匹配
ArrayList mCachedViews:緩存ViewHolder,主要用于解決RecyclerView滑動抖動時的情況,還有用于保存Prefetch的ViewHoder
最大的數量為:mViewCacheMax = mRequestedCacheMax + extraCache(extraCache是由prefetch的時候計算出來的)
ViewCacheExtension mViewCacheExtension:開發者可自定義的一層緩存,是虛擬類ViewCacheExtension的一個實例,開發者可實現方法getViewForPositionAndType(Recycler recycler, int position, int type)來實現自己的緩存。
位置固定
內容不變
數量有限
mRecyclerPool ViewHolder緩存池,在有限的mCachedViews中如果存不下ViewHolder時,就會把ViewHolder存入RecyclerViewPool中。
按照Type來查找ViewHolder
每個Type默認最多緩存5個博客
如何理解recyclerView三級緩存是如何實現的?
RecyclerView在設計的時候講上述5個緩存對象分為了3級。每次創建ViewHolder的時候,會按照優先級依次查詢緩存創建ViewHolder。每次講ViewHolder緩存到Recycler緩存的時候,也會按照優先級依次緩存進去。三級緩存分別是:
一級緩存:返回布局和內容都都有效的ViewHolder
按照position或者id進行匹配
命中一級緩存無需onCreateViewHolder和onBindViewHolder
mAttachScrap在adapter.notifyXxx的時候用到
mChanedScarp在每次View繪制的時候用到,因為getViewHolderForPosition非調用多次,后面將
mCachedView:用來解決滑動抖動的情況,默認值為2
二級緩存:返回View
按照position和type進行匹配
直接返回View
需要自己繼承ViewCacheExtension實現
位置固定,內容不發生改變的情況,比如說Header如果內容固定,就可以使用
三級緩存:返回布局有效,內容無效的ViewHolder
按照type進行匹配,每個type緩存值默認=5
layout是有效的,但是內容是無效的
多個RecycleView可共享,可用于多個RecyclerView的優化
圖解博客
25.0.1.0 屏幕滑動(狀態是item狀態可見,不可見,即將可見變化)時三級緩存是如何理解的?adapter中的幾個方法是如何變化?
屏幕滑動(狀態是item狀態可見,不可見,即將可見變化)時三級緩存是如何理解的?
如圖所示
實例解釋:
由于ViewCacheExtension在實際使用的時候較少用到,因此本例中忽略二級緩存。mChangedScrap和mAttchScrap是RecyclerView內部控制的緩存,本例暫時忽略。
圖片解釋:
RecyclerView包含三部分:已經出屏幕,在屏幕里面,即將進入屏幕,我們滑動的方向是向上
RecyclerView包含三種Type:1,2,3。屏幕里面的都是Type=3
紅色的線代表已經出屏幕的ViewHolder與Recycler的交互情況
綠色的線代表,即將進入屏幕的ViewHolder進入屏幕時候,ViewHolder與Recycler的交互情況
出屏幕時候的情況
當ViewHolder(position=0,type=1)出屏幕的時候,由于mCacheViews是空的,那么就直接放在mCacheViews里面,ViewHolder在mCacheViews里面布局和內容都是有效的,因此可以直接復用。
ViewHolder(position=1,type=2)同步驟1 - 當ViewHolder(position=2,type=1)出屏幕的時候由于一級緩存mCacheViews已經滿了,因此將其放入RecyclerPool(type=1)的緩存池里面。此時ViewHolder的內容會被標記為無效,當其復用的時候需要再次通過Adapter.bindViewHolder來綁定內容。 ViewHolder(position=3,type=2)同步驟3 - 進屏幕時候的情況[博客](https://github.com/yangchong211/YCBlogs) - 當ViewHolder(position=3-10,type=3)進入屏幕繪制的時候,由于Recycler的mCacheViews里面找不到position匹配的View,同時RecyclerPool里面找不到type匹配的View,因此,其只能通過adapter.createViewHolder來創建ViewHolder,然后通過adapter.bindViewHolder來綁定內容。 - 當ViewHolder(position=11,type=1)進入屏幕的時候,發現ReccylerPool里面能找到type=1的緩存,因此直接從ReccylerPool里面取來使用。由于內容是無效的,因此還需要調用bindViewHolder來綁定布局。同時ViewHolder(position=4,type=3)需要出屏幕,其直接進入RecyclerPool(type=3)的緩存池中 - ViewHolder(position=12,type=2)同步驟6 - 屏幕往下拉ViewHolder(position=1)進入屏幕的情況 - 由于mCacheView里面的有position=1的ViewHolder與之匹配,直接返回。由于內容是有效的,因此無需再次綁定內容 - ViewHolder(position=0)同步驟825.0.1.1 SnapHelper有哪些重要的方法,其作用就是是什么?LinearSnapHelper中是如何實現滾動停止的?
SnapHelper有哪些重要的方法,其作用就是是什么?
calculateDistanceToFinalSnap抽象方法
計算最終對齊要移動的距離
計算二個參數對應的 ItemView 當前的坐標與需要對齊的坐標之間的距離。該方法返回一個大小為 2 的 int 數組,分別對應out[0] 為 x 方向移動的距離,out[1] 為 y 方向移動的距離。
@SuppressWarnings("WeakerAccess") @Nullable public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager, @NonNull View targetView);
- findSnapView抽象方法 - 找到要對齊的View - 該方法會找到當前 layoutManager 上最接近對齊位置的那個 view ,該 view 稱為 SanpView ,對應的 position 稱為 SnapPosition 。如果返回 null ,就表示沒有需要對齊的 View ,也就不會做滾動對齊調整。 ``` @SuppressWarnings("WeakerAccess") @Nullable public abstract View findSnapView(LayoutManager layoutManager); ``` - findTargetSnapPosition抽象方法 - 找到需要對齊的目標View的的Position。[博客](https://github.com/yangchong211/YCBlogs) - 更加詳細一點說就是該方法會根據觸發 Fling 操作的速率(參數 velocityX 和參數 velocityY )來找到 RecyclerView 需要滾動到哪個位置,該位置對應的 ItemView 就是那個需要進行對齊的列表項。我們把這個位置稱為 targetSnapPosition ,對應的 View 稱為 targetSnapView 。如果找不到 targetSnapPosition ,就返回RecyclerView.NO_POSITION 。 ``` public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX, int velocityY); ```
LinearSnapHelper中是如何實現滾動停止的?
SnapHelper繼承了 RecyclerView.OnFlingListener,實現了onFling方法。
獲取RecyclerView要進行fling操作需要的最小速率,為啥呢?因為只有超過該速率,ItemView才會有足夠的動力在手指離開屏幕時繼續滾動下去。該方法返回的是一個布爾值!
@Override public boolean onFling(int velocityX, int velocityY) { LayoutManager layoutManager = mRecyclerView.getLayoutManager(); if (layoutManager == null) { return false; } RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); if (adapter == null) { return false; } int minFlingVelocity = mRecyclerView.getMinFlingVelocity(); return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity) && snapFromFling(layoutManager, velocityX, velocityY); }
接著看看snapFromFling方法源代碼,就是通過該方法實現平滑滾動并使得在滾動停止時itemView對齊到目的坐標位置
首先layoutManager必須實現ScrollVectorProvider接口才能繼續往下操作
然后通過createSnapScroller方法創建一個SmoothScroller,這個東西是一個平滑滾動器,用于對ItemView進行平滑滾動操作
根據x和y方向的速度來獲取需要對齊的View的位置,需要子類實現
最終通過 SmoothScroller 來滑動到指定位置博客
private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX, int velocityY) { if (!(layoutManager instanceof ScrollVectorProvider)) { return false; } RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager); if (smoothScroller == null) { return false; } int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY); if (targetPosition == RecyclerView.NO_POSITION) { return false; } smoothScroller.setTargetPosition(targetPosition); layoutManager.startSmoothScroll(smoothScroller); return true; }
總結一下可知:snapFromFling()方法會先判斷layoutManager是否實現了ScrollVectorProvider接口,如果沒有實現該接口就不允許通過該方法做滾動操作。接下來就去創建平滑滾動器SmoothScroller的一個實例,layoutManager可以通過該平滑滾動器來進行滾動操作。SmoothScroller需要設置一個滾動的目標位置,將通過findTargetSnapPosition()方法來計算得到的targetSnapPosition給它,告訴滾動器要滾到這個位置,然后就啟動SmoothScroller進行滾動操作。
接著看下createSnapScroller這個方法源碼博客
先判斷layoutManager是否實現了ScrollVectorProvider這個接口,沒有實現該接口就不創建SmoothScroller
這里創建一個LinearSmoothScroller對象,然后返回給調用函數,也就是說,最終創建出來的平滑滾動器就是這個LinearSmoothScroller
在創建該LinearSmoothScroller的時候主要考慮兩個方面:
第一個是滾動速率,由calculateSpeedPerPixel()方法決定;
第二個是在滾動過程中,targetView即將要進入到視野時,將勻速滾動變換為減速滾動,然后一直滾動目的坐標位置,使滾動效果更真實,這是由onTargetFound()方法決定。
@Nullable protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) { if (!(layoutManager instanceof ScrollVectorProvider)) { return null; } return new LinearSmoothScroller(mRecyclerView.getContext()) { @Override protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView); final int dx = snapDistances[0]; final int dy = snapDistances[1]; final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); if (time > 0) { action.update(dx, dy, time, mDecelerateInterpolator); } } @Override protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; } }; }25.0.1.2 LinearSnapHelper代碼中calculateDistanceToFinalSnap作用是什么?那么out[0]和out[1]分別指什么?
calculateDistanceToFinalSnap的作用是什么
如果是水平方向滾動的,則計算水平方向需要移動的距離,否則水平方向的移動距離為0
如果是豎直方向滾動的,則計算豎直方向需要移動的距離,否則豎直方向的移動距離為0
distanceToCenter方法主要作用是:計算水平或者豎直方向需要移動的距離
@Override public int[] calculateDistanceToFinalSnap( @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToCenter(layoutManager, targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToCenter(layoutManager, targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; }
接著看看distanceToCenter方法
計算對應的view的中心坐標到RecyclerView中心坐標之間的距離
首先是找到targetView的中心坐標
接著也就是找到容器【RecyclerView】的中心坐標
兩個中心坐標的差值就是targetView需要滾動的距離
private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) { final int childCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedMeasurement(targetView) / 2); final int containerCenter; if (layoutManager.getClipToPadding()) { containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; } else { containerCenter = helper.getEnd() / 2; } return childCenter - containerCenter; }
那么out[0]和out[1]分別指什么
返回的是一個長度為2的int 數組out,out[0]是x方向對齊要移動的距離,out[1]是y方向對齊要移動的距離。
25.0.1.3 如何實現可以設置分割線的顏色,寬度,以及到左右兩邊的寬度間距的自定義分割線,說一下思路?
需要實現的分割線功能
可以設置分割線的顏色,寬度,以及到左右兩邊的寬度間距。item默認分割線的顏色不可改變,那么只有重寫onDraw方法,通過設置畫筆point顏色來繪制分割線顏色。而設置分割線左右的間隔是通過getItemOffsets方法實現的。
幾個重要的方法說明
需要自定義類實現RecyclerView.ItemDecoration類,并選擇重寫合適方法。注意下面這三個方法有著強烈的因果關系!
//獲取當前view的位置信息,該方法主要是設置條目周邊的偏移量 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) //在item背后draw public void onDraw(Canvas c, RecyclerView parent, State state) //在item上邊draw public void onDrawOver(Canvas c, RecyclerView parent, State state)
注意的是三個方法的調用順序
首先調用的是getItemOffsets會被多次調用,在layoutManager每次測量可擺放的view的時候回調用一次,在當前狀態下需要擺放多少個view這個方法就會回調多少次。
其次會調用onDraw方法,ItemDecoration的onDraw方法是在RecyclerView的onDraw方法中調用的,注意這時候傳入的canvas是RecyclerView的canvas,要時刻注意這點,它是和RecyclerView的邊界是一致的。這個時候繪制的內容相當于背景,會被item覆蓋。
最后調用的是onDrawOver方法,ItemDecoration的onDrawOver方法是在RecyclerView的draw方法中調用的,同樣傳入的是RecyclerView的canvas,這時候onlayout已經調用,所以此時繪制的內容會覆蓋item。
為每個item實現索引的思路
要實現上面的可以設置分割線顏色和寬度,肯定是要繪制的,也就是需要使用到onDraw方法。那么在getItemOffsets方法中需要讓view擺放位置距離bottom的距離是分割線的寬度。博客
然后通過parent.getChildCount()方法拿到當前顯示的view的數量[注意,該方法并不會獲取不顯示的view的數量],循環遍歷后,直接用paint畫筆進行繪制[注意至于分割線的顏色就是需要設置畫筆的顏色]。
25.0.1.4 如何實現復雜type首頁需求?如果不封裝會出現什么問題和弊端?如何提高代碼的簡便性和高效性?
如何實現復雜type首頁需求
通常寫一個多Item列表的方法
根據不同的ViewType 處理不同的item,如果邏輯復雜,這個類的代碼量是很龐大的。如果版本迭代添加新的需求,修改代碼很麻煩,后期維護困難。
主要操作步驟
在onCreateViewHolder中根據viewType參數,也就是getItemViewType的返回值來判斷需要創建的ViewHolder類型
在onBindViewHolder方法中對ViewHolder的具體類型進行判斷,分別為不同類型的ViewHolder進行綁定數據與邏輯處理
代碼如下所示
public class HomeAdapter extends RecyclerView.Adapter { public static final int TYPE_BANNER = 0; public static final int TYPE_AD = 1; @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType){ case TYPE_BANNER: return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null)); case TYPE_AD: return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_ad_item_layout,null)); } return null; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { int type = getItemViewType(position); switch (type){ case TYPE_BANNER: // banner 邏輯處理 break; case TYPE_AD: // 廣告邏輯處理 break; // ... 此處省去N行代碼 } } @Override public int getItemViewType(int position) { if(position == 0){ return TYPE_BANNER;//banner在開頭 }else { return mData.get(position).type;//type 的值為TYPE_AD,TYPE_IMAGE,TYPE_AD,等其中一個 } } public static class BannerViewHolder extends RecyclerView.ViewHolder{ public BannerViewHolder(View itemView) { super(itemView); } } public static class NewViewHolder extends RecyclerView.ViewHolder{ public VideoViewHolder(View itemView) { super(itemView); } } }
如果不封裝會出現什么問題和弊端
RecyclerView 可以用ViewType來區分不同的item,也可以滿足需求,但還是存在一些問題,比如:
1,在item過多邏輯復雜列表界面,Adapter里面的代碼量龐大,邏輯復雜,后期難以維護。
2,每次增加一個列表都需要增加一個Adapter,重復搬磚,效率低下。
3,無法復用adapter,假如有多個頁面有多個type,那么就要寫多個adapter。
4,要是有局部刷新,那么就比較麻煩了,比如廣告區也是一個九宮格的RecyclerView,點擊局部刷新當前數據,比較麻煩。
上面那樣寫的弊端
類型檢查與類型轉型,由于在onCreateViewHolder根據不同類型創建了不同的ViewHolder,所以在onBindViewHolder需要針對不同類型的ViewHolder進行數據綁定與邏輯處理,這導致需要通過instanceof對ViewHolder進行類型檢查與類型轉型。
不利于擴展,目前的需求是列表中存在5種布局類類型,那么如果需求變動,極端一點的情況就是數據源是從服務器獲取的,數據中的model決定列表中的布局類型。這種情況下,每當model改變或model類型增加,我們都要去改變adapter中很多的代碼,同時Adapter還必須知道特定的model在列表中的位置(position)除非跟服務端約定好,model(位置)不變,很顯然,這是不現實的。
不利于維護,這點應該是上一點的延伸,隨著列表中布局類型的增加與變更,getItemViewType、onCreateViewHolder、onBindViewHolder中的代碼都需要變更或增加,Adapter 中的代碼會變得臃腫與混亂,增加了代碼的維護成本。
如何提高代碼的簡便性和高效性。具體封裝庫看:recyclerView復雜type封裝庫
核心目的就是三個
避免類的類型檢查與類型轉型
增強Adapter的擴展性
增強Adapter的可維護性
當列表中類型增加或減少時Adapter中主要改動的就是getItemViewType、onCreateViewHolder、onBindViewHolder這三個方法,因此,我們就從這三個方法中開始著手。
既然可能存在多個type類型的view,那么能不能把這些比如banner,廣告,文本,視頻,新聞等當做一個HeaderView來操作。
在getItemViewType方法中。
減少if之類的邏輯判斷簡化代碼,可以簡單粗暴的用hashCode作為增加type標識。
通過創建列表的布局類型,同時返回的不再是簡單的布局類型標識,而是布局的hashCode值
onCreateViewHolder
getItemViewType返回的是布局hashCode值,也就是onCreateViewHolder(ViewGroup parent, int viewType)參數中的viewType
在onBindViewHolder方法中。可以看到,在此方法中,添加一種header類型的view,則通過onBindView進行數據綁定。
封裝后好處
拓展性——Adapter并不關心不同的列表類型在列表中的位置,因此對于Adapter來說列表類型可以隨意增加或減少。十分方便,同時設置類型view的布局和數據綁定都不需要在adapter中處理。充分解耦。
可維護性——不同的列表類型由adapter添加headerView處理,哪怕添加多個headerView,相互之間互不干擾,代碼簡潔,維護成本低。
25.0.1.5 關于item條目點擊事件在onCreateViewHolder中寫和在onBindViewHolder中寫有何區別?如何優化?
關于rv設置item條目點擊事件有兩種方式:1.在onCreateViewHolder中寫;2.在onBindViewHolder中寫;3.在ViewHolder中寫。那么究竟是哪一種好呢?
1.在onCreateViewHolder中寫
@NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { final View view = LayoutInflater.from(mContext).inflate(R.layout.item_me_gv_grid, parent, false); final MyViewHolder holder = new MyViewHolder(view); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (listener != null) { listener.onItemClick(view, holder.getLayoutPosition()); } } }); return holder; }
2.在onBindViewHolder中寫
@Override public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (listener != null) { listener.onItemClick(holder.itemView, holder.getAdapterPosition()); } } }); }
onBindViewHolder() 中頻繁創建新的 onClickListener 實例沒有必要,建議實際開發中應該在 onCreateViewHolder() 中每次為新建的 View 設置一次就行。
25.0.1.6 RecyclerView滑動卡頓原因有哪些?如何解決嵌套布局滑動沖突?如何解決RecyclerView實現畫廊卡頓?
RecyclerView滑動卡頓原因有哪些
第一種:嵌套布局滑動沖突
導致嵌套滑動難處理的關鍵原因在于當子控件消費了事件, 那么父控件就不會再有機會處理這個事件了, 所以一旦內部的滑動控件消費了滑動操作, 外部的滑動控件就再也沒機會響應這個滑動操作了
第二種:嵌套布局層次太深,比如六七層等
測量,繪制布局可能會導致滑動卡頓
第三種:比如用RecyclerView實現畫廊,加載比較大的圖片,如果快速滑動,則可能會出現卡頓,主要是加載圖片需要時間
第四種:在onCreateViewHolder或者在onBindViewHolder中做了耗時的操作導致卡頓。
如何解決嵌套布局滑動沖突
這個具體看我的博客:23.RecyclerView滑動沖突
如何解決RecyclerView實現畫廊卡頓?
RecyclerView 滑動時不讓 Glide 加載圖片。滾動停止后才開始恢復加載圖片。
//RecyclerView.SCROLL_STATE_IDLE //空閑狀態 //RecyclerView.SCROLL_STATE_FLING //滾動狀態 //RecyclerView.SCROLL_STATE_TOUCH_SCROLL //觸摸后狀態 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE) { LoggerUtils.e("initRecyclerView"+ "恢復Glide加載圖片"); Glide.with(ImageBrowseActivity.this).resumeRequests(); }else { LoggerUtils.e("initRecyclerView"+"禁止Glide加載圖片"); Glide.with(ImageBrowseActivity.this).pauseRequests(); } } });
在onCreateViewHolder或者在onBindViewHolder中做了耗時的操作導致卡頓
按stackoverflow上面比較通俗的解釋:RecyclerView.Adapter里面的onCreateViewHolder()方法和onBindViewHolder()方法對時間都非常敏感。類似I/O讀寫,Bitmap解碼一類的耗時操作,最好不要在它們里面進行。
25.0.1.7 RecyclerView常見的優化有哪些?實際開發中都是怎么做的,優化前后對比性能上有何提升?
RecyclerView常見的優化有哪些
DiffUtil刷新優化
分頁拉取遠端數據,對拉取下來的遠端數據進行緩存,提升二次加載速度;對于新增或者刪除數據通過 DiffUtil 來進行局部刷新數據,而不是一味地全局刷新數據。
布局優化
減少 xml 文件 inflate 時間
這里的 xml 文件不僅包括 layout 的 xml,還包括 drawable 的 xml,xml 文件 inflate 出 ItemView 是通過耗時的 IO 操作,尤其當 Item 的復用幾率很低的情況下,隨著 Type 的增多,這種 inflate 帶來的損耗是相當大的,此時我們可以用代碼去生成布局,即 new View() 的方式,只要搞清楚 xml 中每個節點的屬性對應的 API 即可。
減少 View 對象的創建
一個稍微復雜的 Item 會包含大量的 View,而大量的 View 的創建也會消耗大量時間,所以要盡可能簡化 ItemView;設計 ItemType 時,對多 ViewType 能夠共用的部分盡量設計成自定義 View,減少 View 的構造和嵌套。博客
對itemView中孩子View的點擊事件優化
onBindViewHolder() 中頻繁創建新的 onClickListener 實例沒有必要,建議實際開發中應該在 onCreateViewHolder() 中每次為新建的 View 設置一次就行。
其他的一些優化點
如果 Item 高度是固定的話,可以使用 RecyclerView.setHasFixedSize(true); 來避免 requestLayout 浪費資源;
具體看Understanding RecyclerView setHasFixedSize
設置 RecyclerView.addOnScrollListener(listener); 來對滑動過程中停止加載的操作。
如果不要求動畫,可以通過 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默認動畫關閉來提神效率。
通過重寫 RecyclerView.onViewRecycled(holder) 來回收資源。
通過 RecycleView.setItemViewCacheSize(size); 來加大 RecyclerView 的緩存,用空間換時間來提高滾動的流暢性。
如果多個 RecycledView 的 Adapter 是一樣的,比如嵌套的 RecyclerView 中存在一樣的 Adapter,可以通過設置 RecyclerView.setRecycledViewPool(pool); 來共用一個 RecycledViewPool。
25.0.1.8 如何解決RecyclerView嵌套RecyclerView條目自動上滾的Bug?如何解決ScrollView嵌套RecyclerView滑動沖突?
RecyclerView嵌套RecyclerView 條目自動上滾的Bug
RecyclerViewA嵌套RecyclerViewB 進入頁面自動跳轉到RecyclerViewB上面頁面會自動滾動。
解決辦法如下所示
一,recyclerview去除焦點
recyclerview.setFocusableInTouchMode(false);
recyclerview.requestFocus();
二,在代碼里面 讓處于ScrollView或者RecyclerView1 頂端的某個控件獲得焦點即可
比如頂部的一個textview
tv.setFocusableInTouchMode(true);
tv.requestFocus();
三,可以直接在Recy
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74392.html
摘要:支持復雜頁面,例如添加自定義頭部和底部布局,支持橫向滑動,還可以支持粘貼頭部類似微信好友分組,支持不規則瀑布流效果,支持側滑刪除功能。支持粘貼頭部的需求效果,這種效果類似微信好友分組的那種功能界面。 目錄介紹 1.復雜頁面庫介紹 2.本庫優勢亮點 2.1 支持多種狀態切換管理 2.2 支持添加多個header和footer 2.3 支持側滑功能和拖拽移動 2.4 其他亮點介紹 ...
摘要:,無法復用,假如有多個頁面有多個,那么就要寫多個。綁定,主要作用是綁定數據到正確的視圖上。可維護性不同的列表類型由添加處理,哪怕添加多個,相互之間互不干擾,代碼簡潔,維護成本低。 目錄介紹 01.先看看實際需求 02.adapter實現多個type 03.這樣寫的弊端 04.如何優雅實現adapter封裝 好消息 博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,...
摘要:為表示之前進行過滾動,為狀態表示滾動結束停下來的抽象方法抽象方法計算最終對齊要移動的距離計算二個參數對應的當前的坐標與需要對齊的坐標之間的距離。抽象方法找到要對齊的該方法會找到當前上最接近對齊位置的那個,該稱為,對應的稱為。 目錄介紹 01.SnapHelper簡單介紹 1.1 SnapHelper作用 1.2 SnapHelper類分析 1.3 LinearSnapHelper...
摘要:是規則的瀑布流。普通的尺寸會出現錯位的問題索引這個是右邊這個是左邊間距解決辦法,可以通過里的來判斷,這個方法不管你高度怎樣,他都是左右左右開始排列的。 目錄介紹 01.規則瀑布流實現02.不規則瀑布流實現2.1 實現方式2.2 遇到問題03.瀑布流上拉加載04.給瀑布流設置分割線05.自定義Manager崩潰06.如何避免刷新抖動07.為何有時出現跳動08.瀑布流圖片優化09.onBi...
摘要:此方法應由實現使用,以獲取視圖來表示來自的數據。如果適配器沒有指示給定位置上的數據已更改,則回收程序將嘗試發回一個以前為該數據初始化的報廢視圖,而不進行重新綁定。如果它只附加了一個適配器,并且新適配器使用與不同的,則將清除其緩存。 目錄介紹 1.RecycleView的結構 2.Adapter 2.1 RecyclerView.Adapter扮演的角色 2.2 重寫的方法 2.3...
摘要:支持復雜頁面,例如添加自定義頭部和底部布局,支持橫向滑動,還可以支持粘貼頭部類似微信好友分組,支持不規則瀑布流效果,支持側滑刪除功能。十分方便實現復雜的布局頁面,結構上層次分明,便于維護。 目錄介紹 1.復雜頁面庫介紹 2.本庫優勢亮點 2.1 支持多種狀態切換管理 2.2 支持添加多個header和footer 2.3 支持側滑功能和拖拽移動 2.4 其他亮點介紹 3.如...
閱讀 2546·2023-04-26 00:57
閱讀 917·2021-11-25 09:43
閱讀 2226·2021-11-11 16:55
閱讀 2223·2019-08-30 15:53
閱讀 3599·2019-08-30 15:52
閱讀 1463·2019-08-30 14:10
閱讀 3383·2019-08-30 13:22
閱讀 1216·2019-08-29 11:18