本文章讲解的内容是深入了解Android的View工作原理,建议对着示例项目阅读文章,示例项目链接如下:
本文章分析的相关的源码基于Android SDK 29(Android 10.0,即Android Q)。
由于掘金文章字数限制,分为两篇发布:
绘制流程
绘制流程从**requestLayout()**方法开始,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 调用checkThread()方法
checkThread();
mLayoutRequested = true;
// 调用scheduleTraversals()方法
scheduleTraversals();
}
}
checkThread()方法的作用是检查当前线程是不是创建ViewRootImpl所在的线程,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) {
// 如果当前线程不是创建ViewRootImpl所在的线程就抛出CalledFromWrongThreadException异常
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
scheduleTraversals()方法的作用是让Android系统优先执行跟View更新相关的异步消息,优先处理跟View更新相关的逻辑,在深入了解Android消息机制和源码分析(Java层和Native层)(上)和深入了解Android消息机制和源码分析(Java层和Native层)(下)这两篇文章有提及过,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
// 省略部分代码
final Thread mThread;
// 省略部分代码
public ViewRootImpl(Context context, Display display) {
// 得到当前线程
mThread = Thread.currentThread();
}
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 添加同步屏障消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 执行添加同步屏障消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// 省略部分代码
// 创建TraversalRunnable对象
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
// 省略部分代码
}
成员变量mTraversalRunnable是TraversalRunnable类型,TraversalRunnable是ViewRootImpl的被关键字final的内部类,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 调用doTraversal()方法
doTraversal();
}
}
看下**doTraversal()**方法,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 删除同步屏障消息
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 调用performTraversals()方法
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
看下**performTraversals()**方法,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() {
// 省略部分代码
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
// 省略部分代码
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// 调用performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)方法,分别传入的是根视图的宽的MeasureSpec和高的MeasureSpec
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 实现WindowManager.LayoutParams中的weight,根据需要增加尺寸,并且在需要时重新测量
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
// 如果需要重新测量,就再次调用performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)方法,分别传入的是根视图的宽的MeasureSpec和高的MeasureSpec
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
// 省略部分代码
}
// 省略部分代码
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 调用performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)方法
performLayout(lp, mWidth, mHeight);
// 省略部分代码
}
// 省略部分代码
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
// 省略部分代码
// 调用performDraw()方法
performDraw();
} else {
if (isViewVisible) {
// 如果取消draw,同时View是可见的,就再次调用scheduleTraversals()方法
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
// 省略部分代码
}
View的绘制三大流程分别从**performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)**方法、**performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)方法和performDraw()**方法开始。
measure流程
MeasureSpec是View类的静态内部类,它代表了一个32位的int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(在某个测量模式下的规格大小),源码如下所示:
// View.java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
// 省略部分代码
}
SpecMode分为三种模式,如下所示:
- UNSPECIFIED:父元素没有对子元素添加任何约束,它可以是任何大小,这个模式一般是在系统内部使用。
- EXACTLY:父元素已经确定了子元素的精确大小,也就是子元素的最终大小由SpecSize的值决定,对应于LayoutParams中的match_parent和具体数值这两种模式。
- AT_MOST:子元素的大小不能大于父元素的SpecSize的值,子元素的默认大小,对应于LayoutParams中的wrap_content。
View的MeasureSpec是由父元素的MeasureSpec和自身的LayoutParams共同决定的。
measure流程从ViewRootImpl类的**performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)**方法开始,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 调用View的measure(int widthMeasureSpec, int heightMeasureSpec)方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
看下View类的**measure(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:
// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 省略部分代码
if (forceLayout || needsLayout) {
// 省略部分代码
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 调用onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
// 省略部分代码
}
// 省略部分代码
}
// 省略部分代码
}
看下**onMeasure(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:
// View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
开发者可以重写这个方法来改变测量View的逻辑。
先看下**getSuggestedMinimumWidth()方法和getSuggestedMinimumHeight()**方法,源码如下所示:
// View.java
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
getSuggestedMinimumWidth()方法返回的是View的建议的最小宽度,如果View没有背景,就返回minWidth,否则就返回背景的最小宽度。
getSuggestedMinimumHeight()方法返回的是View的建议的最小高度,如果View没有背景,就返回minHeight,否则就返回背景的最小高度。
然后看下**getDefaultSize(int size, int measureSpec)**方法,源码如下所示:
// View.java
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
// 得到测量模式
int specMode = MeasureSpec.getMode(measureSpec);
// 得到在这种测量模式下的规格大小
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
最后看下**setMeasuredDimension(int measuredWidth, int measuredHeight)**方法,源码如下所示:
// View.java
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
// 得到这个View的测量宽度
measuredWidth += optical ? opticalWidth : -opticalWidth;
// 得到这个View的测量高度
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
// 调用setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)方法
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
看下**setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)**方法,源码如下所示:
// View.java
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
View的getMeasuredWidth()方法使用到成员变量mMeasuredWidth的值,它的作用是返回原始的测量宽度,View的getMeasuredHeight()方法使用到成员变量mMeasuredHeight的值,它的作用是返回原始的测量高度,源码如下所示:
// View.java
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
前面看的是View的measure过程,接下来看下ViewGroup的measure过程,看下ViewGroup类的**measureChildren(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:
// View.java
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
// 循环执行
for (int i = 0; i < size; ++i) {
// 得到ViewGroup中的子元素
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
// 如果子元素的状态不是隐藏状态,也就是可见状态(VISIBLE)或者不可见状态(INVISIBLE),就测量子元素,调用measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)方法
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
看下**measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)**方法,源码如下所示:
// View.java
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 调用View的measure(int widthMeasureSpec, int heightMeasureSpec)方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
例子
最后看下一个例子:LinearLayout,它重写了**onMeasure(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:
// LinearLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
// 如果是垂直方向,就调用measureVertical(int widthMeasureSpec, int heightMeasureSpec)方法
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
// 如果是水平方向,就调用measureHorizontal(int widthMeasureSpec, int heightMeasureSpec)方法
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
这里只看垂直方向的这种情况,看下**measureVertical(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:
// LinearLayout.java
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// mTotalLength的值是所有子元素的高度加paddingTop和paddingBottom,要注意的是,它和LinearLayout本身的高度不同
mTotalLength = 0;
// 所有子元素的最大宽度
int maxWidth = 0;
int childState = 0;
// 所有layout_weight属性的值小于等于0的子元素中宽度的最大值
int alternativeMaxWidth = 0;
// 所有layout_weight属性的值大于0的子元素中宽度的最大值
int weightedMaxWidth = 0;
boolean allFillParent = true;
// 所有子元素的weight之和
float totalWeight = 0;
final int count = getVirtualChildCount();
// 得到宽度的测量模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// 得到高度的测量模式
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
int consumedExcessSpace = 0;
int nonSkippedChildCount = 0;
// 循环执行
for (int i = 0; i < count; ++i) {
// 得到子元素
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
// 省略部分代码
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
// 调用measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight)方法
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
// 省略部分代码
}
// 省略部分代码
}
// 省略部分代码
// mTotalLength再加上paddingTop和paddingBottom之和
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// 取高度大小和建议最小高度的最大值
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// 省略部分代码
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
// maxWidth再加上paddingLeft和paddingRight之和
maxWidth += mPaddingLeft + mPaddingRight;
// 取最大宽度和建议最小宽度的最大值
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 调用setMeasuredDimension(int measuredWidth, int measuredHeight)方法
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight)方法的作用是测量LinearLayout中子元素,让这些子元素执行measure流程,同时系统通过成员变量mTotalLength来存储LinearLayout在垂直方向的初步高度,每测量一个子元素,都会使成员变量mTotalLength加上子元素的高度和子元素在垂直方向上的margin属性和padding属性。
layout流程
layout流程从ViewRootImpl类的**performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)**方法开始,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// 省略部分代码
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 {
// 调用View的layout(int l, int t, int r, int b)方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// 省略部分代码
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
调用layout(int l, int t, int r, int b)方法,并且left的位置传入0,top的位置传入0,right的位置传入测量宽度,bottom的位置传入测量高度,源码如下所示:
// View.java
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
// 省略部分代码
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(boolean changed, int left, int top, int right, int bottom)方法
onLayout(changed, l, t, r, b);
// 省略部分代码
}
// 省略部分代码
}
看下isLayoutModeOptical(Object o)方法,它的作用是如果是传进来是一个使用光学边界布局的ViewGroup,就返回true,否则就返回false,源码如下所示:
// View.java
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
先看下**setOpticalFrame(int left, int top, int right, int bottom)**方法,源码如下所示:
// View.java
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
// 调用setFrame(int left, int top, int right, int bottom)方法
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
然后看下setFrame(int left, int top, int right, int bottom)方法,这个方法的作用是确定View的四个顶点位置,也就是确定了子元素在父元素中的位置,源码如下所示:
// View.java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// 使旧的位置无效
invalidate(sizeChanged);
// 将left赋值给成员变量mLeft
mLeft = left;
// 将top赋值给成员变量mTop
mTop = top;
// 将right赋值给成员变量mRight
mRight = right;
// 将bottom赋值给成员变量mBottom
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
// 省略部分代码
}
return changed;
}
View的getWidth()的值就是使用mRight的值减mLeft的值,View的getHeight()的值就是使用mBottom的值减mTop的值,源码如下所示:
// View.java
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() {
return mBottom - mTop;
}
接着上面,看下**onLayout(boolean changed, int left, int top, int right, int bottom)**方法,源码如下所示:
// View.java
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
开发者可以重写这个方法来改变布局View的逻辑。
例子
最后看下一个例子:LinearLayout,它重写了**onLayout(boolean changed, int l, int t, int r, int b)**方法,源码如下所示:
// LinearLayout.java
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
// 如果是垂直方向,就调用layoutVertical(int left, int top, int right, int bottom)方法
layoutVertical(l, t, r, b);
} else {
// 如果是垂直方向,就调用layoutHorizontal(int left, int top, int right, int bottom)方法
layoutHorizontal(l, t, r, b);
}
}
这里只看垂直方向的这种情况,看下**layoutVertical(int left, int top, int right, int bottom)**方法,源码如下所示:
// LinearLayout.java
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// 父元素的默认宽度
final int width = right - left;
// 子元素默认的right的值
int childRight = width - mPaddingRight;
// 子元素的可用空间
int childSpace = width - paddingLeft - mPaddingRight;
// 子元素的数量
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
// 根据设置的gravity属性,设置第一个子元素的top的值
switch (majorGravity) {
case Gravity.BOTTOM:
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
// 循环执行
for (int i = 0; i < count; i++) {
// 得到子元素
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
// 得到子元素的测量宽度
final int childWidth = child.getMeasuredWidth();
// 得到子元素的测量高度
final int childHeight = child.getMeasuredHeight();
// 得到子元素的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
// 根据子元素的gravity属性设置left的值
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
// 如果有分割线,就添加分割线的高度
childTop += mDividerHeight;
}
// 子元素的top的值加上marginTop的值
childTop += lp.topMargin;
// 调用setChildFrame(View child, int left, int top, int width, int height)方法设置子元素在父元素的布局位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
setChildFrame(View child, int left, int top, int width, int height)方法的作用是设置子元素在父元素的布局位置,源码如下所示:
// LinearLayout.java
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
这个方法调用了子元素的layout(int l, int t, int r, int b)方法,使子元素执行layout流程。
draw流程
draw流程从ViewRootImpl类的**performDraw()**方法开始,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
// 省略部分代码
try {
// 调用draw(Canvas canvas)方法
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// 省略部分代码
}
看下**draw(Canvas canvas)**方法,源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
int saveCount;
// 第一步:如果有必要,就画背景
drawBackground(canvas);
// 一般情况下,如果可能的话会跳过第二步和第五步
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// 第三步:画View的内容
onDraw(canvas);
// 第四步:画子元素
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// 覆盖(overlay)是内容的一部分,它在前景下面绘制
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 第六步:绘制装饰(前景、滚动条)
onDrawForeground(canvas);
// 第七步:绘制默认焦点突出显示
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// 执行到这里代表整个绘制流程就执行完成了
return;
}
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// 第二步:如果有必要,就保存画布的图层,准备褪色(fading)
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// 剪辑(clip)褪色长度,如果顶部和底部褪色重叠将会产生奇怪的工件
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// 如果有必要,还可以剪辑(clip)水平渐变
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int topSaveCount = -1;
int bottomSaveCount = -1;
int leftSaveCount = -1;
int rightSaveCount = -1;
int solidColor = getSolidColor();
if (solidColor == 0) {
if (drawTop) {
topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
}
if (drawBottom) {
bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
}
if (drawLeft) {
leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
}
if (drawRight) {
rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// 第三步:画View的内容
onDraw(canvas);
// 第四步:画子元素
dispatchDraw(canvas);
// 第五步:如果有必要,就画褪色的边缘(fading edges)和恢复图层(restore layers)
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
// 必须按照保存的顺序进行恢复
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(rightSaveCount, p);
} else {
canvas.drawRect(right - length, top, right, bottom, p);
}
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(leftSaveCount, p);
} else {
canvas.drawRect(left, top, left + length, bottom, p);
}
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(bottomSaveCount, p);
} else {
canvas.drawRect(left, bottom - length, right, bottom, p);
}
}
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(topSaveCount, p);
} else {
canvas.drawRect(left, top, right, top + length, p);
}
}
canvas.restoreToCount(saveCount);
drawAutofilledHighlight(canvas);
// 覆盖(overlay)是内容的一部分,它在前景下面绘制
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 第六步:绘制装饰(前景和滚动条)
onDrawForeground(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
看下View类的**dispatchDraw(Canvas canvas)**方法,源码如下所示:
// View.java
protected void dispatchDraw(Canvas canvas) {
}
这个方法没有任何逻辑处理,ViewGroup类继承View类,看下ViewGroup类重写了这个方法,源码如下所示:
// ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
// 得到子元素的数量
final int childrenCount = mChildrenCount;
// 得到子元素的数组,也就是View数组
final View[] children = mChildren;
int flags = mGroupFlags;
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
// 循环执行
for (int i = 0; i < childrenCount; i++) {
// 得到子元素
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
// 如果这个View是可见状态,就处理其动画
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
// 省略部分代码
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
// 省略部分代码
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
// 调用drawChild(Canvas canvas, View child, long drawingTime)方法
more |= drawChild(canvas, child, drawingTime);
}
}
// 省略部分代码
}
看下**drawChild(Canvas canvas, View child, long drawingTime)**方法,源码如下所示:
// ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
这个方法调用View的**draw(Canvas canvas)**方法。
总结一下,draw流程分为以下七步:
- 画背景。
- 如果有必要,就保存画布的图层,准备褪色(fading)。
- 画View的内容。
- 画子元素。
- 如果有必要,画褪色的边缘(fading edges)和恢复图层(restore layers)。
- 绘制装饰(前景和滚动条)。
- 绘制默认焦点突出显示。
例子
最后看下一个例子:LinearLayout,它重写了**onDraw(Canvas canvas)**方法,源码如下所示:
// LinearLayout.java
@Override
protected void onDraw(Canvas canvas) {
if (mDivider == null) {
// 如果没有分割线,就结束方法
return;
}
if (mOrientation == VERTICAL) {
// 如果是垂直方向,就调用drawDividersVertical(Canvas canvas)方法
drawDividersVertical(canvas);
} else {
// 如果是水平方向,就调用drawDividersHorizontal(Canvas canvas)方法
drawDividersHorizontal(canvas);
}
}
这里只看垂直方向的这种情况,看下**drawDividersVertical(Canvas canvas)**方法,源码如下所示:
// LinearLayout.java
void drawDividersVertical(Canvas canvas) {
// 得到子元素的虚拟数量
final int count = getVirtualChildCount();
// 循环执行
for (int i = 0; i < count; i++) {
// 得到子元素
final View child = getVirtualChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
// 如果子元素的状态不是隐藏状态,也就是可见状态(VISIBLE)或者不可见状态(INVISIBLE),同时这个View有分割线,就画水平的分割线
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int top = child.getTop() - lp.topMargin - mDividerHeight;
drawHorizontalDivider(canvas, top);
}
}
}
if (hasDividerBeforeChildAt(count)) {
// 得到最后一个不是隐藏状态,也就是可见状态(VISIBLE)或者不可见状态(INVISIBLE)的View
final View child = getLastNonGoneChild();
int bottom = 0;
if (child == null) {
bottom = getHeight() - getPaddingBottom() - mDividerHeight;
} else {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
bottom = child.getBottom() + lp.bottomMargin;
}
// 画水平的分割线
drawHorizontalDivider(canvas, bottom);
}
}
这个方法的作用是画水平的分割线。
在子线程更新UI的问题
先看下如下第一个例子,代码如下所示:
package com.tanjiajun.viewdemo
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
/**
* Created by TanJiaJun on 2020/10/8.
*/
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 创建子线程,并且启动它
Thread {
// 在子线程更新UI,设置id为tv_content的TextView的文本为谭嘉俊
findViewById<TextView>(R.id.tv_content).text = "谭嘉俊"
}.start()
}
}
这段代码是在子线程更新UI,结果很顺利地执行完毕,并且符合预期。
我修改下第一个例子,得到第二个例子,代码如下所示:
package com.tanjiajun.viewdemo
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
/**
* Created by TanJiaJun on 2020/10/8.
*/
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 创建子线程,并且启动它
Thread {
// 让子线程睡眠一秒
Thread.sleep(1000)
// 在子线程更新UI,设置id为tv_content的TextView的文本为谭嘉俊
findViewById<TextView>(R.id.tv_content).text = "谭嘉俊"
}.start()
}
}
这段代码也是在子线程更新UI,并且让子线程睡眠一秒,结果就抛出了如下异常:
2020-10-08 17:02:25.544 8619-8665/com.tanjiajun.viewdemo E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.tanjiajun.viewdemo, PID: 8619
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
at android.view.View.requestLayout(View.java:23093)
at android.widget.TextView.checkForRelayout(TextView.java:8908)
at android.widget.TextView.setText(TextView.java:5730)
at android.widget.TextView.setText(TextView.java:5571)
at android.widget.TextView.setText(TextView.java:5528)
at com.tanjiajun.viewdemo.MainActivity$onCreate$1.run(MainActivity.kt:20)
at java.lang.Thread.run(Thread.java:764)
抛出了CalledFromWrongThreadException异常,在前面讲解checkThread()方法的时候也提及过,这个方法的作用是检查当前线程是不是创建ViewRootImpl所在的线程,如果是就通知View执行绘制流程,否则就抛出CalledFromWrongThreadException异常,示例代码的ViewRootImpl是在主线程创建的,也就是判断是否为主线程,第一个例子没有抛出CalledFromWrongThreadException异常的原因是,因为调用setText(CharSequence text)方法的时候ViewRootImpl还没创建,View的绘制流程会在Activity的onResume方法之后执行,也就是ViewRootImpl是在onResume方法之后创建的,所以checkThread()方法还没调用,因此这个时候通知UI刷新就不会抛出CalledFromWrongThreadException异常,第二个例子抛出CalledFromWrongThreadException异常的原因是,因为让线程睡眠一秒,可能这个时候onResume方法已经执行了,并且已经在主线程 创建ViewRootImpl,这个时候调用setText(CharSequence text)后通知UI刷新就会调用checkThread()方法,然后得到当前线程是子线程,和主线程不是同一个线程,因此就抛出CalledFromWrongThreadException异常。
其实Google是如下说法:
The Android UI toolkit is not thread-safe.
Google的意思是Android的UI toolkit不是线程安全的,Google也没说不允许在工作线程(非主线程)中更新UI。
题外话
介绍一个Android开源项目(AOSP)的代码搜索工具,网址如下:
我的GitHub:TanJiaJunBeyond
Android通用框架:Android通用框架
我的掘金:谭嘉俊
我的简书:谭嘉俊
我的CSDN:谭嘉俊