国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

RecyclerView用法和源碼深度解析

ShowerSun / 1624人閱讀

摘要:此方法應由實現使用,以獲取視圖來表示來自的數據。如果適配器沒有指示給定位置上的數據已更改,則回收程序將嘗試發回一個以前為該數據初始化的報廢視圖,而不進行重新綁定。如果它只附加了一個適配器,并且新適配器使用與不同的,則將清除其緩存。

目錄介紹

1.RecycleView的結構

2.Adapter

2.1 RecyclerView.Adapter扮演的角色

2.2 重寫的方法

2.3 notifyDataSetChanged()刷新數據

2.4 數據變更通知之觀察者模式

a.首先看.notifyDataSetChanged()源碼

b.接著查看.notifyChanged()源碼

c.接著查看setAdapter()源碼中的setAdapterInternal(adapter, false, true)方法

d.notify……方法被調用,刷新數據

3.ViewHolder

3.1 ViewHolder的作用

3.2 ViewHolder與復用

3.3 ViewHolder簡單封裝

4.LayoutManager

4.1 作用

4.2 LayoutManager樣式

4.3 LayoutManager當前有且僅有一個抽象函數

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 觸發的三種事件

7.其他知識點

7.1 Recycler && RecycledViewPool

7.2 Recyclerview.getLayoutPosition()區別

8.RecyclerView嵌套方案滑動沖突解決方案

8.1 如何判斷RecyclerView控件滑動到頂部和底部

8.2 RecyclerView嵌套RecyclerView 條目自動上滾的Bug

8.3 ScrollView嵌套RecyclerView滑動沖突

8.4 ViewPager嵌套水平RecyclerView橫向滑動到底后不滑動ViewPager

9.RecyclerView復雜布局封裝庫案例

9.1 能夠實現業務的需求和功能

9.2 具備的優勢分析

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基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉載請注明出處,謝謝!

鏈接地址:https://github.com/yangchong2...

如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!

1.RecycleView的結構

關于RecyclerView,大家都已經很熟悉了,用途十分廣泛,大概結構如下所示

RecyclerView.Adapter - 處理數據集合并負責綁定視圖

ViewHolder - 持有所有的用于綁定數據或者需要操作的View

LayoutManager - 負責擺放視圖等相關操作

ItemDecoration - 負責繪制Item附近的分割線

ItemAnimator - 為Item的一般操作添加動畫效果,如,增刪條目等

如圖所示,直觀展示結構

針對上面幾個屬性,最簡單用法如下所示

recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//設置layoutManager
recyclerView.setLayoutManager(layoutManager);
final RecycleViewItemLine line = new RecycleViewItemLine(this, LinearLayout.HORIZONTAL,1,this.getResources().getColor(R.color.colorAccent));
//設置添加分割線
recyclerView.addItemDecoration(line);
adapter = new MultipleItemAdapter(this);
//設置adapter
recyclerView.setAdapter(adapter);
//添加數據并且刷新adapter
adapter.addAll(list);
adapter.notifyDataSetChanged();


//adapter
//onCreateViewHolder(ViewGroup parent, int viewType)這里的第二個參數就是View的類型,可以根據這個類型判斷去創建不同item的ViewHolder
public class MultipleItemAdapter extends 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);
        }
    }
}

2.Adapter 2.1 RecyclerView.Adapter扮演的角色

一是,根據不同ViewType創建與之相應的的Item-Layout

二是,訪問數據集合并將數據綁定到正確的View上

2.2 重寫的方法

一般常用的重寫方法有以下這么幾個:

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參數)是哪種類型的布局

2.3 notifyDataSetChanged()刷新數據

當時據集合發生改變時,我們通過調用.notifyDataSetChanged(),來刷新列表,因為這樣做會觸發列表的重繪,所以并不會出現任何動畫效果,因此需要調用一些以notifyItem*()作為前綴的特殊方法,比如:

public final void notifyItemInserted(int position) 向指定位置插入Item

public final void notifyItemRemoved(int position) 移除指定位置Item

public final void notifyItemChanged(int position) 更新指定位置Item

2.4 數據變更通知之觀察者模式

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,內部持有觀察者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內部的被觀察者會遍歷通知已經注冊的觀察者的對應方法,這時界面就會響應變更。

3.ViewHolder 3.1 ViewHolder的作用

ViewHolder作用大概有這些:

adapter應當擁有ViewHolder的子類,并且ViewHolder內部應當存儲一些子view,避免時間代價很大的findViewById操作

