自定義LayoutManager之復(fù)用與回收一

轉(zhuǎn)自RecyclerView系列之四實現(xiàn)回收復(fù)用
在上面文章自定義LayoutManager中講解了LayoutManager的自定義,實現(xiàn)了界面的展示和滑動剪撬。熟悉RecyclerView的都知道其緩存機制四瘫,在添加緩存的回收與復(fù)用之前,先簡單介紹下RecyclerView的緩存機制。

1逞泄、RecyclerView的回收復(fù)用原理

1.1铛漓、RecyclerView的回收

  • 在滑動過程中,RecyclerView會將即將與之分離的ViewHolder放到mCachedViews和mRecyclerPool砸民,可使用removeAndRecycleView(View child, Recycler recycler)函數(shù)進行回收抵怎。
    這兩級緩存的區(qū)別是:mCachedViews是第一級緩存奋救,大小默認(rèn)為2,數(shù)據(jù)結(jié)構(gòu)是ArrayList反惕。當(dāng)其中數(shù)量超過2時尝艘,會根據(jù)FIFO的原則移除元素,并將移除的元素添加到mRecyclerPool中姿染。
    mRecyclerPool是一個緩存池背亥,本質(zhì)上它是SparseArray,key是itemViewType悬赏,value是一個ArrayList狡汉,value的大小默認(rèn)為5。

  • 除了上面說到兩級緩存還有mAttachedScrap闽颇,在onLayoutChildren中會調(diào)用函數(shù)detachAndScrapAttachedViews(recycler);將屏幕上ViewHolder進行detach盾戴,并暫存到mAttachedScrap,再重新布局時從mAttachedScrap中取出兵多,attach到RecyclerView上尖啡。

1.2、RecyclerView的復(fù)用

通過View view = recycler.getViewForPosition(position)可以實現(xiàn)復(fù)用剩膘,根據(jù)源碼可知衅斩,在RecyclerView中,總共有四級緩存怠褐,優(yōu)先級:mAttachedScrap>mCachedViews>mViewCacheExtension>mRecyclerPool畏梆。

  • mAttachedScrap:只保存當(dāng)前屏幕中detach的ViewHolder,在重新布局時復(fù)用。
  • mCachedViews:緩存的是剛從RecyclerView中移除的ViewHolder(通過removeAndRecycleView(view, recycler)方法)惫搏,在復(fù)用時需要position或id匹配才能復(fù)用具温,所以只有在來回滑動過程中才會復(fù)用mCachedViews中的ViewHolder。如果不能匹配就需要從mRecyclerPool中取出ViewHolder并重新綁定數(shù)據(jù)筐赔。
  • 復(fù)用mAttachedScrap铣猩、mCachedViews中的ViewHolder是需要精確匹配的,如果能匹配上可直接使用不需綁定數(shù)據(jù)茴丰,如果不能精確匹配达皿,即使mAttachedScrap、mCachedViews中有緩存也不能取出使用贿肩,只能從mRecyclerPool中取出使用峦椰,并且需重綁數(shù)據(jù)。如果mRecyclerPool中沒有緩存就需要調(diào)用onCreateViewHolder進行創(chuàng)建汰规。

2汤功、幾個函數(shù)

  • public void detachAndScrapAttachedViews(Recycler recycler)
    僅用于onLayoutChildren中,在布局前將屏幕上的ViewHolder從RecyclerView中detach掉溜哮,將其放在mAttachedScrap中滔金,以供重新布局時使用色解。

  • View view = recycler.getViewForPosition(position)
    當(dāng)我們需要填充布局時,就可以調(diào)用該方法餐茵,從四個緩存容器中取出合適的View,然后添加到RecyclerView中科阎。

  • removeAndRecycleView(child, recycler)
    該函數(shù)僅用于在滑動過程中,在滾動時忿族,將滾出屏幕的ViewHolder進行remove并添加到mCachedViews或mRecyclerPool中锣笨。
    可以看到,正是這三個函數(shù)的使用道批,可以讓我們自定義的LayoutManager具有復(fù)用功能错英。
    另外,還有幾個常用隆豹,但經(jīng)常出錯的函數(shù):

  • int getItemCount()
    得到的是Adapter中總共有多少數(shù)據(jù)要顯示走趋,也就是總共有多少個item

  • int getChildCount()
    得到的是當(dāng)前RecyclerView在顯示的item的個數(shù),所以這就是getChildCount()與 getItemCount()的區(qū)別

  • View getChildAt(int position)
    獲取某個可見位置的View噪伊,需要非常注意的是,它的位置索引并不是Adapter中的位置索引氮唯,而是當(dāng)前在屏幕上的位置的索引鉴吹。也就是說,要獲取當(dāng)前屏幕上在顯示的第一個item的View,應(yīng)該用getChidAt(0)惩琉,同樣豆励,如果要得到當(dāng)前屏幕上在顯示的最后一個item的View,應(yīng)該用getChildAt(getChildCount()-1)

  • int getPosition(View view)
    這個函數(shù)用于得到某個View在Adapter中的索引位置瞒渠,我們經(jīng)常將它與getChildAt(int position)聯(lián)合使用良蒸,得到某個當(dāng)前屏幕上在顯示的View在Adapter中的位置。

3伍玖、自定義LayoutManager的回收和復(fù)用原理

從上面的原理中可以看到嫩痰,回收復(fù)用主要有兩部分:
第一:在onLayoutChildren初始布局時:

  • 1、在布局前調(diào)用detachAndScrapAttachedViews(recycler)將所有可見的ViewHolder detach窍箍。
  • 2串纺、通過調(diào)用recycler.getViewForPosition(position)申請一個View,并添加到RecyclerView中椰棘,直到填充滿整個屏幕纺棺。

第二:在scrollVerticallyBy滑動時

  • 1、判斷滾動dy后邪狞,那些ViewHolder需要回收祷蝌,然后調(diào)用removeAndRecycleView(child, recycler)進行回收。
  • 2帆卓、然后通過調(diào)用recycler.getViewForPosition(position)獲取View巨朦,填充空白區(qū)域米丘。

4、為自定義LayoutManager添加回收復(fù)用

4.1罪郊、修改onLayoutChildren

上面已經(jīng)提到蠕蚜,在onLayoutChildren中,我們主要做兩件事:

  • 1悔橄、在布局前調(diào)用detachAndScrapAttachedViews(recycler)將所有可見的ViewHolder detach靶累。
  • 2、通過調(diào)用recycler.getViewForPosition(position)申請一個View癣疟,并添加到RecyclerView中挣柬,直到填充滿整個屏幕。

關(guān)鍵就在于如何判斷一屏能顯示多少個item睛挚,在這里每個item高度相同邪蛔,所以可以通過RecyclerView的高度處于item的高度即可

private int mItemWidth,mItemHeight;
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getItemCount() == 0) {//沒有Item,界面空著吧
        detachAndScrapAttachedViews(recycler);
        return;
    }
    detachAndScrapAttachedViews(recycler);

    View childView = recycler.getViewForPosition(0);
    measureChildWithMargins(childView, 0, 0);
    mItemWidth = getDecoratedMeasuredWidth(childView);
    mItemHeight = getDecoratedMeasuredHeight(childView);

    int visiableCount = (int) Math.ceil(getVerticalSpace() * 1.0f / mItemHeight);
if (visiableCount > itemCount)
            visiableCount = itemCount;
    …………
}       
//其中 getVerticalSpace()在上面已經(jīng)提到扎狱,得到的是RecyclerView用于顯示的高度侧到,它的定義是:
private int getVerticalSpace() {
    return getHeight() - getPaddingBottom() - getPaddingTop();
}

一屏可見的item個數(shù)=(int) Math.ceil(getVerticalSpace() * 1.0f / mItemHeight);,這里使用Math.ceil進行向上取整的原因就是:如果一屏內(nèi)可顯示1.5個item淤击,此時可見的item應(yīng)該為2才對匠抗。

