android view一条线,Android View:一条线理解 View 绘制流程

e9ce78a01a93

View 绘制流程

一、需要了解的知识

DecorView

DecroView 其实是一个 FrameLayout,它包含了一个垂直的 LinerLayout,这个 LinerLayout 包含手机通知栏、 titlebar 标题栏,下部分为内容栏也就是 Activity 通过 setContentView 设置的布局。

内容栏的 id 为 content,布局加到了 id 为 content 的 FrameLayout 中。

获取 content 内容栏:

ViewGroup content = findViewById(R.android.id.content);

获取设置的布局 View:

content.getChildAt(0);

e9ce78a01a93

DecorView 层次图

ViewRoot 和 ViewRootImpl

ViewRoot 是连接 WindowManager 和 DecorView 的纽带,View 的三大流程均是通过 ViewRoot 来完成的。然而 ViewRoot 不是 View,它的实现类 ViewRootImpl 跟 View 也没有什么直接的关系。详情可阅Android里那些令人费解的命名(一)ViewRoot

MeasureSpec

MeasureSpec 是 View 的一个 static 类,这个词像两个单词的组成,翻译为“测量规格”或“测量参数”。

MeasureSpec 代表一个 32 位的 int 值,它包含了 View 的大小(低30位代表 SpecSize)和测量模式(高2位代表 SpecMode)。MeasureSpec 将这两个值打包在了一起并提供了打包和解包的方法。

简单地说,MeasureSpec 代表了某个 View 的测量大小和显示模式。

MeasureSpec一共有三种模式:

UPSPECIFIED : 父容器不对 View 有任何限制,要多大给多大,一般用于表示一种数值未定的测量状态;

EXACTLY:父容器已经检测或计算出 View 所需的大小,这是一个比较精确的数值,一般最终 View 的大小就是这里指定的 SpecSize;

AT_MOST:父容器指定了一个可用大小,View 的大小不能超过这个值。

二、从源头寻找 View 绘制流程

2.1 DecorView 的创建和测量

1.创建 ViewRootImpl 对象并调用 performTraversals 开始绘制

ActivityThread 中,当 Activity 对象创建完毕,会将 DecorView 添加到 Window 中,同时创建 ViewRootImpl 对象,并将 ViewRootImp 对象和 DecorView 建立关联。

// 创建 ViewRootImpl 对象

root = new ViewRootImpl(view.getContext(), display);

// 和 DecorView 建立关联

root.setView(view, wparams, panelParentView);

从 ViewRoot 的 performTraversals 方法开始,将 View 按照 measure、layout、和 draw 的过程绘制出来。performTraversals 方法会依次调用 performMeasure、performLayout、performDraw 来完成 DecroView 的绘制流程。

private void performTraversals() {

...

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);

int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

...

// performMeasure 执行测量

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

...

WindowManager.LayoutParams lp = mWindowAttributes;

if (didLayout) {

// performLayout 执行 Layout,确定坐标

performLayout(lp, mWidth, mHeight);

...

}

...

// performDraw 执行绘制

performDraw();

...

}

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {

...

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

...

}

/**

* 基于它的布局参数,计算出窗口中根视图的度量标准。

* 也就是根据屏幕宽高和 DecorView 布局参数来确定 DecorView 的 MeasureSpec

*

* @param windowSize

* 窗口的可用宽度或高度

*

* @param rootDimension

* 窗口的一维(宽度或高度)的布局参数。

*

* @return 用于度量根视图的度量指标。

*/

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 = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);

break;

...

}

return measureSpec;

}

ViewGroup:performMeasure --> measure --> onMeasure 在这个过程中,onMeasure 会对所有子元素进行 measure 。

ViewGroup:performLayout --> layout --> onLayout 然后对所有子元素进行 layout

ViewGroup:performDraw --> draw --> onDraw 传递过程通过 dispatchDraw 来完成

2. 开始测量 DecorView

