UI绘制流程--View的绘制流程

上一篇UI绘制流程–View是如何被添加到屏幕上的我们学习了View是怎么添加到屏幕上的,这一片来学习View绘制流程,它的入口在入口ActivityThread.handleResumeActivity()。

本篇基于9.0的源码以前的一篇文章 Activity启动流程,最后我Activity启动的最后走到了ActivityThread中执行handleResumeActivity方法并里面执行了activity的onResume方法我们在来看这个方法

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
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
final Activity a = r.activity;
...
final Activity a = r.activity;
...
//获取Window也就是PhoneWindow
r.window = r.activity.getWindow();
//获取PhoneWindow中的DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
//获取PhoneWindow的参数
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
...
a.mWindowAdded = true;
wm.addView(decor, l);
...
Looper.myQueue().addIdleHandler(new Idler());
}

代码中performResumeActivity就是去执行activity的onResume方法,之后创建了一个ViewManager ,然后拿到WindowManager的LayoutParams,最后通过addView方法把DecorView和LayoutParams放入ViewManager中。那ViewManager是什么呢

从这里我们可以知道,view的添加和绘制是onResume之后才开始的,所以onResume的时候我们是拿不到View的宽和高的

我们看到它是通过a.getWindowManager()获得,a是activity,那就去activity中找一下这个方法

1
2
3
public WindowManager getWindowManager() {
return mWindowManager;
}

这里直接返回了activity的一个成员变量mWindowManager,那我们去找一下这个成员变量的赋值的地方,可以找到一个set方法

1
2
3
4
5
6
7
8
9
10
11
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

可以看到是调用了WindowManagerImpl中的createLocalWindowManager方法来创建的

1
2
3
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}

结果返回了一个WindowManagerImpl对象,所以上面的ViewManager其实就是一个WindowManagerImpl对象。所以呢最后调用的就是它的addView方法

1
2
3
4
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

它又调用了mGlobal的addView方法,mGlobal是个WindowManagerGlobal对象在成员变量中直接通过单例创建WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();去看它的addView方法

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
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...

WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

...

ViewRootImpl root;
View panelParentView = null;

...
//创建一个ViewRootImpl并设置参数
root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);
//保存传过来的view,ViewRootImpl,LayoutParams
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

...
root.setView(view, wparams, panelParentView);
...
}

看到这里创建了一个ViewRootImpl,给传过来的DecorView置LayoutParams参数,然后放到对应的集合中缓存,最后调用root.setView方法将他们关联起来。

1
2
3
4
5
6
7
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
requestLayout();
...
view.assignParent(this);
}

里面代码太多了,我们只关注里面的 requestLayout()方法就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//判断是不是主线程
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}

判断是不是在当前线程,当前activity的启动时在主线程,这就是为什么不能再子线程中更新UI,不过这里我们知道上面的方法时在onResume之后执行的,所以如果我们在onResume之前的子线程中执行一个很快的更新UI的操作,如果没有执行到这里就不会报错

首先判断是不是在主线程然后调用了scheduleTraversals方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

我们看到mChoreographer.postCallback方法中传了一个mTraversalRunnable参数到队列中去执行,mTraversalRunnable是TraversalRunnable对象,TraversalRunnable其实是一个Runnable对象,所以真正的的执行的代码在其run方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

void doTraversal() {
...
//真正的开始执行绘制
performTraversals();

...
}
}

又调用了performTraversals方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void performTraversals() {
//DecorView
final View host = mView;
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
//measure过程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//layout过程
performLayout(lp, mWidth, mHeight);
...
//绘制过程
performDraw();
}

代码比较多,只提取出3个主要的方法,这几个方法主要执行View的主要绘制流程:测量,布局和绘制。

以上代码其实就是将我们的顶级view->DecorView添加到窗口上,关联到ViewRootImpl中,并调用requestLayout(); 方法请求绘制,最后到了performTraversals方法中执行performMeasure,performLayout,performDraw真正的开始绘制。

下面就分别来看一下这三个方法。

1
2
3
4
5
6
7
8
9
10
11
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

可以看到这里调用了mView的measure方法,这个mView就是我们的前面add进来的DecorView。它是一个FrameLayout。点进去查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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);
}

首先我们看到点进来之后到了View这个类中,measure这个方法时final类型的,所以不能被重写,因此就算他是FrameLayout最终也是在View类中执行measure的方法。

measure方法中又调用了onMeasure方法,然后直接调用setMeasuredDimension方法,最后调用了setMeasuredDimensionRaw方法。这些方法时干什么的呢,

首先我们先找到传入的参数widthMeasureSpec和heightMeasureSpec了解这两个参数的作用。

这两个参数是怎么来的呢,回到我们上面的performTraversals()方法,可以看到int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

