阿里巴巴的Vlayout框架源码原理详解(第一篇流程分析)

先看一下阿里对这个框架留下的Demo的效果:

《阿里巴巴的Vlayout框架源码原理详解(第一篇流程分析)》

看效果大体的可以猜测这个框架给我们提供了很多布局规则,据说淘宝首页就是用这个框架做的。源码地址

好接下来我们就沿着这个Demo这条线开始分析实现原理,从而学习人家的架构搭建方式

先看布局代码

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <android.support.v7.widget.RecyclerView
            android:id="@+id/main_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#aaaaaa"
            android:clipToPadding="true"
            android:paddingLeft="0dp"
            android:paddingRight="0dp"
            android:requiresFadingEdge="none"
            android:scrollbars="vertical" />
    </android.support.v4.widget.SwipeRefreshLayout>
..省略不重要代码块
</FrameLayout>

布局代码很简单就是一个下拉刷新的控价包裹了
RecyclerView,那么里面的所有一切布局规则都是根据RecyclerView的使用规则命名的白,如有对RecyclerView不熟悉的童鞋就看洋神的这篇博客

RecycleView使用详解
既然是用RecycleView,那么看一下RecycleView的Adapter和ViewHoder的实现方式

final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_view);
		// layoutManager.setRecycleOffset(300);
		/**
		 * 设置布局规则
		 */
		recyclerView.setLayoutManager(layoutManager);
		// layoutManager.setReverseLayout(true);
		/**
		 * 只设置间距,而不画分割线
		 */
		RecyclerView.ItemDecoration itemDecoration = new RecyclerView.ItemDecoration() {
			public void getItemOffsets(Rect outRect, View view,
					RecyclerView parent, RecyclerView.State state) {
				int position = ((LayoutParams) view.getLayoutParams())
						.getViewPosition();
				outRect.set(4, 4, 4, 4);
			}
		};
		/**
		 * 自定义View的缓存策略
		 */
		final RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
		recyclerView.setRecycledViewPool(viewPool);
		// recyclerView.addItemDecoration(itemDecoration);
		viewPool.setMaxRecycledViews(0, 20);
		/**
		 * 设置数据适配器
		 */
		final DelegateAdapter delegateAdapter = new DelegateAdapter(
				layoutManager, true);
		recyclerView.setAdapter(delegateAdapter);

这里Vlayout写了一个
DelegateAdapter 类,数据适配器全是基于此类,那么看一看此类是怎么实现的吧

public abstract class VirtualLayoutAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
  //保存了一个布局管理器
    @NonNull
    protected VirtualLayoutManager mLayoutManager;

    public VirtualLayoutAdapter(@NonNull VirtualLayoutManager layoutManager) {
        this.mLayoutManager = layoutManager;
    }

    public void setLayoutHelpers(List<LayoutHelper> helpers) {
        this.mLayoutManager.setLayoutHelpers(helpers);
    }
/**
 * 获取所有布局规则类
 * @return
 */
    @NonNull
    public List<LayoutHelper> getLayoutHelpers() {
        return this.mLayoutManager.getLayoutHelpers();
    }

}

它的父类持有布局管理类,以及可以获取加入的布局管理规则类集合。那么接着看它自己怎么实现的,既然是实现了多种规则按照常规处理方式的话肯定会复写getItemViewType方法,找一下有没有这个方法

@Override
	public int getItemViewType(int position) {
		// 折半算法查出对应的子Adapter
		Pair<AdapterDataObserver, Adapter> p = findAdapterByPosition(position);
		if (p == null) {
			return RecyclerView.INVALID_TYPE;
		}
		/**
		 * 通过子Adapter得到Type
		 */
		int subItemType = p.second.getItemViewType(position
				- p.first.mStartPosition);

		if (subItemType < 0) {
			// negative integer, invalid, just return
			return subItemType;
		}
		// Activity填入的为true所以将type作为key和Adapter作为value存入集合mItemTypeAry
		if (mHasConsistItemType) {
			mItemTypeAry.put(subItemType, p.second);
			return subItemType;
		}
		// 第几个子adapter
		int index = p.first.mIndex;
		// 根据类型和第几个子adapter重新计算类型,就算子adapter里的类型有重复的type也会定义为不同的type
		return (int) getCantor(subItemType, index);
	}

果然不出所料,确实有这个方法,这个方法的主要意思就是从已经保存了子Adpter的集合中用折半算法取出子adapter和它注册的数据观察者
AdapterDataObserver,然后通过子Adapter的getItemViewType获得子View的类型(通俗的讲就是实现多级不同的列表),如果没有覆写此方法的话type默认为-1,此时直接返回-1,那么RecycleView的缓存策略就不会区分缓存itemView,这里的mHasConsistItemType如果不设置的话默认为true,那么所有的类型必须以子Adpter的type为依据,也就是说,此时如果有两个子Adpter,那么如果它们区分了不同的type,但里面有相同的type值,布局确不一样,那么现在就危险了,因为滑动的时候,缓存保存的数据很可能会覆盖前面的,也就是说可能会出现itemView和position的错位问题,这个时候需把mHasConsistItemType改为false,那么这个时候type会根据第几个子Adpter的索引和子Adapter的type做运算所得,所以,肯定保证子Adapter中的itemView保证缓存独立不会被覆盖。