除此之外,由于item高度相同污抬,為了布局方便汞贸,我們在初始化時,利用一個變量來保存在初始化時印机,在Adapter中每一個item的位置:

int offsetY = 0;
for (int i = 0; i < getItemCount(); i++) {
    Rect rect = new Rect(0, offsetY, mItemWidth, offsetY + mItemHeight);
    mItemRects.put(i, rect);
    offsetY += mItemHeight;
}

接下來布局可見的item矢腻,不可見的item不再布局

for (int i = 0; i < visibleCount; i++) {
    Rect rect = mItemRects.get(i);
    View view = recycler.getViewForPosition(i);
    addView(view);
    //addView后一定要measure,先measure再layout
    measureChildWithMargins(view, 0, 0);
    layoutDecorated(view, rect.left, rect.top, rect.right, rect.bottom);
}

mTotalHeight = Math.max(offsetY, getVerticalVisibleHeight());

因為射赛,在上面我們已經(jīng)從保存了初始化狀態(tài)下每個Item的位置多柑,所以在初始化時,直接從mItemRects中取出當(dāng)前要顯示的Item的位置楣责,直接將它擺放在這個位置就可以了顷蟆。需要注意的是,因為我們在之前已經(jīng)使用detachAndScrapAttachedViews(recycler);將所有view從RecyclerView中剝離腐魂,所以帐偎,我們需要重新通過addView(view)添加進來。在添加進來以后蛔屹,需要走一個這個View的測量和layout邏輯削樊,先經(jīng)過測量,再將它layout到指定位置。如果我們沒有測量直接layout漫贞,會什么都出不來甸箱,因為任何view的layout都是依賴measure出來的位置信息的。

到此迅脐,完整的onLayoutChildren的代碼如下:

private int mItemWidth, mItemHeight;
private SparseArray<Rect> mItemRects = new SparseArray<>();;
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getItemCount() == 0) {//沒有Item芍殖,界面空著吧
        detachAndScrapAttachedViews(recycler);
        return;
    }
    detachAndScrapAttachedViews(recycler);

    //將item的位置存儲起來
    View childView = recycler.getViewForPosition(0);
    measureChildWithMargins(childView, 0, 0);
    mItemWidth = getDecoratedMeasuredWidth(childView);
    mItemHeight = getDecoratedMeasuredHeight(childView);

    int visiableCount = (int) Math.ceil(getVerticalSpace() * 1.0f / mItemHeight);
    if (visiableCount > itemCount)
        visiableCount = itemCount;


    //定義豎直方向的偏移量
    int offsetY = 0;

    for (int i = 0; i < getItemCount(); i++) {
        Rect rect = new Rect(0, offsetY, mItemWidth, offsetY + mItemHeight);
        mItemRects.put(i, rect);
        offsetY += mItemHeight;
    }


    for (int i = 0; i < visibleCount; i++) {
        Rect rect = mItemRects.get(i);
        View view = recycler.getViewForPosition(i);
        addView(view);
        //addView后一定要measure,先measure再layout
        measureChildWithMargins(view, 0, 0);
        layoutDecorated(view, rect.left, rect.top, rect.right, rect.bottom);
    }

    //如果所有子View的高度和沒有填滿RecyclerView的高度谴蔑,
    // 則將高度設(shè)置為RecyclerView的高度
    mTotalHeight = Math.max(offsetY, getVerticalSpace());
}

4.2豌骏、處理滾動

經(jīng)過上面的分析可知,我們可知隐锭,我們首先回收滾出屏幕的ViewHolder窃躲,然后再填充滾動后的空白區(qū)域。向上滾動和向下滾動雖然都是回收滾出屏幕的ViewHolder钦睡,但是處理邏輯還是有區(qū)別的蒂窒,下面就按照滾動方向分為兩種情況進行分析。

4.2.1荞怒、處理向上滾動