performMeasure 中其实调用了 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 方法,mView 可以看作是上文 ViewRootImpl 对象设置进来的 DecorView 的实例。接下来看一下 DecorView 的继承关系:

DecorView extends FrameLayout extends ViewGroup extends View

DecorView、FrameLayout 还有 ViewGroup 最终都继承了 View类,FrameLayout 和 ViewGroup 都没有 measure 方法,所以 mView.measure 最终会调用 View 类的 measure 方法。

measure 方法是 View 类的一个 final 方法,不可重写。DecorView 调用了 measure 方法,接下来看一下 View 调用 measure 方法做了什么。

View 类 measure 方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

...

onMeasure(widthMeasureSpec,heightMeasureSpec);

..

}

插播 1:View 中 onMeasure 所做的事情:

measure 方法调用了 onMeasure 方法,所以具体 View 的测量工作是在 onMeasure 方法中进行的:

View 类 onMeasure 方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

}

setMeasuredDimension方法可以简单理解为给 View 的 mMeasuredWidth,和 mMeasuredWidth 设置值,也就是确定 View 的测量宽高。这里的测量宽高是通过传递过来的父 View 的 MeasureSpec 和子 View 的默认属性共同决定的,getDefaultSize 方法会证明这一点。设置完成后就代表当前 View 的宽/高测量值确定了。

那么 View 的测量宽高是通过 getDefaultSize 方法返回的参数来确定具体的测量值。

getDefaultSize 方法传入两个参数

第一个参数为 getSuggestedMinimumWidth 方法的返回值:

protected int getSuggestedMinimumWidth() {

return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());

}

根据该方法的返回值来看,如果当前 View 没有设置背景,则返回的值为 View 的 android:minWidth 属性所设置的值,可以为0。如果指定了背景,则返回的值为 android:minWidth 属性值和背景的最小宽度两者中的最大值。对于高度,逻辑也是一样的,android:minHeight 属性和背景共同决定。

第二个参数为 measure 方法中传递过来的宽/高的 MeasureSpec 格式的测量值,是由父 View 的MeasureSpec 和子 View 自己的 LayoutParams 共同决定的,后文再作分析。

View 类 getDefaultSize 方法

public static int getDefaultSize(int size, int measureSpec) {

int result = size;

int specMode = View.MeasureSpec.getMode(measureSpec);

int specSize = View.MeasureSpec.getSize(measureSpec);

// 根据父 View 传递来的 SpecMode 来决定返回 View 的测量值

switch (specMode) {

case View.MeasureSpec.UNSPECIFIED:

result = size;

break;

case View.MeasureSpec.AT_MOST:

case View.MeasureSpec.EXACTLY:

result = specSize;

break;

}

return result;

}

上面已经分析过这两个参数,getDefaultSize 默认返回的是传递来的第一个参数,也就是由最小宽高和背景宽高共同决定的值。接下来进入 switch 判断,如果传递来的 SpecMode 为 UNSPECIFIED,则返回由 minHeight/minWidth 和背景共同决定的值。如果是 AT_MOST(规定上限)或 EXACTLY(精确值)模式,则返回由父 View 的MeasureSpec 和子 View 自己的 LayoutParams 共同决定的测量参数的值,也就是第二个参数中的 SpecSize。

3. 递归测量 DecorView 的子 View,最后测量 DecorView

View 的 measure 方法会调用 onMeasure 来做具体的测量工作。DecorView 其实是一个 FrameLayout 且重写了 onMeasure 方法,所以接下来会进入 FrameLayout 的 onMeasure 方法。接着会调用父类 ViewGroup 的 measureChildWithMargins 方法来测量所有子 View。

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

...

int maxHeight = 0;

int maxWidth = 0;

int childState = 0;

// 遍历子 View 测量它们的值

for (int i = 0; i < count; i++) {

final View child = getChildAt(i);

// 不为 GONE 的子 View 都会参与测量

if (mMeasureAllChildren || child.getVisibility() != GONE) {

// 父类 ViewGroup 中的方法,用来测量所有子 View

measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

...

}

}

