Android如何自定義RecyclerView的LayoutManager

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

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

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

device-2016-01-07-145618.png

可以看到筆者對item的大小進行的修改,但是仍然每一行只顯示一個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)建一個自定義的CustomLayoutManager,先預設一下想要的效果,為了簡單實現(xiàn),筆者自定義的CustomLayoutManager會進行斜線布局,即從容器左上角開始放置item,下一個item的左上角坐標對應上一個item的右下角坐標。

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獲取一個碎片view,可以從回收的view中獲取,也可能新構造一個

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

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

            layoutDecorated(scrap, offsetX , offsetY, offsetX + width, offsetY + height); // Important!布局到RecyclerView容器中,所有的計算都是為了得出任意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(),以及為了布局必須實現(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))筆者計算了所有item的尺寸并將所有item都擺放到了RecyclerView中。
效果圖如下

device-2016-01-08-104053.png

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

沒有實現(xiàn)重用

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

無法滑動

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

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

基本原理是這樣的:

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

感謝閱讀。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容