先看一下阿里对这个框架留下的Demo的效果:
看效果大体的可以猜测这个框架给我们提供了很多布局规则,据说淘宝首页就是用这个框架做的。源码地址
好接下来我们就沿着这个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,实现真正意义的位置确定,今天就分析到这