...

// 父类 View 中的方法,用来确定当前 View 的测量值(当前是 FrameLayout)

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),

resolveSizeAndState(maxHeight, heightMeasureSpec,

childState << MEASURED_HEIGHT_STATE_SHIFT));

...

}

插播 2:ViewGroup 中 measureChildWithMargins 所做的事情:

ViewGroup 类 measureChildWithMargins 方法源码

protected void measureChildWithMargins(View child,

int parentWidthMeasureSpec, int widthUsed,

int parentHeightMeasureSpec, int heightUsed) {

// child.getLayoutParams()获取的是子 View 在 xml 中所设置的布局参数

final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();

// 根据父 View 的测量参数 parentWidthMeasureSpec/parentHeightMeasureSpec

// 以及子 View 的布局参数 Margin、Padding 和 width/height 来确定子 View 的测量参数

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin

+ widthUsed, lp.width);

final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin

+ heightUsed, lp.height);

// 算出宽和高的测量参数后,让子 View 继续测量。

// 如果子 View 是 ViewGroup 则会递归往下级 View 测量

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

简单理解:首先,拿到子 View 的 xml 设置的布局参数,父 View 的测量参数(MeasureSpec)是由父 View 的 onMeasure 传递过来的。接着,将这些参数传递到 getChildMeasureSpec 方法来计算出子 VIew 的测量参数。最后子 View 调用 measure 方法来继续测量,如果该子 View 是 ViewGroup,会继续调用 measureChildWithMargins 来递归测量下级 View,如果子 View 只剩一个单独的 View,则调用 measure 之后再调用

onMeasure 中的 setMeasuredDimension 来完成测量。

接下来看 getChildMeasureSpec 方法来计算出子 VIew 的测量参数的过程:

ViewGroup 类 getChildMeasureSpec 方法源码

/**

* @param spec 父 View 传递来的测量参数

* @param padding 上面方法传递来的父View的padding和子View的margin以及widthUsed(已使用的宽度/高度)

* lp 是子View的布局参数

* mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed

* @param childDimension 子View布局参数设定的宽或高

* @return 子View的布局参数

*/

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

int specMode = View.MeasureSpec.getMode(spec); // 父View的SpecMode

int specSize = View.MeasureSpec.getSize(spec); // 父View的SpecSize

// 具体大小为父View的大小 减去 父View的padding和子View的margin以及widthUsed

// 暂时称之为 View剩余大小

int size = Math.max(0, specSize - padding);

// 用来生成子View的测量参数

int resultSize = 0;

int resultMode = 0;

switch (specMode) {

// 父View的大小是EXACTLY模式

case View.MeasureSpec.EXACTLY:

if (childDimension >= 0) { // 子View布局参数宽/高大于等于0

resultSize = childDimension; // 子View的宽/高为子View设置的布局参数值

resultMode = View.MeasureSpec.EXACTLY; // 子View的模式为精确模式

} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) { // 子View是MATCH_PARENT

resultSize = size; // 让子View的数值为计算好的View剩余大小

resultMode = View.MeasureSpec.EXACTLY; // 子View的模式为精确模式

} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) { // 子View是WRAP_CONTENT

resultSize = size; // 子View的数值为计算好的View剩余大小

resultMode = View.MeasureSpec.AT_MOST; // 子View的模式为AT_MOST,表示不能超过这个View剩余大小

}

break;

// 父View的大小是AT_MOST模式

case View.MeasureSpec.AT_MOST:

if (childDimension >= 0) { // 子View布局参数宽/高大于等于0

resultSize = childDimension; // 子View的宽/高为子View设置的布局参数值

resultMode = View.MeasureSpec.EXACTLY; // 子View的模式为精确模式

} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) { // 子View是MATCH_PARENT

resultSize = size; // 子View的数值为View剩余大小

resultMode = View.MeasureSpec.AT_MOST; // 因为父View是AT_MOST模式其数据不确定,

// 所以子View也是AT_MOST不能超过View剩余大小

} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {// 子View是WRAP_CONTENT

// 子View想自适应,但它最大不能超过View剩余大小

resultSize = size;

resultMode = View.MeasureSpec.AT_MOST;

}