接着看一下它怎么设置子Adpter的:

/*
	 * 设置ziView的Adapter
	 */
	public void setAdapters(@Nullable List<Adapter> adapters) {
		clear();
		if (adapters == null) {
			adapters = Collections.emptyList();
		}
		List<LayoutHelper> helpers = new LinkedList<>();
		boolean hasStableIds = true;
		mTotal = 0;
		Pair<AdapterDataObserver, Adapter> pair;
		for (Adapter adapter : adapters) {
			// every adapter has an unique index id
			// 创建AdapterDataObserver数据观察者
			// AdapterDataObserver的上一个mTotal末尾为startPosition
			// 每次index累加1
			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(helper);
			pair = Pair.create(observer, adapter);
			// mIndex第几个子Adapter
			mIndexAry.put(observer.mIndex, pair);
			mAdapters.add(pair);

		}
		// (!hasObservers()肯定返回false,因为recycleView默认会创建数据观察者
		if (!hasObservers()) {
			// 所有子adapter的hasStableIds都为true,才为true
			super.setHasStableIds(hasStableIds);
		}
		super.setLayoutHelpers(helpers);
	}

这个方法就是设置子Adpter了,在用这个框架的时候为RecycleView设置为DelegateAdapter ,还要再调用这个方法为它设置子Adpter,而子Adpter真正实现了数据转化为View场景,这里的mTotal 表示的是数据源的数量,它等于所有子View的getCount之和,AdapterDataObserver 是为每个子Adpter添加数据观察者,最后创建子Adpter的布局管理类,并把它们缓存起来,用来二分查找等。

接下来看一下RecycleView.Adapter最重要的两个实现方法,数据绑定和和创建ViewHodler

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
		Pair<AdapterDataObserver, Adapter> pair = findAdapterByPosition(position);
		if (pair == null) {
			return;
		}

		pair.second.onBindViewHolder(holder, position
				- pair.first.mStartPosition);
		pair.second.onBindViewHolderWithOffset(holder, position
				- pair.first.mStartPosition, position);
	}

这个方法为数据绑定,看的出来最终的绑定数据还是用的子adpter的onBindViewHolder来实现,这里的子Adpter,框架为它扩展了一个onBindViewHolderWithOffset方法,稍后再介绍它的用法。

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
			int viewType) {

		if (mHasConsistItemType) {
			//如果子类没有设置viewtype
			Adapter adapter = mItemTypeAry.get(viewType);
			if (adapter != null) {
				return adapter.onCreateViewHolder(parent, viewType);
			}

			return null;
		}

		// reverse Cantor Function
		//Math.floor四舍五入,Math.sqrt求平方根
		int w = (int) (Math.floor(Math.sqrt(8 * viewType + 1) - 1) / 2);
		int t = (w * w + w) / 2;

		int index = viewType - t;
		int subItemType = w - index;

		Adapter adapter = findAdapterByIndex(index);
		if (adapter == null) {
			return null;
		}

		return adapter.onCreateViewHolder(parent, subItemType);
	}

这个方法就是创建ViewHoder了,默认mHasConsistItemType为true时,直接从缓存类里取出子Adpter并调用它的子Adpter来创建ViewHodler,如果子Adpter没有覆写getViewType方法的话,它将利用反向康托尔算法计算出近似值取出子Adpter,并进行创建ViewHodler

其实这个算法就是

private static long getCantor(long k1, long k2) {
		return (k1 + k2) * (k1 + k2 + 1) / 2 + k2;
	}

此算法的逆向

好了,看完DelegateAdapter的这几个重要的方法,我们暂且已经能得出此适配器并没有实现真正意义上的获得子itemView,它只做一个中转工作,所有的工作全部交给子Adpter来做,也就是所有工作交给客户端自己来确定到底使用什么样的布局,当然这也是适配器模式的好处,也就是说此DelegateAdapter采用了委托者模式,所有的数据绑定,布局工作它不管,全部委托给子Adpter来处理。其实原理和通用的适配器原理一样,适配器只做管理和公共交互部分,将数据和ui绑定交给被委托者处理,从而实现代码分离。

好了既然数据怎么绑定怎么分离的我们看完了,那么接下来就看此框架到底是怎么排版的,如果你熟练使用RecycleView的话一定知道RecycleView没有实现怎么布局子View而是把工作都交给了LayoutManager的子类来处理,像LinearLayoutManager水平或垂直排版子View等。这里vlayout框架用了VirtualLayoutManager。好的,那么就进入看看它到底是干什么的