第一个参数表示窗口的宽度,第二个参数表示当前view也就是DectorView的LayoutParams

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

可以看到根据我们View设置的MATCH_PARENT还是WRAP_CONTENT等返回了一个通过MeasureSpec.makeMeasureSpec方法返回了一个int类型的值measureSpec,那它代表什么呢?

我们在测量View的时候需要知道两点:

第一点View的测量模式

第二点View的尺寸

measureSpec表示一个32的整数值,其高两位代表测量模式SpecMode,底30位表示该测量模式下的尺寸SpecSize。

我们进入MeasureSpace类可以看到3个常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 表示父容器不对子容器进行限制,子容器可以是任意大小,
* 一般是系统内部使用
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* 精准测量模式,当view的layout_width 或者 layout_height指定为固定值值
* 或者为match_parent的时候生效,这时候view的测量值就是SpecSize
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* 父容器指定一个固定的大小,子容器可以使不超过这个值的任意大小
* 对应我们的wrap_content
*/
public static final int AT_MOST = 2 << MODE_SHIFT;

对于DecorView这个顶级View来说,它的MeasureSpec 由窗口的尺寸和其自身的LayoutParams决定。

我们在回到measure方法中查看onMeasure方法,我们知道measure方法是个final方法不能被子类重写,不过onMeasure方法就没这个限制了,DecorView继承自FrameLayout,所以我们进入FrameLayout中查看它的onMeasure方法

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
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//循环测量子view
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
...
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//设置自身的宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}

这里找出所有的子View,然后循环调用measureChildWithMargins方法测量子view的宽高,之后调用setMeasuredDimension确定自己的宽高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//获取子控件的测量规格
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);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

