淘宝页面RecyclerView分析及阿里Vlayout框架使用与分析

可以发现布局似乎十分复杂,使用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方法进行控件的摆放。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哒哒呵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值