基于 Android N 分析下 View 的 layout,N 虽然看起来略有些旧,但是框架的核心思想才是最重要的,新的一天开始了。
1 回顾 我们来回顾下,performLayout 请求布局的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 private void performLayout (WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false ; mScrollMayChange = true ; mInLayout = true ; final View host = mView; if (host == null ) { return ; } if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(mTag, "Laying out " + host + " to (" + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")" ); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout" ); try { host.layout(0 , 0 , host.getMeasuredWidth(), host.getMeasuredHeight()); mInLayout = false ; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0 ) { ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false ); if (validLayoutRequesters != null ) { mHandlingLayoutInLayoutRequest = true ; int numValidRequests = validLayoutRequesters.size(); for (int i = 0 ; i < numValidRequests; ++i) { final View view = validLayoutRequesters.get(i); Log.w("View" , "requestLayout() improperly called by " + view + " during layout: running second layout pass" ); view.requestLayout(); } measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); mInLayout = true ; host.layout(0 , 0 , host.getMeasuredWidth(), host.getMeasuredHeight()); mHandlingLayoutInLayoutRequest = false ; validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true ); if (validLayoutRequesters != null ) { final ArrayList<View> finalRequesters = validLayoutRequesters; getRunQueue().post(new Runnable() { @Override public void run () { int numValidRequests = finalRequesters.size(); for (int i = 0 ; i < numValidRequests; ++i) { final View view = finalRequesters.get(i); Log.w("View" , "requestLayout() improperly called by " + view + " during second layout pass: posting in next frame" ); view.requestLayout(); } } }); } } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false ; }
可以看到,在测量后布局阶段:
1 2 host.layout(0 , 0 , host.getMeasuredWidth(), host.getMeasuredHeight());
是通过 view 的 layout 方法出发布局的;
2 ViewGroup 2.1 layout - 核心 DecorView 是一个 ViewGroup,所以会先执行 ViewGroup 的 layout:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public final void layout (int l, int t, int r, int b) { if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) { if (mTransition != null ) { mTransition.layoutChange(this ); } super .layout(l, t, r, b); } else { mLayoutCalledWhileSuppressed = true ; } }
ViewGroup -> View
3 View 3.1 layout - 核心 执行布局,参数是左上角,右下角的测量坐标,是相对于 parent 的;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @SuppressWarnings ({"unchecked" })public void layout (int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0 ) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) { if (mRoundScrollbarRenderer == null ) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this ); } } else { mRoundScrollbarRenderer = null ; } mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null ) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0 ; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this , l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }
所以这里的核心的方法就是执行了 onLayout 方法:
3.2 onLayout - 核心 可以看到 view 的 onLayout 方法是一个空的实现!!
当 View 需要为每个 child view 分配大小和位置时,会通过 layout 方法调用。
有 child 的 ViewGroup 要重写该方法,并在每个子级上调用布局。
参数分析:
boolean changed:表示 VIew 的大小或位置是否发生了变化;
int left:相对于父级的左侧位置
int top:相对于父级的顶部位置
int right:相对于父级的右侧位置
int bottom:相对于父级的底部位置
1 2 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {}
不多说;
3.3 getLayoutDirection 用于获取水平的布局方向:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @ViewDebug .ExportedProperty(category = "layout" , mapping = { @ViewDebug .IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR" ), @ViewDebug .IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL" ) }) @ResolvedLayoutDir public int getLayoutDirection () { final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; if (targetSdkVersion < JELLY_BEAN_MR1) { mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED; return LAYOUT_DIRECTION_RESOLVED_DEFAULT; } return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) == PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR; }
返回值是通过 setLayoutDirection 方法或者 android:layoutDirection 设置的纸;
1 2 3 4 5 6 7 8 public static final int LAYOUT_DIRECTION_LTR = LayoutDirection.LTR;public static final int LAYOUT_DIRECTION_RTL = LayoutDirection.RTL;static final int LAYOUT_DIRECTION_RESOLVED_DEFAULT = LAYOUT_DIRECTION_LTR;
4 DecorView 4.1 onLayout - 核心 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Override protected void onLayout (boolean changed, int left, int top, int right, int bottom) { super .onLayout(changed, left, top, right, bottom); getOutsets(mOutsets); if (mOutsets.left > 0 ) { offsetLeftAndRight(-mOutsets.left); } if (mOutsets.top > 0 ) { offsetTopAndBottom(-mOutsets.top); } if (mApplyFloatingVerticalInsets) { offsetTopAndBottom(mFloatingInsets.top); } if (mApplyFloatingHorizontalInsets) { offsetLeftAndRight(mFloatingInsets.left); } updateElevation(); mAllowUpdateElevation = true ; if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) { getViewRootImpl().requestInvalidateRootRenderNode(); } }
继续看~
5 FrameLayout 5.1 onLayout - 核心 我们看到 child layout 实际上是在 FrameLayout 中发起:
1 2 3 4 5 @Override protected void onLayout (boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false ); }
5.2 layoutChildren - 核心 对 child 进行布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 void layoutChildren (int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0 ; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1 ) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break ; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break ; } case Gravity.LEFT: default : childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break ; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break ; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break ; default : childTop = parentTop + lp.topMargin; } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
DEFAULT_CHILD_GRAVITY 的取值如下:
1 private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
对于 Gravity 的 TOP BOTTOM LEFT RIGHT 取值如下:
1 2 3 4 5 6 7 8 public static final int TOP = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_Y_SHIFT;public static final int BOTTOM = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_Y_SHIFT;public static final int LEFT = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_X_SHIFT;public static final int RIGHT = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_X_SHIFT;
所以可以根据上看的 gravity & Gravity.VERTICAL_GRAVITY_MASK 操作获取的是什么了:
1 2 3 4 5 6 public static final int HORIZONTAL_GRAVITY_MASK = (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_X_SHIFT; public static final int VERTICAL_GRAVITY_MASK = (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_Y_SHIFT;
不多说了~
6 Gravity 6.1 getAbsoluteGravity 将相对对齐转为绝对对齐,根据 RTL 或者 LTR 进行设置:
如果水平方向为 LTR,则 START 将设置 LEFT,而 END 将设置 RIGHT。
如果水平方向是 RTL,则 START 将设置 RIGHT,而 END 将设置 LEFT。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static int getAbsoluteGravity (int gravity, int layoutDirection) { int result = gravity; if ((result & RELATIVE_LAYOUT_DIRECTION) > 0 ) { if ((result & Gravity.START) == Gravity.START) { result &= ~START; if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { result |= RIGHT; } else { result |= LEFT; } } else if ((result & Gravity.END) == Gravity.END) { result &= ~END; if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { result |= LEFT; } else { result |= RIGHT; } } result &= ~RELATIVE_LAYOUT_DIRECTION; } return result; }
这里的 gravity 是 android:layout_gravity 的取值,就是相对 parent 的布局比重;
我们在设置 view 的 android:gravity=”” 的时候,会设置比如 “left|bottom” 这样的值,那么这些值具体的意思是什么呢?
left 和 right 代表一种绝对的对齐,而 start 和 end 表示基于阅读顺序的对齐。
如何理解呢,我们可以通过 android:layoutDirection=”rtl” 来设置视图的排列顺序,对于阿拉伯语的话,需要 RTL 的显示方式的。
当使用 left 的时候,无论是 LTR 还是 RTL,总是左对齐的;
而使用 start,在 LTR 中是左对齐,而在 RTL 中则是右对齐。
7 总结 我们用一张图直观的看下 layout 的流程: