盒子
盒子
文章目录
  1. Activity的视图结构
  2. ViewTree的绘制
    1. measure相关
    2. layout相关
    3. draw相关
    4. 关于invalidate方法

图解Android:View的绘制机制与源码解析

前言

Android的视图是如何绘制的?深入了解一下UI的绘制原理无论对我们APP的性能优化还是对我们的自定义view都有很大的帮助。下文将和大家一道探究一下Android的viewTree的绘制原理,希望对大家的开发和学习有所帮助。

本篇是图解Android系列第二篇,更多文章敬请关注后续文章。如果这篇文章对大家学习Android有帮助,还望大家多多转载。学习小组QQ群: 193765960

版权归作者所有,如有转发,请注明文章出处:https://xiaodanchen.github.io/archives/


Activity的视图结构

先看一下activity的视图结构图

Activity的视图结构

  • 每个activity都有一个Window(实际是phonewindow)
  • Phonewindow含有一个DecorView,这是我们window的topview
  • DecorView是继承自Framelayout,换言之其为整个ViewTree的根节点viewGroup

再看一下Phonewindow的类图

Activity的视图结构

接下来我们来看一下单个Activity的viewTree的结构,我选择了两版sdk来查看

1)Android4.4系统的activity:
Activity的视图结构
2)Android6.0系统的activity:
Activity的视图结构

ViewTree的绘制

id“content”的ContentFrameLayout是我们的布局文件加载显示的区域,更确切地说是我们activity的setcontentView()方法设置的视图显示的区域。下面我么就看看ContentFrameLayout中整个viewTree是如何绘制出来的。
Activity的视图结构
《图解Android:事件传递机制》中我们说过Android中的任何一个布局、任何一个控件包括我们自定义的控件其实都是直接或间接继承自View实现的,所以说这些View应该都具有相同的绘制流程与机制才能显示到屏幕上(可能每个控件的具体绘制逻辑有差异, 但是主流程都是一样的)。每一个View的绘制过程都必须经历三个最主要的过程,也就是measure()、layout()和draw()。
先看一下类图:
Activity的视图结构

那么,整个Android的UI绘制机制是从哪里开始的即入口在哪里呢?答案就是ViewRootImpl类的performTraversals()方法。ViewRootImpl这个类是一个隐藏类,所以如果你是使用Eclipse开发的话可能看不到这个文件(AndroidStudio可以),没关系,根据路径(androidSDK\android-sdk-windows\sources\android-23\android\view\)去找到ViewRootImpl.Java文件,然后用文本阅读工具直接打开就好。
看一下官方对ViewRootImpl的介绍:

1
2
3
4
5
6
7
/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
* detail of {@link WindowManagerGlobal}.
*
* {@hide}
*/

上面这段注释啥意思呢?说白了就是ViewRootImpl是一个window中的viewTree的入口,实现了window对viewTree管理的必需逻辑。

ViewRootImpl类performTraversals()代码,源代码长的恐怖,这里给大家过滤一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
private void performTraversals() {
......
//lp.width和lp.height在创建ViewGroup实例时值为MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
......
performDraw();
......
}
//执行rootView的测量
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//ViewGroup的measure()方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
//执行layout操作
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
......
try {
//viewRoot先进行layout
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
//需要layout的子view的数量
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
//需要layout的子view
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
//如果view中有调用requestLayout()方法,则说明界面需要刷新
mHandlingLayoutInLayoutRequest = true;
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
view.requestLayout();
}
//整个viewTree重新measure
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
//整个viewTree重新layout
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// 再次检查是否有view需要刷新
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post请求,在下一帧的显示的时候去执行刷新
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
private void performDraw() {
......
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
......
}
private void draw(boolean fullRedrawNeeded) {
......
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
//使用硬件渲染,比如GPU
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
//使用软件渲染
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
......
}
/**
* @return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
......
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
attachInfo.mIgnoreDirtyState = false;
}
}
......
return true;
}

measure相关

View类的UI绘制相关函数

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
//final说明该函数不允许被子类override,不需要关注细节
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
//widthMeasureSpec,heightMeasureSpec是由parent决定的
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
/**
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
*
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//View类的默认实现,如果自定义view的话,需要我们自己override
//child的宽高有来自parent的widthMeasureSpec、heightMeasureSpec和子的MeasureSpecMode共同决定
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
  • 注意onMeasure(int widthMeasureSpec, int heightMeasureSpec)入参的含义:
    1) widthMeasureSpec和heightMeasureSpec是parent暴露给child的尺寸
    2) widthMeasureSpec和heightMeasureSpec是32位的数值,其中高16位为MeasureSpecMode,低16位为MeasureSpecSize
    3) MeasureSpecMode有三种取值:
    * MeasureSpec.EXACTLY:child为精准尺寸(layout_with=mach_parent、24dp的情况)
    * MeasureSpec.AT_MOST:child为最大尺寸(layout_with=wrap_content的情况)
    * MeasureSpec.UNSPECIFIED:child未指定尺寸
    
  • child的尺寸有parent穿过来的widthMeasureSpec、heightMeasureSpec和子的MeasureSpecMode共同决定

layout相关

View的layout相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//非final类型,子类可以重载
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(changed, l, t, r, b);
......
}
......
}
//View的onlayout函数默认为空(如果自定义view中需要,可重载)
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
}

ViewGroup的layout相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* {@inheritDoc}
*/
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
//view的layout方法
super.layout(l, t, r, b);
} else {
mLayoutCalledWhileSuppressed = true;
}
}
/**
* 抽象方法,子类必须实现(因为内部必然存在多个view控件,需要layout)
*/
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
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
/**
* ViewGroup的onLayout()方法都需要子类去实现
* 所以我们来看一下LinearLayout的实现
*/
@Override 
protected void onLayout(boolean changed, int l, int t, int r, int b) {  
if (mOrientation == VERTICAL) {  
layoutVertical();  
else {  
layoutHorizontal();  
}  
}
//以垂直方向的布局为例
void layoutVertical() {  
......  
final int count = getVirtualChildCount();  
......
//遍历child
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();  
......
//递归child调用layout
setChildFrame(child, childLeft, childTop + getLocationOffset(child),  
childWidth, childHeight);  
......  
}  
}  
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}

注意:

  • View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现自己的位置逻辑。
  • measure操作完成后得到的是对每个View经测量过的measuredWidth和measuredHeight,layout操作 完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom,这些值都是相对于父View来说的。
  • 凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的,当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的。
  • 使用View的getWidth()和getHeight()方法来获取View测量的宽高,必须保证这两个方法在onLayout流程之后被调用才能返回有效值。

draw相关

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
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
        ......
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
 
        // skip step 2 & 5 if possible (common case)
        ......
 
        // Step 2, save the canvas' layers
        ......
            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }
        ......
 
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
 
        // Step 4, draw the children
        dispatchDraw(canvas);
 
        // Step 5, draw the fade effect and restore layers
        ......
        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }
        ......
 
        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);
        ......
    }

注意:

  • View的onDraw()方法为空,需要用户自己实现
  • 关于draw,官方的注释已经很清楚,我们需要注意的是第四步:递归调用完成viewTree的绘制
  • dispatchdraw()为空,需要在子类去实现

ViewGroup的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
/**
* 遍历各种类型的情况的child,并draw
*/
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
while (transientIndex >= 0) {
......
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
......
}
if (mDisappearingChildren != null) {
......
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}

关于invalidate方法

invalidate系列方法请求重绘View树(也就是draw方法),如果View大小没有发生变化就不会调用layout过程,并且只绘制那 些“需要重绘的”View,也就是哪个View(View只绘制该View,ViewGroup绘制整个ViewGroup)请求invalidate系 列方法,就绘制该View。

有以下几种触发invalidate方法的情况:

  • 直接调用invalidate方法:会绘制调用者本身。
  • 触发setSelection方法:会绘制调用者本身。
  • 触发setVisibility方法:当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在 INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过 程以及draw过程,同样只绘制需要“重新绘制”的视图。
  • 触发setEnabled方法:不会重新绘制任何View包括该调用者本身。
  • 触发requestFocus方法:只绘制“需要重绘”的View。
扫描加群
好好学习,天天向上!