概述
可以发现布局似乎十分复杂,使用Monitor工具看看它的布局情况:
主界面居然只有一个RecyclerView就能实现??
查了资料才发现,原来淘宝使用了阿里团队开发的一个开源框架Vlayout,(全称VirtualLayout)
implementation 'com.alibaba.android:vlayout:1.0.3'
这个Vlayout可以在一个RecyclerView中加载不同的适配器,因为适配器可以加载不同的布局,它们分别是:
1、线性布局。使用ReyclerView开发最常用的布局。
2、网格布局。使用的频率也很高,只是一般都是自己实现,而在这个框架中已经被实现并封装好了。
3、固定布局
4、浮动布局
5、栅格布局
6、一拖N布局
7、吸边布局
8、瀑布流布局
截图来自github上的开源项目Vlayout,地址:
Vlayout地址
具体的demo也可以查看这个地址.
接下来看看如何使用
使用方法也十分简单:
VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
DelegateAdapter adapter = new DelegateAdapter(layoutManager,true){
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
List<Integer> list = new ArrayList<>();
list.add(R.drawable.image1);
list.add(R.drawable.image2);
list.add(R.drawable.image3);
list.add(R.drawable.image4);
list.add(R.drawable.image5);
list.add(R.drawable.image6);
Banner banner = holder.itemView.findViewById(R.id.banner);
banner.setBannerStyle(BannerConfig.CIRCLE_INDICATOR);
banner.setImageLoader(new GlideImageLoader());
banner.setImages(list);
banner.setBannerAnimation(Transformer.DepthPage);
banner.isAutoPlay(true);
banner.setDelayTime(3000);
banner.setIndicatorGravity(BannerConfig.CENTER);
banner.start();
banner.setOnBannerListener(new OnBannerListener() {
@Override
public void OnBannerClick(int position) {
Toast.makeText(getApplicationContext(), "banner点击了" + position, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.vlayout_banner,parent,false);
return new BaseViewHolder(view);
}
@Override
public int getItemCount() {
return 1;
}
};
mRecyclerView.setAdapter(adapter);
跟原本的使用的方法基本一致,只不过将布局管理换成了VirtualLayoutManager,以及将RecyclerView.Adapter换成了自定义的DelegateAdapter,其实DelegateAdapter查看源码的话依然可以发现它是继承于RecyclerView.Adapter,只不过进行了进一层的封装。其他的操作与ReyclerView一模一样,分别实现onCreateViewHolder,onBindViewHolder和getItemCount,这样就添加了一个Adapter上去了。这样一看好像与原本的RecyclerView实现没有什么区别,但也并没有比原先的复杂。但是后续如果想要更多的Adapter,怎么操作?在我过去接触Vlayout之前,我在布局中添加了多个ReyclerView,每个ReyclerView只有一个Adapter。现在的话,能不能有一个大的主Adapter,这个主Adapter可以添加许多小的Adapter,这样是不是很完美?Vlayout中就提供了这样的类:DelegateAdapter。
使用方法:
1、自定义Adapter继承并实现DelegateAdapter.Adapter的方法
2、生成DelegateAdapter对象并添加。
伪代码:
class MyAdapter extends DelegateAdapter.Adapter{
。。。。
}
MyAdapter adapter1 = new MyAdapter(… )
MyAdapter adapter2 = new MyAdapter(… )
…
DelegateAdapter mainAdapter = new DelegateAdapter(…)
mainAdapter.addAdapter(adapter1)
mainAdapter.addAdapter(adapter2)
…
mRecyclerView.setAdapter(adapter);
这样就完成了!
下面以淘宝页面为例:
首先我对DelegateAdapter进行封装,因为不管实现什么样的Adapter,onCreateViewHolder都是相同的:
View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutId,parent,false);
return new BaseViewHolder(view);
即:根据不同的布局id生成不同的view,然后使用相同的ViewHolder进行返回。因此这个可以进行封装。
getItemCount相同:
@Override
public int getItemCount() {
return mCount;
}
onCreateLayoutHelper。继承DelegateAdapter.Adapter必须实现这个方法,因此Adapter必须要有一个LayoutHelper(),这个LayoutHelper会根据以上提供的8种不同的布局实现类进行对应的布局。
@Override
public LayoutHelper onCreateLayoutHelper() {
return mLayoutHelper;
}
唯一无法封装的onBindViewHolder,因为这个对开发者来说会有不同的实现,因此它的“不定”的,所有无法封装。
BaseDelegateAdapter.java
public class BaseDelegateAdapter extends DelegateAdapter.Adapter<BaseViewHolder> {
private int mLayoutId;
private int mCount;
private LayoutHelper mLayoutHelper;
public BaseDelegateAdapter(LayoutHelper layoutHelper, int layoutId,int count){
mLayoutId = layoutId;
mCount = count;
mLayoutHelper = layoutHelper;
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutId,parent,false);
return new BaseViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return mCount;
}
@Override
public LayoutHelper onCreateLayoutHelper() {
return mLayoutHelper;
}
}
BaseViewHolder.java
public class BaseViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> views;
public BaseViewHolder(@NonNull View itemView) {
super(itemView);
views = new SparseArray<>();
}
private void setTextView(@IdRes int id,int content){
View view = views.get(id);
if(view == null){
try {
throw new Exception("请先调用getView保存View先");
} catch (Exception e) {
e.printStackTrace();
}
}
if(view instanceof TextView){
((TextView) view).setText(content);
}else{
try {
throw new Exception("这个id不是一个TextView视图");
} catch (Exception e) {
e.printStackTrace();
}
}
}
private <T extends View> T getView(@IdRes int viewId){
View view = views.get(viewId);
if(view == null){
view = itemView.findViewById(viewId);
views.put(viewId,view);
}
return (T) view;
}
}
之后就是如上所说的使用方法了:
private void initView(){
mRecyclerView = findViewById(R.id.recyclerView);
VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
DelegateAdapter adapter = new DelegateAdapter(layoutManager,true);
mRecyclerView.setLayoutManager(layoutManager);
BaseDelegateAdapter bannerAdapter = new BaseDelegateAdapter(new LinearLayoutHelper(),R.layout.vlayout_banner,1){
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
List<Integer> list = new ArrayList<>();
list.add(R.drawable.image1);
list.add(R.drawable.image2);
list.add(R.drawable.image3);
list.add(R.drawable.image4);
list.add(R.drawable.image5);
list.add(R.drawable.image6);
Banner banner = holder.itemView.findViewById(R.id.banner);
banner.setBannerStyle(BannerConfig.CIRCLE_INDICATOR);
banner.setImageLoader(new GlideImageLoader());
banner.setImages(list);
banner.setBannerAnimation(Transformer.DepthPage);
banner.isAutoPlay(true);
banner.setDelayTime(3000);
banner.setIndicatorGravity(BannerConfig.CENTER);
banner.start();
banner.setOnBannerListener(new OnBannerListener() {
@Override
public void OnBannerClick(int position) {
Toast.makeText(getApplicationContext(), "banner点击了" + position, Toast.LENGTH_SHORT).show();
}
});
}
};
adapter.addAdapter(bannerAdapter);
mRecyclerView.setAdapter(adapter);
}
demo地址:https://github.com/lyx19970504/VLayoutDemo
关于使用方法到这里结束了,下面是源码的解析:
从RecyclerView的setAdapter方法进入:
public void setAdapter(@Nullable Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
requestLayout();
}
这里调用了requestLayout,这是方法能让View本身重新绘制,即调用onMeasure,onLayout,onDraw方法。
onLayout方法:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
调用dispatchLayout方法:
void dispatchLayout() {
。。。
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
可以看到调用的比较重要的方法是dispatchLayoutStep2()和dispatchLayoutStep3()。一般来说开发者都会添加注释,否则这会增加阅读的困难度。
/**
* The final step of the layout where we save the information about views for animations,
* trigger animations and do any necessary cleanup.
*/
private void dispatchLayoutStep3() {
......
}
如上是Step3的注释,可以发现这个步骤是保存views的相关动画信息,动画信息可以先不管。于是进入dispatchLayoutStep2()看看:
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
在这个方法中基本是设置一些参数,但是却调用一个很重要的方法:
mLayout.onLayoutChildren(mRecycler, mState);
从方法名可以看出是对所有的子view进行布局
mLayout则是一个LayoutManager:
@VisibleForTesting LayoutManager mLayout;
那么到这里思路就十分清晰了:首先再activity中,RecyclerView对象首先设置LayoutManager(setLayoutManager),我们比较常用的就是LinearLayoutManager,代码会一路往下走,最后由LayoutMnager调用onLayoutChildren对子views进行布局。
而VirtualLayoutManager框架将则对LinearLayoutManager进一步的扩充使其支持跟多的布局实现(事实上VirtualLayoutManager是继承于LinearLayoutManager的)
VirtualLayoutManager的onLayoutChildren方法:
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
Trace.beginSection(TRACE_LAYOUT);
}
if (mNoScrolling && state.didStructureChange()) {
mSpaceMeasured = false;
mSpaceMeasuring = true;
}
runPreLayout(recycler, state);
try {
super.onLayoutChildren(recycler, state);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
// MaX_VALUE means invalidate scrolling offset - no scroll
runPostLayout(recycler, state, Integer.MAX_VALUE); // hack to indicate its an initial layout
}
。。。
}
可以发现调用了父类的方法:
super.onLayoutChildren(recycler, state);
它的父类是ExposeLinearLayoutManagerEx,而ExposeLinearLayoutManagerEx继承于LinearLayoutManager,所以才说VirtualLayoutManager继承于LinearLayoutManager。
ExposeLinearLayoutManagerEx的onLayoutChildren实现:
public void onLayoutChildren(Recycler recycler, State state) {
this.ensureLayoutStateExpose();
this.mLayoutState.mRecycle = false;
this.myResolveShouldLayoutReverse();
this.mAnchorInfo.reset();
this.mAnchorInfo.mLayoutFromEnd = this.mShouldReverseLayoutExpose ^ this.getStackFromEnd();
this.updateAnchorInfoForLayoutExpose(state, this.mAnchorInfo);
int extra = this.getExtraLayoutSpace(state);
boolean before = state.getTargetScrollPosition() < this.mAnchorInfo.mPosition;
int extraForStart;
int extraForEnd;
if (before == this.mShouldReverseLayoutExpose) {
extraForEnd = extra;
extraForStart = 0;
} else {
extraForStart = extra;
extraForEnd = 0;
}
extraForStart += this.mOrientationHelper.getStartAfterPadding();
extraForEnd += this.mOrientationHelper.getEndPadding();
int endOffset;
int fixOffset;
if (state.isPreLayout() && this.mCurrentPendingScrollPosition != -1 && this.mPendingScrollPositionOffset != -2147483648) {
View existing = this.findViewByPosition(this.mCurrentPendingScrollPosition);
if (existing != null) {
if (this.mShouldReverseLayoutExpose) {
endOffset = this.mOrientationHelper.getEndAfterPadding() - this.mOrientationHelper.getDecoratedEnd(existing);
fixOffset = endOffset - this.mPendingScrollPositionOffset;
} else {
endOffset = this.mOrientationHelper.getDecoratedStart(existing) - this.mOrientationHelper.getStartAfterPadding();
fixOffset = this.mPendingScrollPositionOffset - endOffset;
}
if (fixOffset > 0) {
extraForStart += fixOffset;
} else {
extraForEnd -= fixOffset;
}
}
}
this.onAnchorReady(state, this.mAnchorInfo);
this.detachAndScrapAttachedViews(recycler);
this.mLayoutState.mIsPreLayout = state.isPreLayout();
this.mLayoutState.mOnRefresLayout = true;
ExposeLinearLayoutManagerEx.LayoutState var10000;
int startOffset;
if (this.mAnchorInfo.mLayoutFromEnd) {
this.updateLayoutStateToFillStartExpose(this.mAnchorInfo);
this.mLayoutState.mExtra = extraForStart;
this.fill(recycler, this.mLayoutState, state, false);
startOffset = this.mLayoutState.mOffset;
if (this.mLayoutState.mAvailable > 0) {
extraForEnd += this.mLayoutState.mAvailable;
}
this.updateLayoutStateToFillEndExpose(this.mAnchorInfo);
this.mLayoutState.mExtra = extraForEnd;
var10000 = this.mLayoutState;
var10000.mCurrentPosition += this.mLayoutState.mItemDirection;
this.fill(recycler, this.mLayoutState, state, false);
endOffset = this.mLayoutState.mOffset;
} else {
this.updateLayoutStateToFillEndExpose(this.mAnchorInfo);
this.mLayoutState.mExtra = extraForEnd;
this.fill(recycler, this.mLayoutState, state, false);
endOffset = this.mLayoutState.mOffset;
if (this.mLayoutState.mAvailable > 0) {
extraForStart += this.mLayoutState.mAvailable;
}
this.updateLayoutStateToFillStartExpose(this.mAnchorInfo);
this.mLayoutState.mExtra = extraForStart;
var10000 = this.mLayoutState;
var10000.mCurrentPosition += this.mLayoutState.mItemDirection;
this.fill(recycler, this.mLayoutState, state, false);
startOffset = this.mLayoutState.mOffset;
}
if (this.getChildCount() > 0) {
if (this.mShouldReverseLayoutExpose ^ this.getStackFromEnd()) {
fixOffset = this.fixLayoutEndGapExpose(endOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = this.fixLayoutStartGapExpose(startOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
} else {
fixOffset = this.fixLayoutStartGapExpose(startOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = this.fixLayoutEndGapExpose(endOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
}
}
this.layoutForPredictiveAnimationsExpose(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
this.mCurrentPendingScrollPosition = -1;
this.mPendingScrollPositionOffset = -2147483648;
this.mOrientationHelper.onLayoutComplete();
}
this.mLastStackFromEnd = this.getStackFromEnd();
this.mCurrentPendingSavedState = null;
}
看起来非常复杂,完成了很多的事情,但是开发者进行了注释:
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
可以发现第一步是检查,后面则是对什么东西进行填充,注意这里使用的是this.fill而不是fill,看看fill的实现:
protected int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
。。。
。。。
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutStateExpose(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
while (remainingSpace > 0 && layoutState.hasMore(state)) {
layoutChunkResultCache.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResultCache);
if (layoutChunkResultCache.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResultCache.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
if (!layoutChunkResultCache.mIgnoreConsumed || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResultCache.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResultCache.mConsumed;
}
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResultCache.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutStateExpose(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResultCache.mFocusable) {
break;
}
}
。。。
}
一样的复杂又冗长。但是有个方法特别有吸引力:this.layoutChunk,翻译过来的意思是布局 块,也就是对块布局。注意这个this,它代表调用的VirtualLayoutManager的layoutChunk而不是ExposeLinearLayoutManagerEx的layoutChunk.看看它的实现:
@Override
protected void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, com.alibaba.android.vlayout.layout.LayoutChunkResult result) {
final int position = layoutState.mCurrentPosition;
mTempLayoutStateWrapper.mLayoutState = layoutState;
LayoutHelper layoutHelper = mHelperFinder == null ? null : mHelperFinder.getLayoutHelper(position);
if (layoutHelper == null)
layoutHelper = mDefaultLayoutHelper;
layoutHelper.doLayout(recycler, state, mTempLayoutStateWrapper, result, this);
mTempLayoutStateWrapper.mLayoutState = null;
// no item consumed
if (layoutState.mCurrentPosition == position) {
Log.w(TAG, "layoutHelper[" + layoutHelper.getClass().getSimpleName() + "@" + layoutHelper.toString() + "] consumes no item!");
// break as no item consumed
result.mFinished = true;
} else {
// Update height consumed in each layoutChunck pass
final int positionAfterLayout = layoutState.mCurrentPosition - layoutState.mItemDirection;
final int consumed = result.mIgnoreConsumed ? 0 : result.mConsumed;
// TODO: change when supporting reverseLayout
Range<Integer> range = new Range<>(Math.min(position, positionAfterLayout), Math.max(position, positionAfterLayout));
final int idx = findRangeLength(range);
if (idx >= 0) {
Pair<Range<Integer>, Integer> pair = mRangeLengths.get(idx);
if (pair != null && pair.first.equals(range) && pair.second == consumed)
return;
mRangeLengths.remove(idx);
}
mRangeLengths.add(Pair.create(range, consumed));
Collections.sort(mRangeLengths, new Comparator<Pair<Range<Integer>, Integer>>() {
@Override
public int compare(Pair<Range<Integer>, Integer> a, Pair<Range<Integer>, Integer> b) {
if (a == null && b == null) return 0;
if (a == null) return -1;
if (b == null) return 1;
Range<Integer> lr = a.first;
Range<Integer> rr = b.first;
return lr.getLower() - rr.getLower();
}
});
}
}
这个互相调用的过程比较复杂,用图来表示就是:
VirtualLayoutManager.onLayoutChildren(){
super.onLayoutChildren(recycler, state);
}
➡️
ExposeLinearLayoutManagerEx.onLayoutChildren(){
this.fill(…)
}
➡️
ExposeLinearLayoutManagerEx.fill(){
this.layoutChunk(…)
}
➡️
VirtualLayoutManager.layoutChunk
至于layoutChunk后面再谈。
总体流程如图:
接下来看看DelegateAdapter的实现。首先这个类的好处就是它可以添加多个adapter,每个adapter可以根据不同的LayoutHelper而实现不同的布局。这些helper类框架中都已经实现好了。
从DelegateAdapter(可以理解为一个总管adapter)的addAdapter开始:
addAdapter最终会调用内部的这个方法:
public void addAdapters(int position, @Nullable List<Adapter> adapters) {
。。。
List<LayoutHelper> helpers = new LinkedList<>(super.getLayoutHelpers());
for (Adapter adapter : adapters) {
// every adapter has an unique index id
AdapterDataObserver observer = new AdapterDataObserver(mTotal, mIndexGen == null ? mIndex++ : mIndexGen.incrementAndGet());
adapter.registerAdapterDataObserver(observer);
hasStableIds = hasStableIds && adapter.hasStableIds();
LayoutHelper helper = adapter.onCreateLayoutHelper();
helper.setItemCount(adapter.getItemCount());
mTotal += helper.getItemCount();
helpers.add(position, helper);
mAdapters.add(position, Pair.create(observer, adapter));
position++;
}
if (!hasObservers()) {
super.setHasStableIds(hasStableIds);
}
super.setLayoutHelpers(helpers);
}
可以发现使用了一个LayoutHelper列表将helper添加的adapter储存起来。并且调用父类的setLayoutHelpers方法:
public void setLayoutHelpers(List<LayoutHelper> helpers) {
this.mLayoutManager.setLayoutHelpers(helpers);
}
它的父类VirtualLayoutAdapter,对setLayoutHelpers的实现是调用了VirtualLayoutManager的setLayoutHelpers方法。
setLayoutHelpers实现:
public void setLayoutHelpers(@Nullable List helpers) {
for (LayoutHelper helper : mHelperFinder) {
oldHelpersSet.put(System.identityHashCode(helper), helper);
}
// set ranges
if (helpers != null) {
int start = 0;
for (int i = 0; i < helpers.size(); i++) {
LayoutHelper helper = helpers.get(i);
if (helper instanceof FixAreaLayoutHelper) {
((FixAreaLayoutHelper) helper).setAdjuster(mFixAreaAdjustor);
}
if (helper instanceof BaseLayoutHelper && mLayoutViewBindListener != null) {
((BaseLayoutHelper) helper).setLayoutViewBindListener(mLayoutViewBindListener);
}
if (helper.getItemCount() > 0) {
helper.setRange(start, start + helper.getItemCount() - 1);
} else {
helper.setRange(-1, -1);
}
start += helper.getItemCount();
}
}
this.mHelperFinder.setLayouts(helpers);
for (LayoutHelper helper : mHelperFinder) {
newHelpersSet.put(System.identityHashCode(helper), helper);
}
for (Iterator<Map.Entry<Integer, LayoutHelper>> it = oldHelpersSet.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<Integer, LayoutHelper> entry = it.next();
Integer key = entry.getKey();
if (newHelpersSet.containsKey(key)) {
newHelpersSet.remove(key);
it.remove();
}
}
for (LayoutHelper helper : oldHelpersSet.values()) {
helper.clear(this);
}
if (!oldHelpersSet.isEmpty() || !newHelpersSet.isEmpty()) {
mSpaceMeasured = false;
}
oldHelpersSet.clear();
newHelpersSet.clear();
requestLayout();
}
在这个方法中又出现了新的类:LayoutHelper,这个LayoutHelper就像一个管理容器,它负责管理所有的helper类,并且为HelperFinder设置所有的helper。至于HelperFinder是什么,相信从名字上就能看出来,它是一个HelperFinder的查找工具。
设置完成了以后,回到之前的layoutChunk中:
@Override
protected void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, com.alibaba.android.vlayout.layout.LayoutChunkResult result) {
final int position = layoutState.mCurrentPosition;
mTempLayoutStateWrapper.mLayoutState = layoutState;
LayoutHelper layoutHelper = mHelperFinder == null ? null : mHelperFinder.getLayoutHelper(position);
if (layoutHelper == null)
layoutHelper = mDefaultLayoutHelper;
layoutHelper.doLayout(recycler, state, mTempLayoutStateWrapper, result, this);
。。。
。。。
}
首先HelperFinder工具会根据位置来获得adapter对应的LayoutHelper,一共有8种已经内置的helper,这个在之前已经介绍过了。根据不同的helper会调用不同的doLayout实现方法。怎么根据位置获得呢?比如说,adapter1设置了10个控件,adapter2设置了5个控件,adapter3设置了3个控件,然后helperFinder在0-9的位置时,就会得到adapter1的helper,在10-15的位置时就会获得adapter2的helper,在16-18的位置时就会获得adapter3的helper.
adapter必须实现设置helper的方法:↓
@Override
public LayoutHelper onCreateLayoutHelper() {
return mLayoutHelper;
}
doLayout方法,则是真正对控件进行布局的方法。不同的helper有不同的doLayout方法。
总结
代码一共这么几行:
VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
DelegateAdapter adapter = new DelegateAdapter(layoutManager,true);
mRecyclerView.setLayoutManager(layoutManager);
adapter.addAdapter(x1)
adapter.addAdapter(x2)
...(很多子adapter)
mRecyclerView.setAdapter(adapter);
首先将VirtualLayoutManager添加给RecyclerView和DelegateAdapter,在DelegateAdapter添加子adapter的过程中,会获取所有子adapter的helper类然后存储起来,这些helper类分别对应不同的布局模式。RecyclerView设置DelegateAdapter后,RecyclerView设置的VirtualLayoutManager会调用layoutChunk方法,这个方法中会根据位置获取不同adapter对应的helper方法,然后使用他们的doLayout方法进行控件的摆放。
最后
以上就是活泼钢铁侠为你收集整理的淘宝页面RecyclerView分析及阿里Vlayout框架使用与分析的全部内容,希望文章能够帮你解决淘宝页面RecyclerView分析及阿里Vlayout框架使用与分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复