其RecyclerView內部定義的ViewHolder類包含很多復雜的屬性,內部使用場景也有很多,而我們經常使用的也就是onCreateViewHolder()方法和onBindViewHolder()方法,onCreateViewHolder()方法在RecyclerView需要一個新類型。item的ViewHolder時調用來創建一個ViewHolder,而onBindViewHolder()方法則當RecyclerView需要在特定位置的item展示數據時調用。

3.2 ViewHolder與復用

在復寫RecyclerView.Adapter的時候,需要我們復寫兩個方法:

onCreateViewHolder

onBindViewHolder

這兩個方法從字面上看就是創建ViewHolder和綁定ViewHolder的意思

復用機制是怎樣的?

模擬場景:只有一種ViewType,上下滑動的時候需要的ViewHolder種類是只有一種,但是需要的ViewHolder對象數量并不止一個。所以在后面創建了5個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) {
    //代碼省略了,有需要的小伙伴可以自己看看,這里面邏輯實在太復雜呢
}

3.3 ViewHolder簡單封裝

關于ViewHolder簡單的封裝代碼如下所示:

public abstract class BaseMViewHolder extends RecyclerView.ViewHolder {


    // SparseArray 比 HashMap 更省內存,在某些條件下性能更好,只能存儲 key 為 int 類型的數據,
    // 用來存放 View 以減少 findViewById 的次數

    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<>();
        }
    }

    /**
     * 子類設置數據方法

     */
    public void setData(M data) {}

    /**
     * 第二種findViewById方式
     * 根據 ID 來獲取 View
     * @param viewId viewID
     * @param     泛型
     * @return 將結果強轉為 View 或 View 的子類型
     */
    @SuppressWarnings("unchecked")
    protected  T getView(int viewId) {
        // 先從緩存中找,找打的話則直接返回
        // 如果找不到則 findViewById ,再把結果存入緩存中
        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();
    }


    /**
     * 獲取數據索引的位置
     * @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());
                        }
                    }
                }
            });
        }
    }

    //省略部分代碼
    //關于adapter封裝可以查看我的開源adpater封裝庫:https://github.com/yangchong211/YCBaseAdapter
    //關于recyclerView封裝庫,可以查看我的開源庫:https://github.com/yangchong211/YCRefreshView
}
```

4.LayoutManager 4.1 作用

LayoutManager的職責是擺放Item的位置,并且負責決定何時回收和重用Item。

RecyclerView 允許自定義規則去放置子 view,這個規則的控制者就是 LayoutManager。一個 RecyclerView 如果想展示內容,就必須設置一個 LayoutManager

4.2 LayoutManager樣式

LinearLayoutManager 水平或者垂直的Item視圖。

GridLayoutManager 網格Item視圖。

StaggeredGridLayoutManager 交錯的網格Item視圖。

4.3 LayoutManager當前有且僅有一個抽象函數

具體如下:

public LayoutParams generateDefaultLayoutParams()

4.4 setLayoutManager(LayoutManager layout)源碼

a.setLayoutManager入口源碼

分析:當之前設置過 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();
}

5.ItemDecoration 5.1 作用

通過設置recyclerView.addItemDecoration(new DividerDecoration(this));來改變Item之間的偏移量或者對Item進行裝飾。

當然,你也可以對RecyclerView設置多個ItemDecoration,列表展示的時候會遍歷所有的ItemDecoration并調用里面的繪制方法,對Item進行裝飾。

5.2 RecyclerView.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的正確尺寸并設置偏移量。

5.3 .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();
}

b.接著看下markItemDecorInsetsDirty這個方法做了些什么

這個方法先獲取所有子View的數量,然后遍歷了 RecyclerView 和 LayoutManager 的所有子 View,再將其子 View 的 LayoutParams 中的 mInsetsDirty 屬性置為 true,最后調用了 mRecycler.markItemDecorInsetsDirty()方法處理復用邏輯。

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;
    }
    //調用了 mRecycler.markItemDecorInsetsDirty(),
    //Recycler 是 RecyclerView 的一個內部類,就是它管理著 RecyclerView 的復用邏輯
    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,然后重新調用 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);
    }
     //省略部分代碼
}

總結概括

可以看到在 View 的以上兩個方法中,分別調用了 ItemDecoration 對象的 onDraw onDrawOver 方法。

這兩個抽象方法,由我們繼承 ItemDecoration 來自己實現,他們區別就是 onDraw 在 item view 繪制之前調用,onDrawOver 在 item view 繪制之后調用。

所以繪制順序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。

6.ItemAnimator 6.1 作用

ItemAnimator能夠幫助Item實現獨立的動畫

6.2 觸發的三種事件

某條數據被插入到數據集合中

從數據集合中移除某條數據

更改數據集合中的某條數據

7.其他知識點 7.1 Recycler && RecycledViewPool

RecycledViewPool

RecyclerViewPool用于多個RecyclerView之間共享View。只需要創建一個RecyclerViewPool實例,然后調用RecyclerView的setRecycledViewPool(RecycledViewPool)方法即可。RecyclerView默認會創建一個RecyclerViewPool實例。

下列源碼,是我借助于有道詞典翻譯部分注釋內容……

看出mScrap是一個的映射,mMaxScrap是一個的映射,這兩個成員變量代表可復用View池的基本信息。調用setMaxRecycledViews(int viewType, int max)時,當用于復用的mScrap中viewType對應的ViewHolder個數超過maxNum時,會從列表末尾開始丟棄超過的部分。調用getRecycledView(int viewType)方法時從mScrap中移除并返回viewType對應的List的末尾項

public static class RecycledViewPool {
    private static final int DEFAULT_MAX_SCRAP = 5;
    static class ScrapData {
        final ArrayList mScrapHeap = 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();
        }
    }

    //設置丟棄前要在池中持有的視圖持有人的最大數量
    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所持有的當前視圖數
    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;
    }

    //池持有的視圖持有者總數
    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類型的池已經滿了,它將立即被丟棄。
    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是一個由開發者控制的可以作為View緩存的幫助類。調用Recycler.getViewForPosition(int)方法獲取View時,Recycler先檢查attachedscrap和一級緩存,如果沒有則檢查ViewCacheExtension.getViewForPositionAndType(Recycler, int, int),如果沒有則檢查RecyclerViewPool。注意:Recycler不會在這個類中做緩存View的操作,是否緩存View完全由開發者控制。

public abstract static class ViewCacheExtension {
    abstract public View getViewForPositionAndType(Recycler recycler, int position, int type);
}

Recycler

后續再深入分析

7.2 Recyclerview.getLayoutPosition()問題

在RecycleView中的相關方法中,有兩種類型的位置

布局位置:從LayoutManager的角度看,條目在最新布局計算中的位置。

返回布局位置的方法使用最近一次布局運算后的位置,如getLayoutPosition()和findViewHolderForLayoutPosition(int)。這些位置包含了最近一次布局運算后的變化。你可以根據這些位置來與用戶正在屏幕上看到的保持一致。比如,你有一個條目列表,當用戶請求第5個條目時,你可以使用這些方法來匹配用戶看到的。

適配器位置:從適配器的角度看,條目在是適配器中的位置。

另外一系列方法與AdapterPosition關聯,比如getAdapterPosition()和findViewHolderForAdapterPosition(int)。當你想獲得條目在更新后的適配器中的位置使用這些方法,即使這些位置變化還沒反映到布局中。比如,你想訪問適配器中條目的位置時,就應該使用getAdapterPosition()。注意,notifyDataSetChanged()已經被調用而且還沒計算新布局,這些方法或許不能夠計算適配器位置。所以,你要小心處理這些方法返回NO_POSITION和null的情況。

注意: 這兩種類型的位置是等同的,除非在分發adapter.notify*事件和更新布局時。

關于兩者的區別

網上查了一些資料,發現相關內容很少,最后在stackoverflow上終于看到有大神這樣解釋兩者的區別

具體區別就是adapter和layout的位置會有時間差(<16ms), 如果你改變了Adapter的數據然后刷新視圖, layout需要過一段時間才會更新視圖, 在這段時間里面, 這兩個方法返回的position會不一樣。

在notifyDataSetChanged之后并不能馬上獲取Adapter中的position, 要等布局結束之后才能獲取到

在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;
}

可能會導致的錯誤

這種情況有點難以復現,在 ViewHolder 中處理 item 的點擊事件的時候,發現多個 item 同時點擊就會出現閃退,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();
                //如果滑動到了最底部,就允許繼續向上滑動加載下一頁,否者不允許
                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 進入頁面自動跳轉到RecyclerViewB上面頁面會自動滾動。

兩種解決辦法

一,recyclerview去除焦點

recyclerview.setFocusableInTouchMode(false);

recyclerview.requestFocus();

二,在代碼里面 讓處于ScrollView或者RecyclerView1 頂端的某個控件獲得焦點即可

比如頂部的一個textview

tv.setFocusableInTouchMode(true);

tv.requestFocus();

8.3 ScrollView嵌套RecyclerView滑動沖突

第一種方式:

重寫父控件,讓父控件 ScrollView 直接攔截滑動事件,不向下分發給 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

代碼設置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();
    }
     
    /**
     * 設置 RecyclerView 是否可以垂直滑動
     * @param isEnable
     */
    public void setScrollEnable(boolean isEnable) {
        this.isScrollEnable = isEnable;
    }
}

