Android如何自定義RecyclerView的LayoutManager

如果有需要自定義LayoutManger的同學(xué)基本都已經(jīng)能熟悉使用RecyclerView,在此筆者就不再贅述如何使用RecyclerView了。

首先筆者編寫了一個(gè)簡單的demo用來展示使用RecyclerView包自帶的LinearLayoutManager的效果。

這部分代碼可以在HowToCustomLayoutManager找到,檢出tag為1.0.0的版本運(yùn)行即可
運(yùn)行后如下圖

device-2016-01-07-145618.png

可以看到筆者對(duì)item的大小進(jìn)行的修改,但是仍然每一行只顯示一個(gè)item,這是LinearLayoutManager的布局策略。

@Override
public void onBindViewHolder(DemoViewHolder holder, int position) {
    holder.itemView.getLayoutParams().width = (self.getDemoModels().get(position).getPreferWidth());
    holder.itemView.getLayoutParams().height = (self.getDemoModels().get(position).getPreferHeight());
    holder.setDelegate(self);
    holder.reload(self);
}

接下來我們開始創(chuàng)建一個(gè)自定義的CustomLayoutManager,先預(yù)設(shè)一下想要的效果,為了簡單實(shí)現(xiàn),筆者自定義的CustomLayoutManager會(huì)進(jìn)行斜線布局,即從容器左上角開始放置item,下一個(gè)item的左上角坐標(biāo)對(duì)應(yīng)上一個(gè)item的右下角坐標(biāo)。

public class CustomLayoutManager extends RecyclerView.LayoutManager {
    /** Convenience Var to call this */
    final CustomLayoutManager self = this;

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        detachAndScrapAttachedViews(recycler); // 分離所有的itemView

        int offsetX = 0;
        int offsetY = 0;

        for (int i = 0; i < getItemCount(); i++) {
            View scrap = recycler.getViewForPosition(i); // 根據(jù)position獲取一個(gè)碎片view,可以從回收的view中獲取,也可能新構(gòu)造一個(gè)

            addView(scrap);
            measureChildWithMargins(scrap, 0, 0);  // 計(jì)算此碎片view包含邊距的尺寸

            int width = getDecoratedMeasuredWidth(scrap);  // 獲取此碎片view包含邊距和裝飾的寬度width
            int height = getDecoratedMeasuredHeight(scrap); // 獲取此碎片view包含邊距和裝飾的高度height

            layoutDecorated(scrap, offsetX , offsetY, offsetX + width, offsetY + height); // Important!布局到RecyclerView容器中,所有的計(jì)算都是為了得出任意position的item的邊界來布局

            offsetX += width;
            offsetY += height;
        }
    }

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }
}

如以上代碼所示,繼承LayoutManger必須override方法generateDefaultLayoutParams(),以及為了布局必須實(shí)現(xiàn)[onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)](http://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html#onLayoutChildren(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State))。

在[onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)](http://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html#onLayoutChildren(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State))筆者計(jì)算了所有item的尺寸并將所有item都擺放到了RecyclerView中。
效果圖如下

device-2016-01-08-104053.png

這部分代碼可以在HowToCustomLayoutManager找到,檢出tag為1.0.0的版本運(yùn)行即可
可以看到這個(gè)效果確實(shí)實(shí)現(xiàn)了預(yù)定目標(biāo),斜線擺放。
然而這里有兩個(gè)問題

沒有實(shí)現(xiàn)重用

重用機(jī)制是RecyclerView的主要性能提升點(diǎn),如果沒有實(shí)現(xiàn)重用使用RecyclerView就沒有意義了。
上例中如果有50個(gè)item,RecyclerView就會(huì)有50個(gè)view,其中大部分view的坐標(biāo)都在屏幕外沒有必要顯示。

無法滑動(dòng)

滑動(dòng)也是RecyclerView在大部分情況下應(yīng)有的功能,因?yàn)?a target="_blank" rel="nofollow">RecyclerView主要是為了解決在較小容器中展示大量數(shù)據(jù)的問題。
滑動(dòng)在RecyclerView是比較特別的,RecyclerView本身并不執(zhí)行scroll,例如一個(gè)RecyclerView的高度為100,如果一個(gè)item的坐標(biāo)為(0,100),大小為(100, 100),這個(gè)item將被擺放到RecyclerView容器外部。
RecyclerView中若想顯示這個(gè)item,其流程是RecyclerView獲取用戶滑動(dòng)手勢,判斷LayoutManger是否支持橫向或縱向滑動(dòng),若支持則傳遞信息給LayoutManger,由LayoutManger對(duì)item進(jìn)行平移(也可能是其他操作),而后按照重用機(jī)制應(yīng)當(dāng)回收容器外部的item,添加新進(jìn)入容器的item。

接下來筆者就開始進(jìn)一步優(yōu)化,實(shí)現(xiàn)重用和橫向縱向滑動(dòng)功能。
優(yōu)化后的代碼比較長,筆者已經(jīng)在代碼中做好的注釋,讀者可以查看HowToCustomLayoutManager,檢出tag為1.0.2的版本運(yùn)行,也可以在github直接閱讀CustomLayoutManager

基本原理是這樣的:

在每一次重新對(duì)item布局時(shí)(item信息改變時(shí)),計(jì)算每個(gè)item的坐標(biāo)尺寸記錄下來,如果一個(gè)item的坐標(biāo)尺寸與當(dāng)前顯示區(qū)域矩陣相交就展示這個(gè)item,否則回收這個(gè)item。
顯示區(qū)域有滑動(dòng)偏移量和容器大小決定,每次滑動(dòng)時(shí)都要進(jìn)行重新布局。

感謝閱讀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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