break;

// 父View的大小是UNSPECIFIED模式

case View.MeasureSpec.UNSPECIFIED:

if (childDimension >= 0) {// 子View布局参数宽/高大于等于0

// 子View宽高是精确数值,父View不限制所以子View也不加限制

resultSize = childDimension;

resultMode = View.MeasureSpec.EXACTLY; //子View宽高是精确数值所以其模式也应为EXACTLY

} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {// 子View是MATCH_PARENT

// 如果父View的大小未确定,返回0,否则返回View剩余大小

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

resultMode = View.MeasureSpec.UNSPECIFIED; // 父View大小无限制,子View也应是

} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {

// 判断模式与上一个类似

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

resultMode = View.MeasureSpec.UNSPECIFIED;

}

break;

}

// 根据上文子View的SpecMode和SpecSize来打包成一个MeasureSpec对象

return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);

}

简单总结:

首先拿到父 View 的 specMode 和 specSize,specSize是父 View 的测量宽/高。使用 specSize 减去 父 View 的 padding 和子 View 的 margin 以及widthUsed 来计算出一个剩余大小,也就是子 View 能拿来用作显示的区域大小,暂且称之为 View 剩余大小。

然后根据父 View 的 specMode 和子 View 具体的布局参数来确定子 View 的 specMode 和 specSize:

父 View 是EXACTLY模式,说明父 View 的大小是精确的,一般来说,这个大小就是最终展示到屏幕上的大小。

如果子 View 的大小在布局中设定好了,并且大于等于 0,那么子 View 的值是确定的,并且其模式为 EXACTLY。无论父 View 的大小与模式。

如果子 View 的大小为 "match_parent",当父 View 精确大小,那么子 View 的大小为计算好的 View 剩余大小,并且模式也是 EXACTLY。

如果子 View 的大小为 "wrap_content",当父 View 精确大小,子 View 再怎么自适应也不应超过父 View 剩余大小,所以 specSize 为 View 剩余大小,specMode 为 AT_MOST。

父 View 是AT_MOST模式,说明父 View 的大小不确定,但是会小于某个固定值。

同样,如果子 View 的大小在布局中设定好了,并且大于等于 0,那么子 View 的值是确定的,并且其模式为 EXACTLY。无论父 View 的大小与模式。

如果子 View 的大小为 "match_parent",子 View 的模式为 AT_MOST,并且其最大值为 View 剩余大小。

逻辑同上,也应是 AT_MOST 模式并且最大值为 View 剩余大小。

父 View 是UNSPECIFIED模式,表面父 View 的大小没有约束,所以子 View 的大小也可以是没有限制大小的。

同样,如果子 View 的大小在布局中设定好了,并且大于等于 0,那么子 View 的值是确定的,并且其模式为 EXACTLY。

如果子 View 的大小为 "match_parent",子 View 的大小理所应当也是无限制模式的,并且如果父View的大小未确定,返回0,否则返回View剩余大小。

逻辑同上。

(3)DecorView 作为 FrameLayout 调用 FrameLayout 的 onMeasure 方法,onMeasure 方法又调用了 ViewGroup 的 measureChildWithMargins 方法来测量所有子 View。子 View 测量完毕后,接着调用父类 View 的 setMeasuredDimension 方法,这个方法在上面 View 的测量流程中有提到过。

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;

measuredWidth += optical ? opticalWidth : -opticalWidth;

measuredHeight += optical ? opticalHeight : -opticalHeight;

}

setMeasuredDimensionRaw(measuredWidth, measuredHeight);

}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {

mMeasuredWidth = measuredWidth;

mMeasuredHeight = measuredHeight;

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;

}