向上滾動時dy>0洒琢,這里先假設(shè)向上滾動dy,然后判斷哪些ViewHolder需要回收褐桌,需要新增哪些item纬凤,然后再執(zhí)行offsetChildrenVertical(-travel)進行滑動。

因為在開始移動之前撩嚼,我們對dy做了到頂、到底的邊界判斷并進行了修正挖帘。

int travel = dy;
//如果滑動到最頂部
if (mSumDy + dy < 0) {
    travel = -mSumDy;
} else if (mSumDy + dy > mTotalHeight - getVerticalSpace()) {
    //如果滑動到最底部
    travel = mTotalHeight - getVerticalSpace() - mSumDy;
}

所以真正移動的距離完丽,是修正后的travel。所以在進行處理回收和填充item拇舀,應(yīng)該以travel進行判斷逻族。

1、判斷回收item

在判斷向上滑動回收哪些item時骄崩,應(yīng)該遍歷當(dāng)前屏幕所有可見的item聘鳞,假設(shè)讓它們向上滑動travel,然后判斷是否已經(jīng)超出了上邊界(y=0)要拂,如果超出就進行回收抠璃。

for (int i = getChildCount() - 1; i >= 0; i--) {
    View child = getChildAt(i);
    if (travel > 0) {//需要回收當(dāng)前屏幕,上越界的View
        if (getDecoratedBottom(child) - travel< 0) {
            removeAndRecycleView(child, recycler);
        }
    }
}

在上面代碼中:

  • 首先遍歷屏幕上所有可見的item脱惰,這里getChildCount()-1表示屏幕上可見的最后一個item搏嗡,注意和getItemCount()的區(qū)別。
  • 由于是獲取屏幕上可見的item,所以可以調(diào)用getChildAt(i)直接獲取采盒。開始我不注意使用了recycler.getViewForPosition(i)去獲取了旧乞,這是從四個緩存池中獲取,很明顯不對磅氨。
  • getDecoratedBottom(child) - travel< 0表示向上移動travel后超出了上邊界尺栖,故對進入該判斷的item進行回收。
  • 注意這里使用removeAndRecycleView(child, recycler)方法進行回收烦租,而不是detachAndScrapAttachedViews(recycler)方法延赌。在滾動時,滾出屏幕的ViewHolder應(yīng)該remove掉左权,而不是detach掉皮胡。在onLayoutChildren中進行布局時,需要暫存屏幕上的ViewHolder赏迟,在再次布局時使用屡贺,此時就需要使用detachAndScrapAttachedViews(recycler)方法。
2锌杀、填充空白區(qū)域

假設(shè)向上滾動了travel后甩栈,屏幕的位置如下圖,左邊是初始狀態(tài)糕再,右邊是移動后的情況量没,其中綠色框表示屏幕。其實RecyclerView的滑動只是其中的內(nèi)容在滑動突想,這里假設(shè)內(nèi)容的位置不動殴蹄,那么屏幕相對于內(nèi)容就發(fā)生滑動。

image.png

在滾動travel后猾担,屏幕此時所在的區(qū)域如下:

private Rect getVisibleArea(int travel) {
    Rect result = new Rect(getPaddingLeft(), getPaddingTop() + mSumDy + travel, getWidth() - getPaddingRight(), getHeight()-getPaddingBottom() + mSumDy + travel);
    return result;
}

mSumDy表示已經(jīng)滑動的距離袭灯,travel表示即將滑動的距離。所以mSumDy + travel表示此時滑動后绑嘹,屏幕的位置稽荧。
由于在onLayoutChildren中初始化布局時,已經(jīng)記錄每個item的初始位置工腋,在拿到屏幕移動后的位置后姨丈,只需要和初始化item的位置進行比對,如果存在交集就表示在屏幕內(nèi)擅腰,否則表示已滑出了屏幕蟋恬。

分析到這里,我們還是不知道哪些item要滑入屏幕趁冈,再回看下上圖不難看出筋现,滑入屏幕的item無非就是在當(dāng)前屏幕中可見的最后一個item的下一個item一直到第itemCount個item中一些item將要滑入屏幕。