可能會出現的問題

雖然上面兩種方式解決了滑動沖突,但是有的手機上出現了RecyclerView會出現顯示不全的情況。

針對這種情形,使用網上的方法一種是使用 RelativeLayout 包裹 RecyclerView 并設置屬性:android:descendantFocusability="blocksDescendants"

android:descendantFocusability="blocksDescendants",該屬>性是當一個view 獲取焦點時,定義 ViewGroup 和其子控件直接的關系,常用來>解決父控件的焦點或者點擊事件被子空間獲取。

beforeDescendants: ViewGroup會優先其子控件獲取焦點

afterDescendants: ViewGroup只有當其子控件不需要獲取焦點時才獲取焦點

blocksDescendants: ViewGroup會覆蓋子類控件而直接獲得焦點

相關代碼案例:https://github.com/yangchong2...


    

8.4 viewPager嵌套水平RecyclerView橫向滑動到底后不滑動ViewPager

繼承RecyclerView,重寫dispatchTouchEvent,根據ACTION_MOVE的方向判斷是否調用getParent().requestDisallowInterceptTouchEvent去阻止父view攔截點擊事件

@Override 
public boolean dispatchTouchEvent(MotionEvent ev) { 
    /*---解決垂ViewPager嵌套直RecyclerView嵌套水平RecyclerView橫向滑動到底后不滑動ViewPager start ---*/ 
    ViewParent parent = this; 
    while(!((parent = parent.getParent()) instanceof ViewPager));
    // 循環查找viewPager 
    parent.requestDisallowInterceptTouchEvent(true); 
    return super.dispatchTouchEvent(ev); 
}

9.RecyclerView復雜布局封裝庫案例

開源項目庫的地址:https://github.com/yangchong2...

9.1 能夠實現業務的需求和功能

1.1 支持上拉加載,下拉刷新,可以自定義foot底部布局,支持添加多個自定義header頭部布局。

1.2 支持切換不同的狀態,比如加載中[目前是ProgressBar,加載成功,加載失敗,加載錯誤等不同布局狀態。當然也可以自定義這些狀態的布局

1.3 支持復雜界面使用,比如有的頁面包含有輪播圖,按鈕組合,橫向滑動,還有復雜list,那么用這個控件就可以搞定。

1.4 已經用于實際開發項目投資界,新芽,沙丘大學中……

1.5 輕量級側滑刪除菜單,直接嵌套item布局即可使用,使用十分簡單。

1.6 支持插入或者刪除某條數據,支持CoordinatorLayout炫酷的效果

1.7 支持粘貼頭部的需求效果

1.8 RecyclerView實現條目Item拖拽排序與滑動刪除

9.2 具備的優勢分析

自定義支持上拉加載更多,下拉刷新,支持自由切換狀態【加載中,加載成功,加載失敗,沒網絡等狀態】的控件,拓展功能[支持長按拖拽,側滑刪除]可以選擇性添加 。具體使用方法,可以直接參考demo。

輕量級側滑刪除菜單,支持recyclerView,listView,直接嵌套item布局即可使用,整個側滑菜單思路是:跟隨手勢將item向左滑動

10.針對阿里VLayout代碼分析

關于Vlayout的使用和相關介紹的博客有許多。具體可以看這篇博客:https://blog.csdn.net/m0_3770...

關于使用Vlayout實現復雜頁面的案例有:https://github.com/yangchong2...,https://github.com/yangchong211/YCVideoPlayer

實現的復雜界面效果展示:


11.版本更新說明

v1.0.0 2016年5月5日

v1.1.0 更新于2017年2月1日

v1.1.1 更新于2017年6月9日

v2.0.0 更新于2018年9月26日

關于其他內容介紹 01.關于博客匯總鏈接

1.技術博客匯總

2.開源項目匯總

3.生活博客匯總

4.喜馬拉雅音頻匯總

5.其他匯總

02.關于我的博客

我的個人站點: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...

泡在網上的日子:http://www.jcodecraeer.com/me...

郵箱:yangchong211@163.com

阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV

segmentfault頭條:https://segmentfault.com/u/xi...

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77341.html

相關文章

  • SnapHelper源碼深度解析

    摘要:為表示之前進行過滾動,為狀態表示滾動結束停下來的抽象方法抽象方法計算最終對齊要移動的距離計算二個參數對應的當前的坐標與需要對齊的坐標之間的距離。抽象方法找到要對齊的該方法會找到當前上最接近對齊位置的那個,該稱為,對應的稱為。 目錄介紹 01.SnapHelper簡單介紹 1.1 SnapHelper作用 1.2 SnapHelper類分析 1.3 LinearSnapHelper...

    ThinkSNS 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<