先获取子控件的宽高的测量规格,然后调用子控件的measure方法传入测量规格,子控件的测量规格是怎么获取的呢,点进去看到getChildMeasureSpec这个方法是在ViewGroup类中

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
    /**
* @param spec 父控件的测量规格
* @param padding 父控件已经占用的大小(减去padding和margin)
* @param childDimension 子控件LayoutParams中的尺寸
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父控件的测量模式
int specMode = MeasureSpec.getMode(spec);
//父控件的尺寸
int specSize = MeasureSpec.getSize(spec);
//子容器可用大小要减去父view的padding和子view的margin
int size = Math.max(0, specSize - padding);

int resultSize = 0;
int resultMode = 0;

switch (specMode) {
// 如果父控件是精准尺寸,也就是父控件知道自己的大小
case MeasureSpec.EXACTLY:
//如果子view设置了尺寸比如100dp,那么测量大小就是100dp
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果子view设置的MATCH_PAREN想要沾满父view
//父view是精准模式,那么把父view的size给它
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果子view设置的WRAP_CONTENT,那么它想随意决定自己的大小
//你可以随意玩,但是不能大于父控件的大小,
//那么暂时把父view的size给它
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// 如果父控件是最大模式,也就是父控件也不知道自己的大小
case MeasureSpec.AT_MOST:
//子控件设定了具体值
if (childDimension >= 0) {
//那就返回这个具体值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子view想和父view一样大,但是父view也不知道自己多大
//把暂时父view的size给它,约束它不能超过父view
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子view想要自己确定尺寸
//不能大于父view的size
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// 父view是不确定的,一般是系统调用开发中不用
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);
}

对于普通的view来说
它的MeasureSpec由其父view的MeasureSpec和自身的LayoutParams来决定

parentSpecMode/childLayoutParams EXACTLY AT_MOST UNSPECIFIED
dp/px EXACTLY / chileSize EXACTLY / chileSize EXACTLY / chileSize
match_parent EXACTLY / parentSize AT_MOST /parentSize 0
wrap_content AT_MOST/ parentSize AT_MOST /parentSize 0
  • 当view采用固定宽高的时候,不管父容器是什么模式,子view的MeasureSpec都是精确模式,并且大小就是其LayoutParams中设置的大小
  • 当view的宽或高是match_parent的时候,如果父容器是精准模式,那么子view的也是精准模式,其大小是父view的剩余空间,如果父容器是最大模式,那么子view也是最大模式,其大小暂时设为父view的大小并不能超过父view的大小。
  • 当view的宽或高是wrap_content的时候,不管父容器是什么模式,子view总是最大化,并且不超过父容器的剩余空间。

OK总结一下

  • ViewGroup执行measure方法->里面通过onMeasure方法递归测量子控件的宽高,测量完后通过setMeasuredDimension调用setMeasuredDimensionRaw方法最终保存自己的宽高。
  • View执行measure->onMeasure测量自己->测量完后通过setMeasuredDimension调用setMeasuredDimensionRaw方法最终保存自己的宽高

我们回到view的onMeasure方法

1
2
3
4
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

可以看到它在调用setMeasuredDimension传参的的时候调用了getDefaultSize方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

这个逻辑很简单,首先UNSPECIFIED我们不用管一般系统用,然后我们看到AT_MOST和EXACTLY最后的结果是一样的都赋值为specSize,这个specSize就是view测量后的大小。也就是getSuggestedMinimumWidth和getSuggestedMinimumHeight两个方法返回的值。

1
2
3
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

从上面可以看出如果view没有设置背景,则返回mMinWidth,反之则宽度为mMinWidth和背景宽度的最大值。mMinWidth对应我们xml中设置的android:minWidth属性值,如果没设置则为0,mBackground.getMinimumWidth()则是返回的Drawable的原始宽度。

从上面的getDefaultSize方法我们可以得出一个结论,当我们直接继承view自定义控件的时候,需要重写其onMeasure方法,然后设置其wrap_content时候的大小,否则即便我们在布局中使用wrap_content,实际情况也相当于match_parent。原因可以从上面的表中看到,如果一个view设置了wrap_content,那么其测量模式是AT_MOST,在这种模式下view的宽高都等于父容器的剩余空间大小。

那怎么解决上面的问题呢?看一个重写onMeasure的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 宽的测量规格
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
// 宽的测量尺寸
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
// 高度的测量规格
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
// 高度的测量尺寸
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

//根据View的逻辑得到,比如TextView根据设置的文字计算wrap_content时的大小。
//这两个数据根据实现需求计算。
int wrapWidth,wrapHeight;

// 如果是是AT_MOST则对哪个进行特殊处理
if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(wrapWidth, wrapHeight);
}else if(widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(wrapWidth, heightSpecSize);
}else if(heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize, wrapHeight);
}
}

我们只需给view指定一个默认的宽高,并在AT_MOST的时候设置宽高即可,默认宽高的大小根据实际情况来

OK,measure的方法就看完了下面来看layout的流程,这个比measure简单多了

1
2
3
4
5
6
7
8
//lp顶层布局的布局属性,顶层布局的宽和高
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
final View host = mView;
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

这里吧mView赋值给host然后调用了其layout方法,我们知道mView其实就是DecorView。

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
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来确定4个顶点的位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//父容器确定子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);
}
}
}

final boolean wasLayoutValid = isLayoutValid();

mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

if (!wasLayoutValid && isFocused()) {
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
if (canTakeFocus()) {
// We have a robust focus, so parents should no longer be wanting focus.
clearParentsWantFocus();
} else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
clearParentsWantFocus();
} else if (!hasParentWantsFocus()) {
// original requestFocus was likely on this view directly, so just clear focus
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
}
} else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
View focused = findFocus();
if (focused != null) {
if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
}
}
}

if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}

layout的流程首先通过setFrame方法设定view的4个顶点的位置,4个顶点确定了,view在父容器中的位置也就确定了,然后调用onLayout方法来确定子元素的位置。onLayout需要不同的ViewGroup去自己实现比如LinearLayout和RelativeLayout的实现是不同的。

OK,layout也看完了下面看最后一步Draw的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void performDraw() {
...
boolean canUseAsync = draw(fullRedrawNeeded);
...
}
private boolean draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
...
mView.draw(canvas);
...
}

通过一系列的跳转,我们终于找到关键方法mView.draw(canvas),从这里就进入了view中的draw方法

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

// 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) {
// 绘制自己
if (!dirtyOpaque) onDraw(canvas);

// 绘制子view
dispatchDraw(canvas);

drawAutofilledHighlight(canvas);

// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}

// 绘制装饰 前景滚动条(foreground, scrollbars)
onDrawForeground(canvas);

// 绘制默认的焦点突出显示
drawDefaultFocusHighlight(canvas);

if (debugDraw()) {
debugDrawFocus(canvas);
}

// we're done...
return;
}

...

view的绘制过程上面注释已经写清楚了

  1. 绘制背景 (background.draw(canvas))
  2. 绘制自己 (onDrow)
  3. 绘制子view(dispatchDrow)
  4. 绘制装饰(前景、滚动条)

如果我们是自定义view,就去实现onDraw方法,如果我们是自定义ViewGroup,那就去实现dispatchDraw方法,dispatchDraw方法中会遍历子view调用子view的draw方法。

到这里draw方法就看完了,view的绘制流程也执行完毕!

ps: view中有个特殊的方法setWillNotDraw

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

从注释中看出来,如果一个view不需要绘制任何东西,这个标志位设置为true之后,系统会进行相应的优化。

默认情况下,view没有启动这个标志位,但是ViewGroup是会默认启动这个标志位的。所以当我们继承ViewGroup的时候并且明确知道需要通过onDraw来绘制内容的时候,我们需要显示的关闭这个标志位。

# 进阶

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×