如果有需要自定義LayoutManger的同學(xué)基本都已經(jīng)能熟悉使用RecyclerView,在此筆者就不再贅述如何使用RecyclerView了。
首先筆者編寫了一個(gè)簡單的demo用來展示使用RecyclerView包自帶的LinearLayoutManager的效果。
這部分代碼可以在HowToCustomLayoutManager找到,檢出tag為1.0.0的版本運(yùn)行即可
運(yùn)行后如下圖
可以看到筆者對(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中。
效果圖如下
這部分代碼可以在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)行重新布局。
感謝閱讀。