vlayout是阿里的一个开源ui框架,是一个实现多样的item的列表的神器,相信大多数开发者对于vlayout内部的实现原理不太了解。本篇文章就和大家一起探讨下valyout的内部原理:
用过vlayout的同学都知道他的主要几个关键类,首先是各种layoutHelper的实现类,VirtualLayoutManager,DelegateAdapter
阅读过RecycleView源码的同学都知道,layoutManager是一个核心的内部类,recycleView把添加到其中的view都是通过 layoutManager进行布局,当用户调用setLayoutmanager方法之后,就会触发requestLayout方法
/**
* Set the {@link LayoutManager} that this RecyclerView will use.
*
* <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
* or {@link android.widget.GridView}, RecyclerView allows client code to provide custom
* layout arrangements for child views. These arrangements are controlled by the
* {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p>
*
* <p>Several default strategies are provided for common uses such as lists and grids.</p>
*
* @param layout LayoutManager to use
*/
public void setLayoutManager(LayoutManager layout) {
if (layout == mLayout) {
return;
}
stopScroll();
// TODO We should do this switch a dispatchLayout pass and animate children. There is a good
// chance that LayoutManagers will re-use views.
if (mLayout != null) {
// end all running animations
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
mRecycler.clear();
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
mRecycler.clear();
}
// this is just a defensive measure for faulty item animators.
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout
+ " is already attached to a RecyclerView: " + layout.mRecyclerView);
}
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();
requestLayout();
}
requestLayout的调用势必就会触发recycle的onlayout方法,我们看下onLayout的源码
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Trace.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
Trace.endSection();
mFirstLayoutComplete = true;
}
我们可以看到在onlayout方法中又调用了dispatchLaout方法,我们接着往下看
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
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();
}
在dispatchLaout方法,我们只关注一个比较重要的方法,dispatchLayoutStep2,我们看下这个方法
private void dispatchLayoutStep2() {
eatRequestLayout();
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();
resumeRequestLayout(false);
}
我们可以看到在dispatchLayoutStep2方法中的 mLayout.onLayoutChildren(mRecycler, mState);
mlayout就是我们LayoutManager的一个引用,这里相信大家可以明白,recycleView调用setLayoutManager之后,最终是把view的布局交给了LayoutManager去管理。
对RecycleView的原理有初步了解后,我们就来看vLayout的原理,文章开头,介绍了一个VirtualLayoutManager,这个类是LinearManager的子类,我们用过vlayout的同学应该知道,vlayout是一个线性方向的多样式布局;
其中就是通过这个VirtualLayoutManager关键类来实现各种样式item的管理;
public class VirtualLayoutManager extends ExposeLinearLayoutManagerEx implements LayoutManagerHelper {
class ExposeLinearLayoutManagerEx extends LinearLayoutManager {
我们可以看到virtualLayoutManager是linearlayoutMangaer的间接子类
通过对recycleview的分析,我们也应该来找一下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
}
if ((mNestedScrolling || mNoScrolling) && mSpaceMeasuring) {
// measure required, so do measure
mSpaceMeasured = true;
// get last child
int childCount = getChildCount();
View lastChild = getChildAt(childCount - 1);
if (lastChild != null) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) lastChild.getLayoutParams();
// found the end of last child view
mMeasuredFullSpace = getDecoratedBottom(lastChild) + params.bottomMargin + computeAlignOffset(lastChild, true, false);
if (mRecyclerView != null && mNestedScrolling) {
ViewParent parent = mRecyclerView.getParent();
if (parent instanceof View) {
// make sure the fullspace be the min value of measured space and parent's height
mMeasuredFullSpace = Math.min(mMeasuredFullSpace, ((View) parent).getMeasuredHeight());
}
}
} else {
mSpaceMeasuring = false;
}
mSpaceMeasuring = false;
if (mRecyclerView != null && getItemCount() > 0) {
// relayout
mRecyclerView.post(new Runnable() {
@Override
public void run() {
// post relayout
if (mRecyclerView != null)
mRecyclerView.requestLayout();
}
});
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
Trace.endSection();
}
}
大家可以看到我加的注释 //关键的一步
当VirtualLayoutManager的onLayoutChildren方法执行后,他会调用 super.onLayoutChildren(recycler, state)
实际上就是调用了他的父类ExposeLinearLayoutManagerEx的onLayoutChildren方法
*/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// 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
if (DEBUG) {
Log.d(TAG, "is pre layout:" + state.isPreLayout());
}
if (mCurrentPendingSavedState != null && mCurrentPendingSavedState.getInt("AnchorPosition") >= 0) {
mCurrentPendingScrollPosition = mCurrentPendingSavedState.getInt("AnchorPosition");
}
ensureLayoutStateExpose();
mLayoutState.mRecycle = false;
// resolve layout direction
myResolveShouldLayoutReverse();
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayoutExpose ^ getStackFromEnd();
// calculate anchor position and coordinate
updateAnchorInfoForLayoutExpose(state, mAnchorInfo);
if (DEBUG) {
Log.d(TAG, "Anchor info:" + mAnchorInfo);
}
// LLM may decide to layout items for "extra" pixels to account for scrolling target,
// caching or predictive animations.
int extraForStart;
int extraForEnd;
final int extra = getExtraLayoutSpace(state);
boolean before = state.getTargetScrollPosition() < mAnchorInfo.mPosition;
if (before == mShouldReverseLayoutExpose) {
extraForEnd = extra;
extraForStart = 0;
} else {
extraForStart = extra;
extraForEnd = 0;
}
extraForStart += mOrientationHelper.getStartAfterPadding();
extraForEnd += mOrientationHelper.getEndPadding();
if (state.isPreLayout() && mCurrentPendingScrollPosition != RecyclerView.NO_POSITION &&
mPendingScrollPositionOffset != INVALID_OFFSET) {
// if the child is visible and we are going to move it around, we should layout
// extra items in the opposite direction to make sure new items animate nicely
// instead of just fading in
final View existing = findViewByPosition(mCurrentPendingScrollPosition);
if (existing != null) {
final int current;
final int upcomingOffset;
if (mShouldReverseLayoutExpose) {
current = mOrientationHelper.getEndAfterPadding() -
mOrientationHelper.getDecoratedEnd(existing);
upcomingOffset = current - mPendingScrollPositionOffset;
} else {
current = mOrientationHelper.getDecoratedStart(existing)
- mOrientationHelper.getStartAfterPadding();
upcomingOffset = mPendingScrollPositionOffset - current;
}
if (upcomingOffset > 0) {
extraForStart += upcomingOffset;
} else {
extraForEnd -= upcomingOffset;
}
}
}
int startOffset;
int endOffset;
onAnchorReady(state, mAnchorInfo);
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;
在这里调用了最关键的一步,也就是mananger开始真正布局的逻辑,
fill(recycler, mLayoutState, state, false);大家可以去看fill方法的源码,它里面实际上是调用了
VirtualLayoutManager的layoutChunk(recycler, state, layoutState, layoutChunkResultCache);方法,我们来看下这个方法
@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;
大家看到熟悉的layoutHelper类,doLayout方法中会调用layoutViews(recycler, state, layoutState, result, helper);
这个方法是一个空实现,他会把具体的实现就给vlayout支持的八大布局的子类去实现,比如
LinearLayoutHelper和GridLayoutHelper等等,我们来看下LinearLayoutHelper的layoutViews方法
public void layoutViews(RecyclerView.Recycler recycler, RecyclerView.State state,
VirtualLayoutManager.LayoutStateWrapper layoutState, LayoutChunkResult result,
LayoutManagerHelper helper) {
// reach the end of this layout
if (isOutOfRange(layoutState.getCurrentPosition())) {
return;
}
int currentPosition = layoutState.getCurrentPosition();
// find corresponding layout container
View view = nextView(recycler, layoutState, helper, result);
if (view == null) {
return;
}
VirtualLayoutManager.LayoutParams params = (VirtualLayoutManager.LayoutParams) view.getLayoutParams();
final boolean layoutInVertical = helper.getOrientation() == VERTICAL;
int startSpace = 0, endSpace = 0, gap = 0;
boolean isLayoutEnd = layoutState.getLayoutDirection() == VirtualLayoutManager.LayoutStateWrapper.LAYOUT_END;
boolean isStartLine = isLayoutEnd
? currentPosition == getRange().getLower().intValue()
: currentPosition == getRange().getUpper().intValue();
boolean isEndLine = isLayoutEnd
? currentPosition == getRange().getUpper().intValue()
: currentPosition == getRange().getLower().intValue();
if (isStartLine) {
startSpace = layoutInVertical
? (isLayoutEnd ? mMarginTop + mPaddingTop : mMarginBottom + mPaddingBottom)
: (isLayoutEnd ? mMarginLeft + mPaddingLeft : mMarginRight + mPaddingRight);
}
if (isEndLine) {
endSpace = layoutInVertical
? (isLayoutEnd ? mMarginBottom + mPaddingBottom : mMarginTop + mPaddingTop)
: (isLayoutEnd ? mMarginRight + mPaddingRight : mMarginLeft + mPaddingLeft);
}
if (!isStartLine) {
gap = mDividerHeight;
}
final int widthSize = helper.getContentWidth() - helper.getPaddingLeft() - helper
.getPaddingRight() - getHorizontalMargin() - getHorizontalPadding();
int widthSpec = helper.getChildMeasureSpec(widthSize, params.width, !layoutInVertical);
int heightSpec;
float viewAspectRatio = params.mAspectRatio;
if (!Float.isNaN(viewAspectRatio) && viewAspectRatio > 0) {
heightSpec = View.MeasureSpec.makeMeasureSpec((int) (widthSize / viewAspectRatio + 0.5f),
View.MeasureSpec.EXACTLY);
} else if (!Float.isNaN(mAspectRatio) && mAspectRatio > 0) {
heightSpec = View.MeasureSpec.makeMeasureSpec((int) (widthSize / mAspectRatio + 0.5),
View.MeasureSpec.EXACTLY);
} else {
heightSpec = helper.getChildMeasureSpec(
helper.getContentHeight() - helper.getPaddingTop() - helper.getPaddingBottom()
- getVerticalMargin() - getVerticalPadding(), params.height,
layoutInVertical);
}
helper.measureChild(view, widthSpec, heightSpec);
OrientationHelper orientationHelper = helper.getMainOrientationHelper();
result.mConsumed = orientationHelper.getDecoratedMeasurement(view) + startSpace + endSpace + gap;
int left, top, right, bottom;
if (helper.getOrientation() == VERTICAL) {
// not support RTL now
if (helper.isDoLayoutRTL()) {
right = helper.getContentWidth() - helper.getPaddingRight() - mMarginRight - mPaddingRight;
left = right - orientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = helper.getPaddingLeft() + mMarginLeft + mPaddingLeft;
right = left + orientationHelper.getDecoratedMeasurementInOther(view);
}
// whether this layout pass is layout to start or to end
if (layoutState.getLayoutDirection() == VirtualLayoutManager.LayoutStateWrapper.LAYOUT_START) {
// fill start, from bottom to top
bottom = layoutState.getOffset() - startSpace - (isStartLine ? 0 : mDividerHeight);
top = bottom - orientationHelper.getDecoratedMeasurement(view);
} else {
// fill end, from top to bottom
top = layoutState.getOffset() + startSpace + (isStartLine ? 0 : mDividerHeight);
bottom = top + orientationHelper.getDecoratedMeasurement(view);
}
} else {
top = helper.getPaddingTop() + mMarginTop + mPaddingTop;
bottom = top + orientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.getLayoutDirection() == VirtualLayoutManager.LayoutStateWrapper.LAYOUT_START) {
// fill left, from right to left
right = layoutState.getOffset() - startSpace - (isStartLine ? 0 : mDividerHeight);
left = right - orientationHelper.getDecoratedMeasurement(view);
} else {
// fill right, from left to right
left = layoutState.getOffset() + startSpace + (isStartLine ? 0 : mDividerHeight);
right = left + orientationHelper.getDecoratedMeasurement(view);
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
//关键步骤
layoutChild(view, left, top, right, bottom, helper);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + helper.getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
}
handleStateOnResult(result, view);
}
看到这里相信大家应该能明白,layouthelper和 VirtualLayoutManager之间的关系了
RecycleView把view的事件交互都交给了VirtualLayoutManager去实现,把布局最终交给了layouthelper辅助类去实现。
现在最后还剩下一个DelegateAdapter ,这个类就是一个适配器的管理者,他管理所有的和layouthelper一起传递到DelegateAdapter里面的子Adapter,从而实现数据源和item的绑定,实现数据的刷新。DelegateAdapter的源码逻辑不复杂,这里我就不带大家看了,有兴趣的朋友可以研究下。