概述
VirtualLayout是一個針對RecyclerView的LayoutManager擴展, 主要提供一整套布局方案和布局間的組件復(fù)用的問題。
設(shè)計思路
- 通過定制化的LayoutManager,接管整個RecyclerView的布局邏輯;
- LayoutManager管理了一系列LayoutHelper,LayoutHelper負責具體布局邏輯實現(xiàn)的地方;
- 每一個LayoutHelper負責頁面某一個范圍內(nèi)的組件布局;
- 不同的LayoutHelper可以做不同的布局邏輯,因此可以在一個RecyclerView頁面里提供異構(gòu)的布局結(jié)構(gòu),這就能比系統(tǒng)自帶的LinearLayoutManager、GridLayoutManager等提供更加豐富的能力。 同時支持擴展LayoutHelper來提供更多的布局能力。
主要功能
- 默認通用布局實現(xiàn),解耦所有的View和布局之間的關(guān)系: Linear, Grid, 吸頂, 浮動, 固定位置等。
- LinearLayoutHelper: 線性布局
- GridLayoutHelper: Grid布局, 支持橫向的colspan
- FixLayoutHelper: 固定布局,始終在屏幕固定位置顯示
- ScrollFixLayoutHelper: 固定布局,但之后當頁面滑動到該圖片區(qū)域才顯示, 可以用來做返回頂部或其他書簽等
- FloatLayoutHelper: 浮動布局,可以固定顯示在屏幕上,但用戶可以拖拽其位置
- ColumnLayoutHelper: 欄格布局,都布局在一排,可以配置不同列之間的寬度比值
- SingleLayoutHelper: 通欄布局,只會顯示一個組件View
- OnePlusNLayoutHelper: 一拖N布局,可以配置1-5個子元素
- StickyLayoutHelper: stikcy布局, 可以配置吸頂或者吸底
- StaggeredGridLayoutHelper: 瀑布流布局,可配置間隔高度/寬度
- 上述默認實現(xiàn)里可以大致分為兩類:一是非fix類型布局,像線性、Grid、欄格等,它們的特點是布局在整個頁面流里,隨頁面滾動而滾動;另一類就是fix類型的布局,它們的子節(jié)點往往不隨頁面滾動而滾動。
- 所有除布局外的組件復(fù)用,VirtualLayout將用來管理大的模塊布局組合,擴展了RecyclerView,使得同一RecyclerView內(nèi)的組件可以復(fù)用,減少View的創(chuàng)建和銷毀過程。
以上是VLayout 在 GitHub 上的介紹
RecyclerView 焦點
如果發(fā)現(xiàn)你的RecyclerView 在滑動的過程中經(jīng)常跳屏(暫且稱它為跳屏,現(xiàn)象是總是跳到某個item),那么你可能遇到RecyclerView與子item某控件的焦點沖突問題。處理這類問題的方法有很多,例如你可以在你的xml中使用android:descendantFocusability,這里就不贅述了。
橫向滑動組件
在使用VLayout的過程中,你會發(fā)現(xiàn)根本找不到一個橫向線性布局的Helper(LinearLayoutHelper不支持橫向)

那么我們按照VLayout設(shè)計者的建議,自行嵌套一個橫向的RecyclerView。創(chuàng)建類HorizontalAdapter 并繼承DelegateAdapter.Adapter<HorizontalViewHolder>
public class HorizontalAdapter extends DelegateAdapter.Adapter<HorizontalViewHolder> {
private Context mContext;
private LayoutHelper mLayoutHelper;
public HorizontalAdapter(Context context, LayoutHelper layoutHelper) {
this.mContext = context;
this.mLayoutHelper = layoutHelper;
}
@Override
public LayoutHelper onCreateLayoutHelper() {
return mLayoutHelper;
}
@Override
public HorizontalViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 4) {
return new HorizontalViewHolder(
LayoutInflater.from(mContext).inflate(R.layout.layout_recyclerview, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(NormalViewHolder holder, int position) {
if (holder.itemView instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) holder.itemView;
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(new HotItemAdapter(mContext, list,listener));
}
}
@Override
public int getItemViewType(int position) {
return 4;
}
@Override
public int getItemCount() {
return 1;
}
}
至此,初步的Adapter已經(jīng)碼好了。那么問題來了
橫向RecyclerView 狀態(tài)重置。
這句話什么意思,就是當你的橫向RecyclerView滑出屏幕外被復(fù)用時,之前對橫向RecyclerView或其子item做的操作都將重置。例如橫向RecyclerView滑動過的距離,當你滑出屏幕外后再滑回來,橫向RecyclerView又回到第一個item。
解決思路:
當橫向RecyclerView 將被復(fù)用時,記錄RecyclerView的狀態(tài)。當橫向RecyclerView 即將出現(xiàn)在屏幕內(nèi)時,恢復(fù)其狀態(tài)。
我們在HorizontalAdapter中重寫兩個方法onViewDetachedFromWindow和onViewAttachedToWindow,以上面滑動距離為例
@Override
public void onViewDetachedFromWindow(NormalViewHolder holder) {
if (holder.itemView instanceof RecyclerView) {
RecyclerView recyclerView = ((RecyclerView) holder.itemView);
LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
position = manager.findFirstVisibleItemPosition();
View view = manager.findViewByPosition(position);
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (view != null) {
xOffset = view.getLeft() - lp.leftMargin; //如果你設(shè)置了margin則減去
}
}
super.onViewDetachedFromWindow(holder);
}
@Override
public void onViewAttachedToWindow(NormalViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (holder.itemView instanceof RecyclerView) {
RecyclerView recyclerView = ((RecyclerView) holder.itemView);
LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
manager.scrollToPositionWithOffset(position, xOffset);
}
}
如果你還內(nèi)嵌了ViewPager(輪播?),同樣會出現(xiàn)組件的狀態(tài)問題。因為VLayout干預(yù)不到這一層,所以我們還是得自己解決,解決方案同上
共用復(fù)用池
當我們需要嵌套RecyclerView時,我們讓子RecyclerView與父RecyclerView共用一個復(fù)用池
public HorizontalAdapter(Context context, LayoutHelper layoutHelper, RecyclerView.RecycledViewPool viewPool) {
this.mContext = context;
this.mLayoutHelper = layoutHelper;
this.viewPool = viewPool;
}
@Override
public void onBindViewHolder(NormalViewHolder holder, int position) {
if (holder.itemView instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) holder.itemView;
recyclerView.setRecycledViewPool(viewPool);
...
}
}