总之就是 DecorView 作为 FrameLayout 先测量子 View,然后再确定自己的测量值。关于子 View 是如何测量的,可以回去看第一条插播。

2.2 Layout 过程

DecroView 完成 performMeasure 中的 measure 过程后,就完成了 DecroView 的大小和绘制模式,接下来就会进行 Layout 过程来放置 DecroView 的具体位置。然后判断 didLayout (didLayout 由 Window 状态等参数决定),如果为 true 则执行 Layout 过程。

private void performTraversals() {

...

// performMeasure 执行测量

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

...

WindowManager.LayoutParams lp = mWindowAttributes;

if (didLayout) {

// performLayout 执行 Layout,确定坐标

performLayout(lp, mWidth, mHeight);

...

}

...

}

ViewRootImp 中 performLayout 方法

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,

int desiredWindowHeight) {

...

// 赋值给 host 并判断是否为 null

final View host = mView;

if (host == null) {

return;

}

...

// 进行具体的 layout 过程

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

...

}

由上面代码可知 DecorView 调用 layout 方法,这个方法被 ViewGroup 重写为 final 类型(说明所有继承 ViewGroup 的 View 的 layout 过程被 ViewGroup 统一管理),DecorView 是一个 FrameLayout 继承了 ViewGroup。所以会调用 ViewGroup 的 layout 方法。

ViewGroup layout 方法

@Override

public final void layout(int l, int t, int r, int b) {

// mSuppressLayout 判断当前布局是否被父 ViewGroup 进行增加或删除的动画效果

// mTransition 判断当前布局是否存在动画或动画正在改变布局

if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {

if (mTransition != null) {

mTransition.layoutChange(this);

}

super.layout(l, t, r, b);

} else {

// transition 动画正在运行设置为 true,等待动画完成重新调用 requestLayout() 重新请求 Layout 步骤

// record the fact that we noop'd it; request layout when transition finishes

mLayoutCalledWhileSuppressed = true;

}

}

上述代码可以简单理解为 layout 之前先进行 Transition 动画的判定,如果有动画在执行则设置 mLayoutCalledWhileSuppressed 为 true 作为标记,等待动画完毕重新调用 layout。

如果Transition 没有在执行,则调用父类的 layout(l, t, r, b); 方法,也就是 View 类的 layout() 方法。

View layout 方法

public void layout(int l, int t, int r, int b) {

...

// 先标记旧的属性值

int oldL = mLeft;

int oldT = mTop;

int oldB = mBottom;

int oldR = mRight;

// 判断 LayoutMode 是否为 LAYOUT_MODE_OPTICAL_BOUNDS 模式,如果是则执行 setOpticalFrame。

// 不是则执行 setFrame,这个函数的作用为:判断当前 View 的属性是否发生变化,并返回是否有变化。

// 同时为当前 View 设置一个大小和位置。

boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

// 判断当前 View 是否发生变化,来决定是否需要重新 layout

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;

...

}

...

}

拿到上下左右的属性值并标记,然后判断属性值是否发生变化并更新设置。

如果 View 发生了变化则回调 onLayout() 来确定坐标,在初始情况下各个属性都是默认的。一旦子 View 完成 measure 过程,这些属性都会被赋值,所以会回调具体类的 onLayout() 方法。View 类的 onLayout() 方法是空实现

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}

由于 DecorView 继承 FrameLayout 又继承 ViewGroup 又继承 View,所以倒着来看 :

ViewGroup 的 onLayout() 方法

@Override

protected abstract void onLayout(boolean changed,

int l, int t, int r, int b);

简单粗暴的一个抽象方法,也就是说需要子类具体的去实现。

FrameLayout 的 onLayout() 方法

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

layoutChildren(left, top, right, bottom, false /* no force left gravity */);

}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {

final int count = getChildCount();

// 获取父 View 的相关属性

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);

// 省略的是一些根据子 View 的 LayoutGravity 和父 View 的边界属性来重新计算的代码

