摘要:此方法應(yīng)由實現(xiàn)使用,以獲取視圖來表示來自的數(shù)據(jù)。如果適配器沒有指示給定位置上的數(shù)據(jù)已更改,則回收程序?qū)L試發(fā)回一個以前為該數(shù)據(jù)初始化的報廢視圖,而不進行重新綁定。如果它只附加了一個適配器,并且新適配器使用與不同的,則將清除其緩存。
目錄介紹
1.RecycleView的結(jié)構(gòu)
2.Adapter
2.1 RecyclerView.Adapter扮演的角色
2.2 重寫的方法
2.3 notifyDataSetChanged()刷新數(shù)據(jù)
2.4 數(shù)據(jù)變更通知之觀察者模式
a.首先看.notifyDataSetChanged()源碼
b.接著查看.notifyChanged()源碼
c.接著查看setAdapter()源碼中的setAdapterInternal(adapter, false, true)方法
d.notify……方法被調(diào)用,刷新數(shù)據(jù)
3.ViewHolder
3.1 ViewHolder的作用
3.2 ViewHolder與復(fù)用
3.3 ViewHolder簡單封裝
4.LayoutManager
4.1 作用
4.2 LayoutManager樣式
4.3 LayoutManager當前有且僅有一個抽象函數(shù)
4.4 setLayoutManager(LayoutManager layout)源碼
5.ItemDecoration
5.1 作用
5.2 RecyclerView.ItemDecoration是一個抽象類
5.3 addItemDecoration()源碼分析
a.首先看addItemDecoration源碼
b.接著看下markItemDecorInsetsDirty這個方法
c.接著看下mRecycler.markItemDecorInsetsDirty();這個方法
d.回過頭在看看addItemDecoration中requestLayout方法
e.在 RecyclerView 中搜索 mItemDecorations 集合
6.ItemAnimator
6.1 作用
6.2 觸發(fā)的三種事件
7.其他知識點
7.1 Recycler && RecycledViewPool
7.2 Recyclerview.getLayoutPosition()區(qū)別
8.RecyclerView嵌套方案滑動沖突解決方案
8.1 如何判斷RecyclerView控件滑動到頂部和底部
8.2 RecyclerView嵌套RecyclerView 條目自動上滾的Bug
8.3 ScrollView嵌套RecyclerView滑動沖突
8.4 ViewPager嵌套水平RecyclerView橫向滑動到底后不滑動ViewPager
9.RecyclerView復(fù)雜布局封裝庫案例
9.1 能夠?qū)崿F(xiàn)業(yè)務(wù)的需求和功能
9.2 具備的優(yōu)勢分析
10.針對阿里VLayout代碼分析
11.版本更新說明
v1.0.0 2016年5月5日
v1.1.0 更新于2017年2月1日
v1.1.1 更新于2017年6月9日
v2.0.0 更新于2018年8月21日
v2.1.0 更新于2018年9月29日
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識點,Android技術(shù)博客,Python學習筆記等等,還包括平時開發(fā)中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉(zhuǎn)載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
1.RecycleView的結(jié)構(gòu)
關(guān)于RecyclerView,大家都已經(jīng)很熟悉了,用途十分廣泛,大概結(jié)構(gòu)如下所示
RecyclerView.Adapter - 處理數(shù)據(jù)集合并負責綁定視圖
ViewHolder - 持有所有的用于綁定數(shù)據(jù)或者需要操作的View
LayoutManager - 負責擺放視圖等相關(guān)操作
ItemDecoration - 負責繪制Item附近的分割線
ItemAnimator - 為Item的一般操作添加動畫效果,如,增刪條目等
如圖所示,直觀展示結(jié)構(gòu)
針對上面幾個屬性,最簡單用法如下所示
recyclerView = (RecyclerView) findViewById(R.id.recyclerView); LinearLayoutManager layoutManager = new LinearLayoutManager(this); //設(shè)置layoutManager recyclerView.setLayoutManager(layoutManager); final RecycleViewItemLine line = new RecycleViewItemLine(this, LinearLayout.HORIZONTAL,1,this.getResources().getColor(R.color.colorAccent)); //設(shè)置添加分割線 recyclerView.addItemDecoration(line); adapter = new MultipleItemAdapter(this); //設(shè)置adapter recyclerView.setAdapter(adapter); //添加數(shù)據(jù)并且刷新adapter adapter.addAll(list); adapter.notifyDataSetChanged(); //adapter //onCreateViewHolder(ViewGroup parent, int viewType)這里的第二個參數(shù)就是View的類型,可以根據(jù)這個類型判斷去創(chuàng)建不同item的ViewHolder public class MultipleItemAdapter extends RecyclerView.Adapter2.Adapter 2.1 RecyclerView.Adapter扮演的角色{ public static enum ITEM_TYPE { ITEM_TYPE_IMAGE, ITEM_TYPE_TEXT } private final LayoutInflater mLayoutInflater; private final Context mContext; private ArrayList mTitles; public MultipleItemAdapter(Context context) { mContext = context; mLayoutInflater = LayoutInflater.from(context); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()) { return new ImageViewHolder(mLayoutInflater.inflate(R.layout.item_image, parent, false)); } else { return new TextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false)); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof TextViewHolder) { ((TextViewHolder) holder).mTextView.setText(mTitles[position]); } else if (holder instanceof ImageViewHolder) { ((ImageViewHolder) holder).mTextView.setText(mTitles[position]); } } @Override public int getItemViewType(int position) { return position % 2 == 0 ? ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal() : ITEM_TYPE.ITEM_TYPE_TEXT.ordinal(); } @Override public int getItemCount() { return mTitles == null ? 0 : mTitles.length; } public void addAll(ArrayList list){ if(mTitles!=null){ mTitles.clear(); }else { mTitles = new ArrayList<>(); } mTitles.addAll(list); } public static class TextViewHolder extends RecyclerView.ViewHolder { @InjectView(R.id.text_view) TextView mTextView; TextViewHolder(View view) { super(view); ButterKnife.inject(this, view); } } public static class ImageViewHolder extends RecyclerView.ViewHolder { @InjectView(R.id.text_view) TextView mTextView; @InjectView(R.id.image_view) ImageView mImageView; ImageViewHolder(View view) { super(view); ButterKnife.inject(this, view); } } }
一是,根據(jù)不同ViewType創(chuàng)建與之相應(yīng)的的Item-Layout
二是,訪問數(shù)據(jù)集合并將數(shù)據(jù)綁定到正確的View上
2.2 重寫的方法
一般常用的重寫方法有以下這么幾個:
public VH onCreateViewHolder(ViewGroup parent, int viewType) 創(chuàng)建Item視圖,并返回相應(yīng)的ViewHolder public void onBindViewHolder(VH holder, int position) 綁定數(shù)據(jù)到正確的Item視圖上。 public int getItemCount() 返回該Adapter所持有的Itme數(shù)量 public int getItemViewType(int position) 用來獲取當前項Item(position參數(shù))是哪種類型的布局2.3 notifyDataSetChanged()刷新數(shù)據(jù)
當時據(jù)集合發(fā)生改變時,我們通過調(diào)用.notifyDataSetChanged(),來刷新列表,因為這樣做會觸發(fā)列表的重繪,所以并不會出現(xiàn)任何動畫效果,因此需要調(diào)用一些以notifyItem*()作為前綴的特殊方法,比如:
public final void notifyItemInserted(int position) 向指定位置插入Item
public final void notifyItemRemoved(int position) 移除指定位置Item
public final void notifyItemChanged(int position) 更新指定位置Item
2.4 數(shù)據(jù)變更通知之觀察者模式
a.首先看.notifyDataSetChanged()源碼
/** @see #notifyItemChanged(int) * @see #notifyItemInserted(int) * @see #notifyItemRemoved(int) * @see #notifyItemRangeChanged(int, int) * @see #notifyItemRangeInserted(int, int)
*/ public final void notifyDataSetChanged() { mObservable.notifyChanged(); } ```
b.接著查看.notifyChanged();源碼
被觀察者AdapterDataObservable,內(nèi)部持有觀察者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,具體實現(xiàn)為RecyclerViewDataObserver,當數(shù)據(jù)源發(fā)生變更時,及時響應(yīng)界面變化
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……方法被調(diào)用,刷新數(shù)據(jù)
當數(shù)據(jù)變更時,調(diào)用notify**方法時,Adapter內(nèi)部的被觀察者會遍歷通知已經(jīng)注冊的觀察者的對應(yīng)方法,這時界面就會響應(yīng)變更。
3.ViewHolder 3.1 ViewHolder的作用
ViewHolder作用大概有這些:
adapter應(yīng)當擁有ViewHolder的子類,并且ViewHolder內(nèi)部應(yīng)當存儲一些子view,避免時間代價很大的findViewById操作
其RecyclerView內(nèi)部定義的ViewHolder類包含很多復(fù)雜的屬性,內(nèi)部使用場景也有很多,而我們經(jīng)常使用的也就是onCreateViewHolder()方法和onBindViewHolder()方法,onCreateViewHolder()方法在RecyclerView需要一個新類型。item的ViewHolder時調(diào)用來創(chuàng)建一個ViewHolder,而onBindViewHolder()方法則當RecyclerView需要在特定位置的item展示數(shù)據(jù)時調(diào)用。
3.2 ViewHolder與復(fù)用
在復(fù)寫RecyclerView.Adapter的時候,需要我們復(fù)寫兩個方法:
onCreateViewHolder
onBindViewHolder
這兩個方法從字面上看就是創(chuàng)建ViewHolder和綁定ViewHolder的意思
復(fù)用機制是怎樣的?
模擬場景:只有一種ViewType,上下滑動的時候需要的ViewHolder種類是只有一種,但是需要的ViewHolder對象數(shù)量并不止一個。所以在后面創(chuàng)建了5個ViewHolder之后,需要的數(shù)量夠了,無論怎么滑動,都只需要復(fù)用以前創(chuàng)建的對象就行了。那么逗比程序員們思考一下,為什么會出現(xiàn)這種情況呢
看到了下面log之后,第一反應(yīng)是在這個ViewHolder對象的數(shù)量“夠用”之后就停止調(diào)用onCreateViewHolder方法,但是onBindViewHolder方法每次都會調(diào)用的
查看一下createViewHolder源代碼
發(fā)現(xiàn)這里并沒有限制
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對象的數(shù)量“夠用”之后就停止調(diào)用onCreateViewHolder方法,可以查看
獲取為給定位置初始化的視圖。
此方法應(yīng)由{@link LayoutManager}實現(xiàn)使用,以獲取視圖來表示來自{@LinkAdapter}的數(shù)據(jù)。
如果共享池可用于正確的視圖類型,則回收程序可以重用共享池中的廢視圖或分離視圖。如果適配器沒有指示給定位置上的數(shù)據(jù)已更改,則回收程序?qū)L試發(fā)回一個以前為該數(shù)據(jù)初始化的報廢視圖,而不進行重新綁定。
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) { //代碼省略了,有需要的小伙伴可以自己看看,這里面邏輯實在太復(fù)雜呢 }3.3 ViewHolder簡單封裝
關(guān)于ViewHolder簡單的封裝代碼如下所示:
public abstract class BaseMViewHolderextends RecyclerView.ViewHolder { // SparseArray 比 HashMap 更省內(nèi)存,在某些條件下性能更好,只能存儲 key 為 int 類型的數(shù)據(jù), // 用來存放 View 以減少 findViewById 的次數(shù) private SparseArray viewSparseArray; BaseMViewHolder(View itemView) { super(itemView); if(viewSparseArray==null){ viewSparseArray = new SparseArray<>(); } } public BaseMViewHolder(ViewGroup parent, @LayoutRes int res) { super(LayoutInflater.from(parent.getContext()).inflate(res, parent, false)); if(viewSparseArray==null){ viewSparseArray = new SparseArray<>(); } } /** * 子類設(shè)置數(shù)據(jù)方法
*/ public void setData(M data) {} /** * 第二種findViewById方式 * 根據(jù) ID 來獲取 View * @param viewId viewID * @param4.LayoutManager 4.1 作用泛型 * @return 將結(jié)果強轉(zhuǎn)為 View 或 View 的子類型 */ @SuppressWarnings("unchecked") protected T getView(int viewId) { // 先從緩存中找,找打的話則直接返回 // 如果找不到則 findViewById ,再把結(jié)果存入緩存中 View view = viewSparseArray.get(viewId); if (view == null) { view = itemView.findViewById(viewId); viewSparseArray.put(viewId, view); } return (T) view; } /** * 獲取上下文context * @return context */ protected Context getContext(){ return itemView.getContext(); } /** * 獲取數(shù)據(jù)索引的位置 * @return position */ protected int getDataPosition(){ RecyclerView.Adapter adapter = getOwnerAdapter(); if (adapter!=null && adapter instanceof RecyclerArrayAdapter){ return getAdapterPosition() - ((RecyclerArrayAdapter) adapter).getHeaderCount(); } return getAdapterPosition(); } /** * 獲取adapter對象 * @param * @return adapter */ @Nullable private T getOwnerAdapter(){ RecyclerView recyclerView = getOwnerRecyclerView(); //noinspection unchecked return recyclerView != null ? (T) recyclerView.getAdapter() : null; } @Nullable private RecyclerView getOwnerRecyclerView(){ try { Field field = RecyclerView.ViewHolder.class.getDeclaredField("mOwnerRecyclerView"); field.setAccessible(true); return (RecyclerView) field.get(this); } catch (NoSuchFieldException ignored) { ignored.printStackTrace(); } catch (IllegalAccessException ignored) { ignored.printStackTrace(); } return null; } /** * 添加子控件的點擊事件 * @param viewId 控件id */ protected void addOnClickListener(@IdRes final int viewId) { final View view = getView(viewId); if (view != null) { if (!view.isClickable()) { view.setClickable(true); } view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(getOwnerAdapter()!=null){ if (((RecyclerArrayAdapter)getOwnerAdapter()).getOnItemChildClickListener() != null) { ((RecyclerArrayAdapter)getOwnerAdapter()).getOnItemChildClickListener() .onItemChildClick(v, getDataPosition()); } } } }); } } //省略部分代碼 //關(guān)于adapter封裝可以查看我的開源adpater封裝庫:https://github.com/yangchong211/YCBaseAdapter //關(guān)于recyclerView封裝庫,可以查看我的開源庫:https://github.com/yangchong211/YCRefreshView } ```
LayoutManager的職責是擺放Item的位置,并且負責決定何時回收和重用Item。
RecyclerView 允許自定義規(guī)則去放置子 view,這個規(guī)則的控制者就是 LayoutManager。一個 RecyclerView 如果想展示內(nèi)容,就必須設(shè)置一個 LayoutManager
4.2 LayoutManager樣式LinearLayoutManager 水平或者垂直的Item視圖。
GridLayoutManager 網(wǎng)格Item視圖。
StaggeredGridLayoutManager 交錯的網(wǎng)格Item視圖。
4.3 LayoutManager當前有且僅有一個抽象函數(shù)
具體如下:
public LayoutParams generateDefaultLayoutParams()4.4 setLayoutManager(LayoutManager layout)源碼
a.setLayoutManager入口源碼
分析:當之前設(shè)置過 LayoutManager 時,移除之前的視圖,并緩存視圖在 Recycler 中,將新的 mLayout 對象與 RecyclerView 綁定,更新緩存 View 的數(shù)量。最后去調(diào)用 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); } } //更新新的緩存數(shù)據(jù) mRecycler.updateViewCacheSize(); //重新請求 View 的測量、布局、繪制 requestLayout(); }5.ItemDecoration 5.1 作用
通過設(shè)置recyclerView.addItemDecoration(new DividerDecoration(this));來改變Item之間的偏移量或者對Item進行裝飾。
當然,你也可以對RecyclerView設(shè)置多個ItemDecoration,列表展示的時候會遍歷所有的ItemDecoration并調(diào)用里面的繪制方法,對Item進行裝飾。
5.2 RecyclerView.ItemDecoration是一個抽象類
該抽象類常見的方法如下所示:
public void onDraw(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之前調(diào)用,所以這有可能被Item的內(nèi)容所遮擋 public void onDrawOver(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之后調(diào)用,因此裝飾將浮于Item之上 public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 與padding或margin類似,LayoutManager在測量階段會調(diào)用該方法,計算出每一個Item的正確尺寸并設(shè)置偏移量。5.3 .addItemDecoration()源碼分析
a.通過下面代碼可知,mItemDecorations是一個ArrayList,我們將ItemDecoration也就是分割線對象,添加到其中。
可以看到,當通過這個方法添加分割線后,會指定添加分割線在集合中的索引,然后再重新請求 View 的測量、布局、(繪制)。注意: requestLayout會調(diào)用onMeasure和onLayout,不一定調(diào)用onDraw!
關(guān)于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(); }
b.接著看下markItemDecorInsetsDirty這個方法做了些什么
這個方法先獲取所有子View的數(shù)量,然后遍歷了 RecyclerView 和 LayoutManager 的所有子 View,再將其子 View 的 LayoutParams 中的 mInsetsDirty 屬性置為 true,最后調(diào)用了 mRecycler.markItemDecorInsetsDirty()方法處理復(fù)用邏輯。
void markItemDecorInsetsDirty() { final int childCount = mChildHelper.getUnfilteredChildCount(); //先遍歷了 RecyclerView 和 LayoutManager 的所有子 View for (int i = 0; i < childCount; i++) { final View child = mChildHelper.getUnfilteredChildAt(i); //將其子 View 的 LayoutParams 中的 mInsetsDirty 屬性置為 true ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; } //調(diào)用了 mRecycler.markItemDecorInsetsDirty(), //Recycler 是 RecyclerView 的一個內(nèi)部類,就是它管理著 RecyclerView 的復(fù)用邏輯 mRecycler.markItemDecorInsetsDirty(); }
c.接著看下markItemDecorInsetsDirty()這個方法
該方法就是獲取RecyclerView 緩存的集合,然后遍歷集合得到RecyclerView 的緩存單位是 ViewHolder,獲取緩存對象,在獲取到layoutParams,并且將其 mInsetsDirty 字段一樣置為 true
void markItemDecorInsetsDirty() { //就是 RecyclerView 緩存的集合 final int cachedCount = mCachedViews.size(); for (int i = 0; i < cachedCount; i++) { //RecyclerView 的緩存單位是 ViewHolder,獲取緩存對象 final ViewHolder holder = mCachedViews.get(i); //獲得 LayoutParams LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); if (layoutParams != null) { //將其 mInsetsDirty 字段一樣置為 true layoutParams.mInsetsDirty = true; } } }
d.回過頭在看看addItemDecoration中requestLayout方法
requestLayout 方法用一種責任鏈的方式,層層向上傳遞,最后傳遞到 ViewRootImpl,然后重新調(diào)用 view 的 measure、layout、draw 方法來展示布局
@CallSuper public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } }
e.在 RecyclerView 中搜索 mItemDecorations 集合
在onDraw中
@Override public void onDraw(Canvas c) { super.onDraw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); } }
在draw方法中
@Override public void draw(Canvas c) { super.draw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); } //省略部分代碼 }
總結(jié)概括
可以看到在 View 的以上兩個方法中,分別調(diào)用了 ItemDecoration 對象的 onDraw onDrawOver 方法。
這兩個抽象方法,由我們繼承 ItemDecoration 來自己實現(xiàn),他們區(qū)別就是 onDraw 在 item view 繪制之前調(diào)用,onDrawOver 在 item view 繪制之后調(diào)用。
所以繪制順序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。
6.ItemAnimator 6.1 作用ItemAnimator能夠幫助Item實現(xiàn)獨立的動畫
6.2 觸發(fā)的三種事件某條數(shù)據(jù)被插入到數(shù)據(jù)集合中
從數(shù)據(jù)集合中移除某條數(shù)據(jù)
更改數(shù)據(jù)集合中的某條數(shù)據(jù)
7.其他知識點 7.1 Recycler && RecycledViewPool
RecycledViewPool
RecyclerViewPool用于多個RecyclerView之間共享View。只需要創(chuàng)建一個RecyclerViewPool實例,然后調(diào)用RecyclerView的setRecycledViewPool(RecycledViewPool)方法即可。RecyclerView默認會創(chuàng)建一個RecyclerViewPool實例。
下列源碼,是我借助于有道詞典翻譯部分注釋內(nèi)容……
看出mScrap是一個
public static class RecycledViewPool { private static final int DEFAULT_MAX_SCRAP = 5; static class ScrapData { final ArrayListmScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } SparseArray mScrap = new SparseArray<>(); private int mAttachCount = 0; //丟棄所有視圖 public void clear() { for (int i = 0; i < mScrap.size(); i++) { ScrapData data = mScrap.valueAt(i); data.mScrapHeap.clear(); } } //設(shè)置丟棄前要在池中持有的視圖持有人的最大數(shù)量 public void setMaxRecycledViews(int viewType, int max) { ScrapData scrapData = getScrapDataForType(viewType); scrapData.mMaxScrap = max; final ArrayList scrapHeap = scrapData.mScrapHeap; while (scrapHeap.size() > max) { scrapHeap.remove(scrapHeap.size() - 1); } } //返回給定視圖類型的RecycledViewPool所持有的當前視圖數(shù) public int getRecycledViewCount(int viewType) { return getScrapDataForType(viewType).mScrapHeap.size(); } //從池中獲取指定類型的ViewHolder,如果沒有指定類型的ViewHolder,則獲取{@Codenull} @Nullable public ViewHolder getRecycledView(int viewType) { final ScrapData scrapData = mScrap.get(viewType); if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { final ArrayList scrapHeap = scrapData.mScrapHeap; return scrapHeap.remove(scrapHeap.size() - 1); } return null; } //池持有的視圖持有者總數(shù) int size() { int count = 0; for (int i = 0; i < mScrap.size(); i++) { ArrayList viewHolders = mScrap.valueAt(i).mScrapHeap; if (viewHolders != null) { count += viewHolders.size(); } } return count; } //向池中添加一個廢視圖保存器。 //如果那個ViewHolder類型的池已經(jīng)滿了,它將立即被丟棄。 public void putRecycledView(ViewHolder scrap) { final int viewType = scrap.getItemViewType(); final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap; if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) { return; } if (DEBUG && scrapHeap.contains(scrap)) { throw new IllegalArgumentException("this scrap item already exists"); } scrap.resetInternal(); scrapHeap.add(scrap); } long runningAverage(long oldAverage, long newValue) { if (oldAverage == 0) { return newValue; } return (oldAverage / 4 * 3) + (newValue / 4); } void factorInCreateTime(int viewType, long createTimeNs) { ScrapData scrapData = getScrapDataForType(viewType); scrapData.mCreateRunningAverageNs = runningAverage( scrapData.mCreateRunningAverageNs, createTimeNs); } void factorInBindTime(int viewType, long bindTimeNs) { ScrapData scrapData = getScrapDataForType(viewType); scrapData.mBindRunningAverageNs = runningAverage( scrapData.mBindRunningAverageNs, bindTimeNs); } boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) { long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs; return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); } boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) { long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs; return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); } void attach(Adapter adapter) { mAttachCount++; } void detach() { mAttachCount--; } //分離舊適配器并附加新適配器。如果它只附加了一個適配器,并且新適配器使用與oldAdapter不同的ViewHolder, //則RecycledViewPool將清除其緩存。 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,boolean compatibleWithPrevious) { if (oldAdapter != null) { detach(); } if (!compatibleWithPrevious && mAttachCount == 0) { clear(); } if (newAdapter != null) { attach(newAdapter); } } private ScrapData getScrapDataForType(int viewType) { ScrapData scrapData = mScrap.get(viewType); if (scrapData == null) { scrapData = new ScrapData(); mScrap.put(viewType, scrapData); } return scrapData; } }
ViewCacheExtension
ViewCacheExtension是一個由開發(fā)者控制的可以作為View緩存的幫助類。調(diào)用Recycler.getViewForPosition(int)方法獲取View時,Recycler先檢查attachedscrap和一級緩存,如果沒有則檢查ViewCacheExtension.getViewForPositionAndType(Recycler, int, int),如果沒有則檢查RecyclerViewPool。注意:Recycler不會在這個類中做緩存View的操作,是否緩存View完全由開發(fā)者控制。
public abstract static class ViewCacheExtension { abstract public View getViewForPositionAndType(Recycler recycler, int position, int type); }
Recycler
后續(xù)再深入分析
7.2 Recyclerview.getLayoutPosition()問題
在RecycleView中的相關(guān)方法中,有兩種類型的位置
布局位置:從LayoutManager的角度看,條目在最新布局計算中的位置。
返回布局位置的方法使用最近一次布局運算后的位置,如getLayoutPosition()和findViewHolderForLayoutPosition(int)。這些位置包含了最近一次布局運算后的變化。你可以根據(jù)這些位置來與用戶正在屏幕上看到的保持一致。比如,你有一個條目列表,當用戶請求第5個條目時,你可以使用這些方法來匹配用戶看到的。
適配器位置:從適配器的角度看,條目在是適配器中的位置。
另外一系列方法與AdapterPosition關(guān)聯(lián),比如getAdapterPosition()和findViewHolderForAdapterPosition(int)。當你想獲得條目在更新后的適配器中的位置使用這些方法,即使這些位置變化還沒反映到布局中。比如,你想訪問適配器中條目的位置時,就應(yīng)該使用getAdapterPosition()。注意,notifyDataSetChanged()已經(jīng)被調(diào)用而且還沒計算新布局,這些方法或許不能夠計算適配器位置。所以,你要小心處理這些方法返回NO_POSITION和null的情況。
注意: 這兩種類型的位置是等同的,除非在分發(fā)adapter.notify*事件和更新布局時。
關(guān)于兩者的區(qū)別
網(wǎng)上查了一些資料,發(fā)現(xiàn)相關(guān)內(nèi)容很少,最后在stackoverflow上終于看到有大神這樣解釋兩者的區(qū)別
具體區(qū)別就是adapter和layout的位置會有時間差(<16ms), 如果你改變了Adapter的數(shù)據(jù)然后刷新視圖, layout需要過一段時間才會更新視圖, 在這段時間里面, 這兩個方法返回的position會不一樣。
在notifyDataSetChanged之后并不能馬上獲取Adapter中的position, 要等布局結(jié)束之后才能獲取到
在notifyItemInserted之后,Layout不能馬上獲取到新的position,因為布局還沒更新(需要<16ms的時間刷新視圖), 所以只能獲取到舊的,但是Adapter中的position就可以馬上獲取到最新的position。
public final int getAdapterPosition() { if (mOwnerRecyclerView == null) { return NO_POSITION; } return mOwnerRecyclerView.getAdapterPositionFor(this); } public final int getLayoutPosition() { return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; }
可能會導致的錯誤
這種情況有點難以復(fù)現(xiàn),在 ViewHolder 中處理 item 的點擊事件的時候,發(fā)現(xiàn)多個 item 同時點擊就會出現(xiàn)閃退,debug 看到 position = -1
解決辦法:使用 ViewHolder#getLayoutPosition() 獲取 position,而不要通過 ViewHolder#getAdapterPosition() 來獲取 position 的
8.RecyclerView嵌套方案滑動沖突解決方案 8.1 如何判斷RecyclerView控件滑動到頂部和底部
有一種使用場景,購物商城的購物車頁面,當RecyclerView滑動到頂部時,讓刷新控件消費事件;當RecyclerView滑動到底部時,讓下一頁控件[猜你喜歡]消費事件。
代碼如下所示:
public class VerticalRecyclerView extends RecyclerView { private float downX; private float downY; /** 第一個可見的item的位置 */ private int firstVisibleItemPosition; /** 第一個的位置 */ private int[] firstPositions; /** 最后一個可見的item的位置 */ private int lastVisibleItemPosition; /** 最后一個的位置 */ private int[] lastPositions; private boolean isTop; private boolean isBottom; public VerticalRecyclerView(Context context) { this(context, null); } public VerticalRecyclerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public VerticalRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { LayoutManager layoutManager = getLayoutManager(); if (layoutManager != null) { if (layoutManager instanceof GridLayoutManager) { lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); firstVisibleItemPosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition(); } else if (layoutManager instanceof LinearLayoutManager) { lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); firstVisibleItemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager; if (lastPositions == null) { lastPositions = new int[staggeredGridLayoutManager.getSpanCount()]; firstPositions = new int[staggeredGridLayoutManager.getSpanCount()]; } staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions); staggeredGridLayoutManager.findFirstVisibleItemPositions(firstPositions); lastVisibleItemPosition = findMax(lastPositions); firstVisibleItemPosition = findMin(firstPositions); } } else { throw new RuntimeException("Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager"); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downX = ev.getX(); downY = ev.getY(); //如果滑動到了最底部,就允許繼續(xù)向上滑動加載下一頁,否者不允許 getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: float dx = ev.getX() - downX; float dy = ev.getY() - downY; boolean allowParentTouchEvent; if (Math.abs(dy) > Math.abs(dx)) { if (dy > 0) { //位于頂部時下拉,讓父View消費事件 allowParentTouchEvent = isTop = firstVisibleItemPosition == 0 && getChildAt(0).getTop() >= 0; } else { //位于底部時上拉,讓父View消費事件 int visibleItemCount = layoutManager.getChildCount(); int totalItemCount = layoutManager.getItemCount(); allowParentTouchEvent = isBottom = visibleItemCount > 0 && (lastVisibleItemPosition) >= totalItemCount - 1 && getChildAt(getChildCount() - 1).getBottom() <= getHeight(); } } else { //水平方向滑動 allowParentTouchEvent = true; } getParent().requestDisallowInterceptTouchEvent(!allowParentTouchEvent); } return super.dispatchTouchEvent(ev); } private int findMax(int[] lastPositions) { int max = lastPositions[0]; for (int value : lastPositions) { if (value >= max) { max = value; } } return max; } private int findMin(int[] firstPositions) { int min = firstPositions[0]; for (int value : firstPositions) { if (value < min) { min = value; } } return min; } public boolean isTop() { return isTop; } public boolean isBottom() { return isBottom; } }8.2 RecyclerView嵌套RecyclerView條目自動上滾的Bug
RecyclerViewA嵌套RecyclerViewB 進入頁面自動跳轉(zhuǎn)到RecyclerViewB上面頁面會自動滾動。
兩種解決辦法
一,recyclerview去除焦點
recyclerview.setFocusableInTouchMode(false);
recyclerview.requestFocus();
二,在代碼里面 讓處于ScrollView或者RecyclerView1 頂端的某個控件獲得焦點即可
比如頂部的一個textview
tv.setFocusableInTouchMode(true);
tv.requestFocus();
8.3 ScrollView嵌套RecyclerView滑動沖突
第一種方式:
重寫父控件,讓父控件 ScrollView 直接攔截滑動事件,不向下分發(fā)給 RecyclerView,具體是定義一個ScrollView子類,重寫其 onInterceptTouchEvent()方法
public class NoNestedScrollview extends NestedScrollView { private int downX; private int downY; private int mTouchSlop; public NoNestedScrollview(Context context) { super(context); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } public NoNestedScrollview(Context context, AttributeSet attrs) { super(context, attrs); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } public NoNestedScrollview(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override public boolean onInterceptTouchEvent(MotionEvent e) { int action = e.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: downX = (int) e.getRawX(); downY = (int) e.getRawY(); break; case MotionEvent.ACTION_MOVE: //判斷是否滑動,若滑動就攔截事件 int moveY = (int) e.getRawY(); if (Math.abs(moveY - downY) > mTouchSlop) { return true; } break; default: break; } return super.onInterceptTouchEvent(e); } }
第二種解決方式
a.禁止RecyclerView滑動
recyclerView.setLayoutManager(new GridLayoutManager(mContext,2){ @Override public boolean canScrollVertically() { return false; } @Override public boolean canScrollHorizontally() { return super.canScrollHorizontally(); } }); recyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayout.VERTICAL,false){ @Override public boolean canScrollVertically() { return false; } });
b.重寫LayoutManager
代碼設(shè)置LayoutManager.setScrollEnabled(false);
public class ScrollLayoutManager extends LinearLayoutManager { private boolean isScrollEnable = true; public ScrollLayoutManager(Context context) { super(context); } public ScrollLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } public ScrollLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override public boolean canScrollVertically() { return isScrollEnable && super.canScrollVertically(); } /** * 設(shè)置 RecyclerView 是否可以垂直滑動 * @param isEnable */ public void setScrollEnable(boolean isEnable) { this.isScrollEnable = isEnable; } }
可能會出現(xiàn)的問題
雖然上面兩種方式解決了滑動沖突,但是有的手機上出現(xiàn)了RecyclerView會出現(xiàn)顯示不全的情況。
針對這種情形,使用網(wǎng)上的方法一種是使用 RelativeLayout 包裹 RecyclerView 并設(shè)置屬性:android:descendantFocusability="blocksDescendants"
android:descendantFocusability="blocksDescendants",該屬>性是當一個view 獲取焦點時,定義 ViewGroup 和其子控件直接的關(guān)系,常用來>解決父控件的焦點或者點擊事件被子空間獲取。
beforeDescendants: ViewGroup會優(yōu)先其子控件獲取焦點
afterDescendants: ViewGroup只有當其子控件不需要獲取焦點時才獲取焦點
blocksDescendants: ViewGroup會覆蓋子類控件而直接獲得焦點
相關(guān)代碼案例:https://github.com/yangchong2...
8.4 viewPager嵌套水平RecyclerView橫向滑動到底后不滑動ViewPager
繼承RecyclerView,重寫dispatchTouchEvent,根據(jù)ACTION_MOVE的方向判斷是否調(diào)用getParent().requestDisallowInterceptTouchEvent去阻止父view攔截點擊事件
@Override public boolean dispatchTouchEvent(MotionEvent ev) { /*---解決垂ViewPager嵌套直RecyclerView嵌套水平RecyclerView橫向滑動到底后不滑動ViewPager start ---*/ ViewParent parent = this; while(!((parent = parent.getParent()) instanceof ViewPager)); // 循環(huán)查找viewPager parent.requestDisallowInterceptTouchEvent(true); return super.dispatchTouchEvent(ev); }9.RecyclerView復(fù)雜布局封裝庫案例
開源項目庫的地址:https://github.com/yangchong2...
9.1 能夠?qū)崿F(xiàn)業(yè)務(wù)的需求和功能1.1 支持上拉加載,下拉刷新,可以自定義foot底部布局,支持添加多個自定義header頭部布局。
1.2 支持切換不同的狀態(tài),比如加載中[目前是ProgressBar,加載成功,加載失敗,加載錯誤等不同布局狀態(tài)。當然也可以自定義這些狀態(tài)的布局
1.3 支持復(fù)雜界面使用,比如有的頁面包含有輪播圖,按鈕組合,橫向滑動,還有復(fù)雜list,那么用這個控件就可以搞定。
1.4 已經(jīng)用于實際開發(fā)項目投資界,新芽,沙丘大學中……
1.5 輕量級側(cè)滑刪除菜單,直接嵌套item布局即可使用,使用十分簡單。
1.6 支持插入或者刪除某條數(shù)據(jù),支持CoordinatorLayout炫酷的效果
1.7 支持粘貼頭部的需求效果
1.8 RecyclerView實現(xiàn)條目Item拖拽排序與滑動刪除
9.2 具備的優(yōu)勢分析自定義支持上拉加載更多,下拉刷新,支持自由切換狀態(tài)【加載中,加載成功,加載失敗,沒網(wǎng)絡(luò)等狀態(tài)】的控件,拓展功能[支持長按拖拽,側(cè)滑刪除]可以選擇性添加 。具體使用方法,可以直接參考demo。
輕量級側(cè)滑刪除菜單,支持recyclerView,listView,直接嵌套item布局即可使用,整個側(cè)滑菜單思路是:跟隨手勢將item向左滑動
10.針對阿里VLayout代碼分析關(guān)于Vlayout的使用和相關(guān)介紹的博客有許多。具體可以看這篇博客:https://blog.csdn.net/m0_3770...
關(guān)于使用Vlayout實現(xiàn)復(fù)雜頁面的案例有:https://github.com/yangchong2...,https://github.com/yangchong211/YCVideoPlayer
實現(xiàn)的復(fù)雜界面效果展示:
v1.0.0 2016年5月5日
v1.1.0 更新于2017年2月1日
v1.1.1 更新于2017年6月9日
v2.0.0 更新于2018年9月26日
關(guān)于其他內(nèi)容介紹 01.關(guān)于博客匯總鏈接1.技術(shù)博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關(guān)于我的博客我的個人站點:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77341.html
摘要:為表示之前進行過滾動,為狀態(tài)表示滾動結(jié)束停下來的抽象方法抽象方法計算最終對齊要移動的距離計算二個參數(shù)對應(yīng)的當前的坐標與需要對齊的坐標之間的距離。抽象方法找到要對齊的該方法會找到當前上最接近對齊位置的那個,該稱為,對應(yīng)的稱為。 目錄介紹 01.SnapHelper簡單介紹 1.1 SnapHelper作用 1.2 SnapHelper類分析 1.3 LinearSnapHelper...
閱讀 4240·2021-09-26 10:17
閱讀 883·2021-09-22 15:02
閱讀 3470·2021-09-06 15:00
閱讀 1064·2021-07-25 16:52
閱讀 2747·2019-08-29 16:16
閱讀 2523·2019-08-29 13:25
閱讀 1598·2019-08-26 13:51
閱讀 2194·2019-08-26 10:58