如果有需要自定義LayoutManger的同學基本都已經(jīng)能熟悉使用RecyclerView乌妙,在此筆者就不再贅述如何使用RecyclerView了兰伤。
首先筆者編寫了一個簡單的demo用來展示使用RecyclerView包自帶的LinearLayoutManager的效果。
這部分代碼可以在HowToCustomLayoutManager找到,檢出tag為1.0.0的版本運行即可
運行后如下圖
可以看到筆者對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,先預設(shè)一下想要的效果悯蝉,為了簡單實現(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中獲取嗡靡,也可能新構(gòu)造一個
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中哈误。
效果圖如下
這部分代碼可以在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ū)域有滑動偏移量和容器大小決定悬蔽,每次滑動時都要進行重新布局。
感謝閱讀捉兴。