...

// 计算完毕之后子 View 再递归进行 layout。根据具体的 View 回调相关的 onLayout 方法

child.layout(childLeft, childTop, childLeft + width, childTop + height);

}

}

到 layoutChildren 方法也是拿了一堆属性 ,然后遍历子 View 来balabala 进行计算(省略了)最终确定子 View 的左右上下边距再使用递归的方式来进行子 View 的 layout 方法。

进行 layout 会回调 onLayout 方法,不同的 View 会重写 onLayout 方法,具体可以去看不同 View 的源码。

onLayout(boolean changed, int left, int top, int right, int bottom) 方法中的各种属性在 measure 过程中计算好传递过来的,但是具体进行 layout 过程可能会根据子 View 的 LayoutGravity 模式来进行具体的更改。

至此,Layout 过程告一段落。

2.3 Draw 过程

performLayout 执行完毕之后会进行 performDraw 方法来进行绘制,

private void performTraversals() {

...

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);

int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

...

// performMeasure 执行测量

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

...

WindowManager.LayoutParams lp = mWindowAttributes;

if (didLayout) {

// performLayout 执行 Layout,确定坐标

performLayout(lp, mWidth, mHeight);

...

}

...

// performDraw 执行绘制

performDraw();

...

}

performDraw()方法经过层层调用会执行mView.draw(canvas);方法,这里的 mView 依旧是 DecorView 的实例。因为View的draw 方法一般不去重写,官网文档也建议不要去重写 draw 方法,所以接下来进入 View 类的 draw 方法:

public void draw(Canvas canvas) {

...

/*

* 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)

*/

// Step 1, draw the background, if needed

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) {

// Step 3, draw the content

if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children

dispatchDraw(canvas);

...

// Step 6, draw decorations (foreground, scrollbars)

onDrawForeground(canvas);

// Step 7, draw the default focus highlight

drawDefaultFocusHighlight(canvas);

if (debugDraw()) {

debugDrawFocus(canvas);

}

// we're done...

return;

}

...

}

这块的注释写的非常清晰,一条条列出来了绘制步骤,其中第 2 步和第 5 步是在特点情况下才会进行的,所以分析时省略这两块的步骤:

绘制背景

private void drawBackground(Canvas canvas) {

Drawable final Drawable background = mBackground;

......

//mRight - mLeft, mBottom - mTop layout确定的四个点来设置背景的绘制区域

if (mBackgroundSizeChanged) {

background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);

mBackgroundSizeChanged = false; rebuildOutline();

}

......

//调用Drawable的draw() 把背景图片画到画布上

background.draw(canvas);

......

}

如有必要,请保存画布图层以准备淡入淡出

绘制View的内容

View 的 onDraw() 和 ViewGroup 的 onDraw() 都是空实现,所以需要具体的子 View 去实现相应的绘制功能

绘制View的子View们

View 的 dispatchDraw(canvas); 方法也是一个空实现,ViewGroup 实现了这个方法,在 ViewGroup 中该函数遍历所有子元素的 draw 方法,这样绘制事件就一层一层地传递了下去。

ViewGroup 的 dispatchDraw 方法

@Override

protected void dispatchDraw(Canvas canvas) {

...

if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {

for (int i = 0; i < count; i++) {

final View child = children[i];

if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {

more |= drawChild(canvas, child, drawingTime);

}

}

} else {

for (int i = 0; i < count; i++) {

final View child = children[getChildDrawingOrder(count, i)];

if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {

more |= drawChild(canvas, child, drawingTime);

}

}

}

......

}

drawChild 方法直接调用了 child.draw(canvas, this, drawingTime); 方法。又是在循环中,所以会调用所有 child View 的 draw 方法。

如有必要,绘制渐变边缘并恢复图层

绘制装饰(例如滚动条)

绘制默认焦点高亮

到这里 View 的绘制流程就基本结束了。

参考资料:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值