Rect visibleRect = getVisibleArea(travel);
//布局子View階段
if (travel >= 0) {
    View lastView = getChildAt(getChildCount() - 1);
    int minPos = getPosition(lastView) + 1;//從最后一個View+1開始吧\

    //順序addChildView
    for (int i = minPos; i <= getItemCount() - 1; i++) {
        Rect rect = mItemRects.get(i);
        if (Rect.intersects(visibleRect, rect)) {
            View child = recycler.getViewForPosition(i);
            addView(child);
            measureChildWithMargins(child, 0, 0);
            layoutDecorated(child, rect.left, rect.top - mSumDy, rect.right, rect.bottom - mSumDy);
        } else {
            break;
        }
    }
}

mSumDy += travel;
// 平移容器內(nèi)的item
offsetChildrenVertical(-travel);

我們來看看上面代碼:
首先獲取滑動后的屏幕的位置

Rect visibleRect = getVisibleArea(travel);

然后,找到移動前最后一個可見的View

View lastView = getChildAt(getChildCount() - 1);

然后矾飞,找到它之后的一個item:

int minPos = getPosition(lastView) + 1;

然后從這個item開始查詢一膨,看它和它之后的每個item是不是都在可見區(qū)域內(nèi),之后就是判斷這個item是不是在顯示區(qū)域,如果在就加進來并且布局洒沦,如果不在就退出循環(huán):

for (int i = minPos; i <= getItemCount() - 1; i++) {
    Rect rect = mItemRects.get(i);
    if (Rect.intersects(visibleRect, rect)) {
        View child = recycler.getViewForPosition(i);
        addView(child);
        measureChildWithMargins(child, 0, 0);
        layoutDecorated(child, rect.left, rect.top - mSumDy, rect.right, rect.bottom - mSumDy);
    } else {
        break;
    }
}

需要注意的是:mItemRects中記錄的是item的位置是參考屏幕(0,0)豹绪,在向上滾動時,我們需要把高度減去滑動的距離申眼,這樣才能實現(xiàn)滾入屏幕瞒津。注意這個滑動距離并不包括即將滑動的距離travel,雖然我們判斷哪些item是新增顯示時括尸,假設(shè)移動了travel巷蚪,其實到目前為止并沒有發(fā)生滾動。所以我們在布局時濒翻,仍然需要按上次的移動距離來進行布局屁柏,所以這里在布局時使用是layoutDecorated(child, rect.left, rect.top - mSumDy, rect.right, rect.bottom - mSumDy),單純只是減去了mSumDy,并沒有同時減去mSumDy和travel,最后才調(diào)用offsetChildrenVertical(-travel)來整體移動布局好的item有送。這時才會把我們剛才新增布局上的item顯示出來淌喻。
所以,此時完整的scrollVerticallyBy的代碼如下:

public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getChildCount() <= 0) {
        return dy;
    }

    int travel = dy;
    //如果滑動到最頂部
    if (mSumDy + dy < 0) {
        travel = -mSumDy;
    } else if (mSumDy + dy > mTotalHeight - getVerticalSpace()) {
        //如果滑動到最底部
        travel = mTotalHeight - getVerticalSpace() - mSumDy;
    }

    //回收越界子View
    for (int i = getChildCount() - 1; i >= 0; i--) {
        View child = getChildAt(i);
        if (travel > 0) {//需要回收當(dāng)前屏幕雀摘,上越界的View
            if (getDecoratedBottom(child) - travel < 0) {
                removeAndRecycleView(child, recycler);
            }
        }
    }
    
    Rect visibleRect = getVisibleArea(travel);
    //布局子View階段
    if (travel >= 0) {
        View lastView = getChildAt(getChildCount() - 1);
        int minPos = getPosition(lastView) + 1;//從最后一個View+1開始吧

        //順序addChildView
        for (int i = minPos; i <= getItemCount() - 1; i++) {
            Rect rect = mItemRects.get(i);
            if (Rect.intersects(visibleRect, rect)) {
                View child = recycler.getViewForPosition(i);
                addView(child);
                measureChildWithMargins(child, 0, 0);
                layoutDecorated(child, rect.left, rect.top - mSumDy, rect.right, rect.bottom - mSumDy);
            } else {
                break;
            }
        }
    }

    mSumDy += travel;
    // 平移容器內(nèi)的item
    offsetChildrenVertical(-travel);
    return travel;
}