public class VirtualLayoutManager extends ExposeLinearLayoutManagerEx implements
		LayoutManagerHelper

它继承与
ExposeLinearLayoutManagerEx ,并实现了LayoutManagerHelper接口,看来它们就是重点了,接着进入ExposeLinearLayoutManagerEx 看一下

class ExposeLinearLayoutManagerEx extends LinearLayoutManager 

哈哈,这不是我们所熟悉的
LinearLayoutManager 吗,一般要自定义LayoutMannager的话基本上重写一些方法就ok

如果要自定义布局属性的话重写它
public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
			return new LayoutParams(c, attrs);
		}
//默认布局属性必须重写
public abstract LayoutParams generateDefaultLayoutParams();
		
		public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
			return 0;
		}
//垂直滚动时调用
		public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
			return 0;
		}
//水平滚动时调用
	public int scrollHorizontallBy(int dy, Recycler recycler, State state) {
			return 0;
		}

		//只有这个方法返回true的时候才可以调用scrollHorizontallBy方法
		public boolean canScrollHorizontally() {
			return false;
		}

		//是否可以垂直滚动,只有它返回true时,方法scrollVerticallyBy才会调用
		public boolean canScrollVertically() {
			return false;
		}

		//滚动到确定的位置
		public void scrollToPosition(int position) {
			
			}
		}
//平滑滚动到某个位置
		public void smoothScrollToPosition(RecyclerView recyclerView,
				State state, int position) {
		
		}

好,OnLayoutChild肯定会被实现,因为只是自定义LayoutManager最重要的方法,找一下

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
		}
//此处省略若干行
		
	}

不出所料这个方法的确被实现,现在可以大体的看出来,此框架应该是把LayoutManager该干的布局那部分给拆出去了,那么验证猜测继续


public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        这里省略若干行,基本上是一些参数判断和赋值,比如是否数据改变了,是否有滑动偏移值
        int startOffset;
        int endOffset;
        onAnchorReady(state, mAnchorInfo);
        //释放回收adapter
        detachAndScrapAttachedViews(recycler);
        mLayoutState.mIsPreLayout = state.isPreLayout();
        mLayoutState.mOnRefresLayout = true;
//从底部开始布局
        if (mAnchorInfo.mLayoutFromEnd) {
            // fill towards start
            updateLayoutStateToFillStartExpose(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
            if (mLayoutState.mAvailable > 0) {
                extraForEnd += mLayoutState.mAvailable;
            }
            // fill towards end
            updateLayoutStateToFillEndExpose(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
        } else {
            // fill towards end
            updateLayoutStateToFillEndExpose(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
//开始填充数据
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
            if (mLayoutState.mAvailable > 0) {
                extraForStart += mLayoutState.mAvailable;
            }
            // fill towards start
            updateLayoutStateToFillStartExpose(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
//此处再省略若干行
        }
    }

假如没有设置锚点或者没有设置从底部开始布局子View或者没有设置
mLayoutFromEnd=true,那么默认从顶部开始布局,在开始填充之前先对以前的子View进行全部回收,那么继续看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;
            }
        }
        if (DEBUG) {
            validateChildOrderExpose();
        }
        return start - layoutState.mAvailable;
    }

这个方法的核心思想就是计算RecycleView的可以使用的空间大小,根据这个限制不断的排版子View与添加子View,直到排到边界为止,最终通过
layoutChunk安排子View的位置

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进行真正的布局,找到adapter对应的LayoutHelper交给它进行布局
		LayoutHelper layoutHelper = mHelperFinder == null ? null
				: mHelperFinder.getLayoutHelper(position);
		if (layoutHelper == null)
			layoutHelper = mDefaultLayoutHelper;

		layoutHelper.doLayout(recycler, state, mTempLayoutStateWrapper, result,
				this);

	此处省略若干行…
	}

进入这个方法之后发现它没有实现真正的layout的而是把布局交给子Adapter绑定的LayoutHelper 来进行布局,也就是真正的确定子View的位置排列的并不是LayoutManager,而是LayoutHelper
类,再回到例子代码


if (FLOAT_LAYOUT) {
			FloatLayoutHelper layoutHelper = new FloatLayoutHelper();
			layoutHelper.setAlignType(FixLayoutHelper.BOTTOM_RIGHT);
			layoutHelper.setDefaultLocation(100, 400);
			LayoutParams layoutParams = new LayoutParams(150, 150);
			adapters.add(new SubAdapter(this, layoutHelper, 1, layoutParams));
		}

每次加载子Adpter的时候都为它绑定一个LayoutHelper,实现真正意义的位置确定,今天就分析到这

    原文作者:飞雨的夏天
    原文地址: https://blog.csdn.net/xiatiandefeiyu/article/details/78246706
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