哈喽,大家好。今天我们来简单聊聊Android中View的绘制流程。这些东西个人感觉挺枯燥的,不过又是必须要掌握的东西,只有硬着头皮学了。本篇文章我会尽量说的简单易懂,如果有不懂得,可以留言交流。
好了,废话不多说,下面开始正文。
说到View绘制流程,我们就从绘制流程的触发开始讲起。
View的绘制是ActivityThread在创建Activity后调用的handleResumeActivity方法中触发的,所以我们在Avtivity的onCreate方法中获取到的View宽高都是0也是这么来的,因为还没开始绘制。下面我们来看看handleResumeActivity方法
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
...
WindowManager.LayoutParams l = r.window.getAttributes();
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);//这里调用的是WindowManagerImpl的addView方法
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
...
}
代码省略了很多,其中注释的地方就是关键。我们跟进去发现最后是调用WindowManagerGlobal类的addView方法。下面我们来看看这个方法的源码
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
ViewRootImpl root;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
...
try {
root.setView(view, wparams, panelParentView);//这里调用了ViewRootImpl的setView方法
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
...
requestLayout();//继续跟进
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
...
}
}
}
void doTraversal() {
if (mTraversalScheduled) {
...
performTraversals();
...
}
}
最终我们调用了ViewRootImpl的performTraversals方法,在这个方法中我们会分别调用performMeasure,performLayout和performDraw方法,这三个方法分别就是对应View中的measure,layout和draw方法。这样一来,View绘制是如何触发的就有一个大致的了解了。
下面我们从measure方法开始分析。
MeasureSpec
MeasureSpec封装了控件的布局需求。measure respec由size和mode组成。其中size是大小,mode是模式。mode一共有三种:
- UNSPECIFIED:父控件没有对子控件施加任何约束。它可以是任意大小。这个一般是系统内部使用,我们很少使用。
- EXACTLY:父控件为子控件的高宽确定了固定的值。这个相当于是在布局中填入固定的值或是match_parent。
- AT_MOST:子控件的高宽可以为任意值,但是必须小于父控件的高宽。这个相当于在布局中填入wrap_content。
我们对MeasureSpec做了一个简单的介绍,下面我们来看看MeasureSpec的值是如何生成的。首先一个MeasureSpec是由父控件的MeasureSpec和子控件的LayoutParams一起生成的,所以有的说这个是父控件对子控件的约束是不完全正确的。那么有人肯定就会有一个疑问,那根布局如何生成这个MeasureSpec呢。下面我们就开看看根布局是如何生成MeasureSpec的
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);
这里系统调用了getRootMeasureSpec方法来获取MeasureSpec。下面我们来看看getRootMeasureSpec的内部实现
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
这里根据传进来的windowSize和rootDimension来生成对应的MeasureSpec。这里的windowSize一般为屏幕的高宽。
在知道了根布局是如何获取的MeasureSpec之后,我们来看看ViewGroup在测量子View时是如何生成相应的MeasureSpec的。因为ViewGroup中并没有具体实现onMeasure方法,所以我们来用LinearLayout来看看
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
LinearLayout的onMeasure方法分了垂直和水平两种情况,我们就看看垂直情况。
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
...
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
...
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;
}
...
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
...
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
//这里是测量子View高宽的方法
measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);
...
}
...
}
...
}
这里调用了measureChildBeforeLayout方法来测量子View的高宽,我们进入方法,看看是如何生成子View需要的MeasureSpec的
void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
}
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec
, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//这里是获取子View宽的MeasureSpec,传入的参数以此为父控件MeasureSpec,已使用宽(为0)加上Margin和Padding的值,
//子控件的LayoutParams宽的模式。
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
//这里是获取子View宽的MeasureSpec,参数同上。
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//调用子View的measure方法。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在代码中我加入了注释,我们可以看到最终是调用getChildMeasureSpec方法来获取子View需要的MeasureSpec。下面就来看看getChildMeasureSpec方法的内部实现
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//根据方法getMode和getSize分别获取父控件的模式和大小。
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//计算出合适的大小。
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
//如果父控件的布局需求模式为EXACTLY。
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//如果子View有指定的值,那么就直接等于这个值。
resultSize = childDimension;
//子View模式为EXACTLY。
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 如果子控件大小设置为MATCH_PARENT(childDimension = -1),那么就让他等于父控件的大小。
resultSize = size;
//子View模式为EXACTLY。
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 如果子控件大小设置为WRAP_CONTENT(childDimension = -2),那么就让他小于等于父控件的大小。
resultSize = size;
//子View模式为AT_MOST。
resultMode = MeasureSpec.AT_MOST;
}
break;
// 如果父控件的模式为AT_MOST。
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//如果子View有固定大小,那么就等于固定的大小。
resultSize = childDimension;
//子View模式为EXACTLY。
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View为MATCH_PARENT,那么就应该等于父控件的大小,所以将值设置成size。
resultSize = size;
//子View模式为AT_MOST。
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子View的大小为小于等于父控件大小,所以将值设置成size。
resultSize = size;
//子View模式为AT_MOST。
resultMode = MeasureSpec.AT_MOST;
}
break;
// UNSPECIFIED多为系统使用,这里我们就不具体分析了,代码比较简单,有兴趣的同学可以自己看看。
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
代码中加入了注释,大家可以仔细的看看,有问题的话可以留言相互交流下。
measure
介绍完MeasureSpec过后,我们从ViewGroup来开始分析是如何测量控件高宽的。因为各个布局的排列方式不一样,所以ViewGroup没有对测量方法有具体的实现,所以我们结合上面讲过的LinearLayout来详细分析下它是如何对子View进行高宽测量的。因为LinearLayout最终也是继承View的,它这里只实现了View的onMeasure方法,所以我们直接看他的onMeasure方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
我们可以看到方法中是分垂直排列和水平排列来分别测量的,我们就看垂直排列就好。水平排列大家有兴趣的可以自己去看看。
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;
//获取子View数量
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;
//开始循环测量子View的高宽
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
//如果子View为空,总高度加0
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
//如果子View为不可见,跳过
i += getChildrenSkipCount(child, i);
continue;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
//获取子View的LayoutParams
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//记录子View是否有设置weight属性
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
//如果父控件布局模式为EXACTLY ,并且子View有设置weight 属性。先记录子View的topMargin和bottomMargin值。
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
//如果父控件布局不为EXACTLY ,先设置子View的height为 WRAP_CONTENT来测量子View高宽。
lp.height = LayoutParams.WRAP_CONTENT;
}
//测量子View的高宽
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
//获取子View测量后的高度
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
//如果子View的weight >0,并且height原来值为0,这里把height 改为0;
lp.height = 0;
consumedExcessSpace += childHeight;
}
//累加高度
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
...
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
//记录一下父控件布局规则不为EXACTLY 而子View的width为 MATCH_PARENT的情况
matchWidth = true;
matchWidthLocally = true;
}
//计算子View宽度
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
//记录是否所有子View宽度都设置为MATCH_PARENT
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
...
//总高度加入padding的值
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
//和背景高度对比
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
//测量高度和MeasureSpec中的高度对比
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
//计算剩余高度
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
if (skippedMeasure
|| ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
//如果前面有父控件布局模式为EXACTLY,并且子View有设置weight属性和height为0时会进入这个判断,重新测量子View高宽。
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
//根据剩余高度来测量有weight属性的子View高度
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final float childWeight = lp.weight;
if (childWeight > 0) {
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;
final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
|| heightMode == MeasureSpec.EXACTLY)) {
// This child needs to be laid out from scratch using
// only its share of excess space.
childHeight = share;
} else {
// This child had some intrinsic height to which we
// need to add its share of excess space.
childHeight = child.getMeasuredHeight() + share;
}
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
//计算子View宽度
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
//记录是否所有子View宽度都设置为MATCH_PARENT
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
//累加所有子View高度
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
//总高度加入padding值
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth);
...
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
//宽度加入padding值
maxWidth += mPaddingLeft + mPaddingRight;
//对比宽度和背景宽度
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//设置LinearLayout的高宽
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
代码中加入了注释,我省略掉了不常用的代码。流程比较复杂,大家可以慢慢的看几次,加深理解。
在看过LinearLayout的onMeasure方法过后,我们来看看View的是如何对自己进行测量的。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
View的measure方法中我们可以看到调用了onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//调用setMeasuredDimension方法来设置View的高宽
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected int getSuggestedMinimumWidth() {
//对比宽和View背景的大小
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//根据传入的measureSpec来获取高宽的值
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;
}
View的测量代码中加入了简单的注释,有问题的话可以留言讨论。
layout
measure分析完成过后,我们来分析一下layout的流程。
Layout的作用是ViewGroup用来确定子View的位置,当ViewGroup的位置确定过后,会在Onlayout方法中遍历所有子View并调用其layout方法来确定子View的位置。layout方法是确定View本身的位置,下面我们来看看View的layout方法
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;
//先调用setFrame方法,将l,t,r,b四个值传入,确定View在父控件中的位置。
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//调用OnLayout方法来确定子View的位置
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);
}
}
}
...
}
在代码中加入了关键方法的注释,其他代码我们先忽略,在确定自己位置之后,会去确定子View的位置,那么我们来看看OnLayout的实现
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
我们看到OnLayout是一个空实现,它和onMeasure方法一样,不同的布局会有不同的实现,所以我们还是在LinearLayout中来分析下是如何实现OnLayout方法的
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
因为LinearLayout的特性,这里分为水平和垂直两种,我们来看看垂直的layoutVertical
void layoutVertical(int left, int top, int right, int bottom) {
...
final int count = getVirtualChildCount();
...
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
//如果child为null,那么childTop加0
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//获取child的宽和高
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
...
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//调用setChildFrame方法,传入子View距离父控件左边和顶部的距离,再传入子View的高宽
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
//childTop 累加子View高度,使下一个子View排列在改View下方刚好符合LinearLayout垂直排列的特性
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
代码中主要是调用setChildFrame方法来确定子View的位置,我们进入setChildFrame方法看看
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
我们看到在方法中调用了子View自己的layout方法来确定自己的位置,这下layout方法也就明了了。
draw
最后我们来看看View的draw方法,调用这个方法过后View的绘制也将完成。
draw的流程相对来说比较简单,主要是分了如下几个步骤:
1.绘制背景
2.绘制自己
3.绘制children
3.绘制装饰
下面我们来看看draw的代码
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// 1.绘制背景
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// 2.绘制自己
if (!dirtyOpaque) onDraw(canvas);
//3.绘制children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 4.绘制装饰
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
代码中有简单的解释,在绘制子View是调用的是dispatchDraw方法,在ViewGroup中遍历所有子View,并最终调用子View的draw方法,这样绘制就一层层的传递下去。
到此为止整个View的绘制流程就分析完成了,其中没有很细节的去分析每一句代码,个人认为分析源码主要是为了了解Android机制的一些流程。
文中有错误的地方欢迎大家提出,我会及时修改。
谢谢观看!!!
网友评论