此時已經(jīng)實現(xiàn)了向上滾動的功能裸删,并在向上滾動過程中,回收滑動屏幕的ViewHolder阵赠。

4.2.2涯塔、處理向下滾動

在分析完向上滾動的處理后,向下滾動的處理就很簡單了清蚀,和向上滾動是完全相反的匕荸。

1、判斷回收item

遍歷當(dāng)前屏幕可見的item轧铁,假設(shè)向下移動travel后,判斷哪些item滑出了屏幕的底部旦棉,回收滑出的item即可齿风。

for (int i = getChildCount() - 1; i >= 0; i--) {
    View child = getChildAt(i);
    if (travel > 0) {//需要回收當(dāng)前屏幕,上越界的View
        …………
    }else if (travel < 0) {//回收當(dāng)前屏幕绑洛,下越界的View
        if (getDecoratedTop(child) - travel > getHeight() - getPaddingBottom()) {
            removeAndRecycleView(child, recycler);
        }
    }
}

getDecoratedTop(child) - travel得到移動travel距離后item的頂部位置救斑,然后判斷是否大于屏幕底部的位置getHeight() - getPaddingBottom(),若大于則表示滑動了屏幕真屯。

2脸候、為滾動后的空白處填充Item

向下滾動,RecyclerView的頭部位置滾動后會有空白,故可以從當(dāng)前屏幕可見的第一個item的上一個開始遍歷运沦,到第0個item結(jié)束泵额,判斷哪些item在屏幕內(nèi),將在屏幕內(nèi)的item添加進來携添。

Rect visibleRect = getVisibleArea(travel);
//布局子View階段
if (travel >= 0) {
    …………
} else {
    View firstView = getChildAt(0);
    int maxPos = getPosition(firstView) - 1;

    for (int i = maxPos; i >= 0; i--) {
        Rect rect = mItemRects.get(i);
        if (Rect.intersects(visibleRect, rect)) {
            View child = recycler.getViewForPosition(i);
            addView(child, 0);
            measureChildWithMargins(child, 0, 0);
            layoutDecoratedWithMargins(child, rect.left, rect.top - mSumDy, rect.right, rect.bottom - mSumDy);
        } else {
            break;
        }
    }
}

下面來看看這段代碼:
在這里嫁盲,先得到在滾動前顯示的第一個item的前一個item:

View firstView = getChildAt(0);
int maxPos = getPosition(firstView) - 1;

如果在顯示區(qū)域,那么烈掠,就將它插在第一的位置:

 addView(child, 0);

同樣羞秤,在布局Item時,由于還沒有移動左敌,所以在布局時并不考慮travel的事:layoutDecoratedWithMargins(child, rect.left, rect.top - mSumDy, rect.right, rect.bottom - mSumDy)瘾蛋。
其它的代碼都很好理解了,這里就不再講了矫限。
這樣就完整實現(xiàn)了滾動的回收和復(fù)用功能了哺哼,完整的scrollVerticallyBy代碼如下:

public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getChildCount() <= 0) {
        return dy;
    }

    int travel = dy;
    //如果滑動到最頂部
    if (mSumDy + dy < 0) {
        travel = -mSumDy;
    } else if (mSumDy + dy > mTotalHeight - getVerticalSpace()) {
        //如果滑動到最底部
        travel = mTotalHeight - getVerticalSpace() - mSumDy;
    }

    //回收越界子View
    for (int i = getChildCount() - 1; i >= 0; i--) {
        View child = getChildAt(i);
        if (travel > 0) {//需要回收當(dāng)前屏幕,上越界的View
            if (getDecoratedBottom(child) - travel < 0) {
                removeAndRecycleView(child, recycler);
                continue;
            }
        } else if (travel < 0) {//回收當(dāng)前屏幕奇唤,下越界的View
            if (getDecoratedTop(child) - travel > getHeight() - getPaddingBottom()) {
                removeAndRecycleView(child, recycler);
            }
        }
    }

    Rect visibleRect = getVisibleArea(travel);
    //布局子View階段
    if (travel >= 0) {
        View lastView = getChildAt(getChildCount() - 1);
        int minPos = getPosition(lastView) + 1;//從最后一個View+1開始吧

        //順序addChildView
        for (int i = minPos; i <= getItemCount() - 1; i++) {
            Rect rect = mItemRects.get(i);
            if (Rect.intersects(visibleRect, rect)) {
                View child = recycler.getViewForPosition(i);
                addView(child);
                measureChildWithMargins(child, 0, 0);
                layoutDecorated(child, rect.left, rect.top - mSumDy, rect.right, rect.bottom - mSumDy);
            } else {
                break;
            }
        }
    } else {
        View firstView = getChildAt(0);
        int maxPos = getPosition(firstView) - 1;

        for (int i = maxPos; i >= 0; i--) {
            Rect rect = mItemRects.get(i);
            if (Rect.intersects(visibleRect, rect)) {
                View child = recycler.getViewForPosition(i);
                addView(child, 0);//將View添加至RecyclerView中幸斥,childIndex為1,但是View的位置還是由layout的位置決定
                measureChildWithMargins(child, 0, 0);
                layoutDecoratedWithMargins(child, rect.left, rect.top - mSumDy, rect.right, rect.bottom - mSumDy);
            } else {
                break;
            }
        }
    }

    mSumDy += travel;
    // 平移容器內(nèi)的item
    offsetChildrenVertical(-travel);
    return travel;
}

到此為止咬扇,我們已經(jīng)為LayoutManager增加了回收復(fù)用的功能甲葬,但是這里我們調(diào)用offsetChildrenVertical(-travel)來實現(xiàn)平移,當(dāng)需要實現(xiàn)在平移時懈贺,改變每個item的大小经窖、角度等參數(shù)時,offsetChildrenVertical(-travel)就無法完成了梭灿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末画侣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子堡妒,更是在濱河造成了極大的恐慌配乱,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件皮迟,死亡現(xiàn)場離奇詭異搬泥,居然都是意外死亡,警方通過查閱死者的電腦和手機伏尼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門忿檩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人爆阶,你說我怎么就攤上這事燥透∩秤剑” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵班套,是天一觀的道長肢藐。 經(jīng)常有香客問我,道長孽尽,這世上最難降的妖魔是什么窖壕? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮杉女,結(jié)果婚禮上瞻讽,老公的妹妹穿的比我還像新娘。我一直安慰自己熏挎,他們只是感情好速勇,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坎拐,像睡著了一般烦磁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哼勇,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天都伪,我揣著相機與錄音,去河邊找鬼积担。 笑死陨晶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的帝璧。 我是一名探鬼主播先誉,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼的烁!你這毒婦竟也來了褐耳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤渴庆,失蹤者是張志新(化名)和其女友劉穎铃芦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體襟雷,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡刃滓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗤军。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片注盈。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡晃危,死狀恐怖叙赚,靈堂內(nèi)的尸體忽然破棺而出老客,到底是詐尸還是另有隱情,我是刑警寧澤震叮,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布胧砰,位于F島的核電站,受9級特大地震影響苇瓣,放射性物質(zhì)發(fā)生泄漏尉间。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一击罪、第九天 我趴在偏房一處隱蔽的房頂上張望哲嘲。 院中可真熱鬧,春花似錦媳禁、人聲如沸眠副。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽囱怕。三九已至,卻和暖如春毫别,著一層夾襖步出監(jiān)牢的瞬間娃弓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工岛宦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留台丛,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓恋博,卻偏偏與公主長得像齐佳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子债沮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容