您的位置:首页 > 其它

View绘制流程源码解析

2017-08-27 16:52 766 查看


View绘制流程源码解析

标签: android源码绘制流程
2017-05-23 17:37 219人阅读 评论(0) 收藏 举报


 分类:

自定义View(25) 


版权声明:本文为博主原创文章,未经博主允许不得转载。

1.写在前面
  很多人在获取控件宽高的时候,遇到了一些问题,因为涉及到的知识点比较广,所以有时我只能说个大概,这一次我希望大家可以做一个彻底的了解,请思考一下,下面三段代码分别打印什么?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View view = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
mTextView = (TextView) view.findViewById(R.id.text_view);
Log.e("TAG", "height -> " + mTextView.getHeight());
}
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View view = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
mTextView = (TextView) view.findViewById(R.id.text_view);
new Handler().post(new Runnable() {
@Override
public void run() {
Log.e("TAG", "height -> " + mTextView.getHeight());
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onResume() {
super.onResume();
Log.e("TAG", "height -> " + mTextView.getHeight());
}
1
2
3
4
5
1
2
3
4
5
2.预备知识 
  请先了解一下我之前的几篇文章插件式换肤框架搭建 - setContentView源码阅读 、 Android插件化架构
- Activity的启动流程分析和自定义 View - 仿 QQ 运动步数进度效果,在写 仿 QQ 运动步数进度效果 的时候我带大家分析了 invalidate() 的源码,虽然文章中没提到,但在直播视频中我们有彻底的分析,我当时说了
invalidate() 源码分析只是一个小插曲。这期的重点是通过分析源码,彻底了解整个 View 的绘制加载流程。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:orientation="vertical"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView1"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView2"
android:textSize="20sp"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView3"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</LinearLayout>

</LinearLayout>
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
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
  上面是 R.layout.activity_main 的布局,下面是我们的显示效果,布局我写得非常简单,这样有利于我们分析源码。


 

  这有什么好分析的就是三个 TextView 垂直放在 LinearLayout 中,no,no,no 分析源码不光有利于我们自定义 View,还有很重要的一点就是优化,我们只有充分了解源码的前提下,才能做各种优化。
3.ViewRootImpl类的requestLayout()
  我在网上也看了一些文章,大家都写得非常不错,如果觉得我的分析不是很好,可以看看这篇Android 应用层 View 绘制流程与源码分析 ,我们要开始了,请握紧。
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
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
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
  这是 setContentView() 中的源码,里面有这样一行代码 mLayoutInflater.inflate(layoutResID, mContentParent); 会来到 ViewRootImpl 类的 requestLayout() 方法。虽然过程有点曲折,但是我之前的确讲过好几次了,View 的绘制流程就是从这个方法开始的。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 一系列方法下来之后,进入performTraversals这个方法真的很长
performTraversals();

if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
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
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
  一系列方法下来之后,进入 performTraversals() 这个方法真的很长,不要在这个方法里面游太久,这里我只贴一些关键代码:
private void performTraversals() {
// ... ...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// ... ...
performLayout(lp, mWidth, mHeight);
// ... ...
performDraw();
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
  里面有七八百行,最终我只挑了三行,这看源码的效率还是可以的,如果你想看得非常细可以一行一行去看,我敢保证在里面游个十天半个月也出不来。所以最终绘制流程其实就是上面的三个方法。
4. View绘制流程第一步:performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 调用onMeasure()
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
1
2
3
4
1
2
3
4
  如果是按照我们上面那个简单的布局小事例,接下来应该到了LinearLayout中的onMeasure()方法,当然这里我省了一些步骤,但不影响免得大家蒙B。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
// 我们以垂直为例
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
// 测量子孩子
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);

final int childHeight = child.getMeasuredHeight();

final int totalLength = mTotalLength;
// 高度是子View的高度不断的叠加
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
}
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
// 设置宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
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
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
  LinearLayout在调用onMeasure()方法的时候,会不断的循环测量子View,如果是垂直方向,高度是子View的高度叠加,我们现在来看看是怎么测量子View的。
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
1
2
3
4
5
6
1
2
3
4
5
6
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//  getChildMeasureSpec 这个方法非常关键
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  getChildMeasureSpec()这个方法可以说很重要,记得上次 自定义View简介 - onMeasure,onDraw,自定义属性 是下面这样,上次不想说太明白其实还是怕大家蒙B,上次说的其实有点小瑕疵,我们其实需要根据具体的源码来。 



public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取当前Parent View的Mode和Size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
int size = Math.max(0, specSize - padding);
//定义返回值存储变量
int resultSize = 0;
int resultMode = 0;
//依据当前Parent的Mode进行switch分支逻辑
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//如果child的layout_w和h属性在xml或者java中给予具体大于等于0的数值
//设置child的size为真实layout_w和h属性值,mode为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//如果child的layout_wOrh属性在xml或者java中给予MATCH_PARENT
//设置child的size为size,mode为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
//如果child的layout_wOrh属性在xml或者java中给予WRAP_CONTENT
//设置child的size为size,mode为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// ......
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
// 如果父 View 是 AT_MOST 就算子 View 是 MATCH_PARENT,
// 其实子View获得的测量模式还是AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// ......
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
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
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
  最后用大白话总结一下,View的绘制流程第一步是onMeasure(),该方法用来测量和指定布局到底占多大的宽高,因为控件的宽高是由父布局和本身来决定的,所以测量是不断的往内走,而最终确定宽高是由内不断的往外走,是递归的方式。
5.View绘制流程第二步:performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
// 调用layout()方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

public void layout(int l, int t, int r, int b) {
onLayout(changed, l, t, r, b);
}
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
  如果是按照我们上面那个简单的布局小事例,接下来应该到了LinearLayout中的onLayout()方法
@Override
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);
}
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;

int childTop;
int childLeft;

// Where right end of child should go
//计算父窗口推荐的子View宽度
final int width = right - left;
//计算父窗口推荐的子View右侧位置
int childRight = width - mPaddingRight;

// Space available for child
//child可使用空间大小
int childSpace = width - paddingLeft - mPaddingRight;
//通过ViewGroup的getChildCount方法获取ViewGroup的子View个数
final int count = getVirtualChildCount();
//获取Gravity属性设置
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//依据majorGravity计算childTop的位置值
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;

// mTotalLength contains the padding already
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) {
//LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//获取子View的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);
//依据不同的absoluteGravity计算childLeft位置
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;
}

childTop += lp.topMargin;
//通过垂直排列计算调运child的layout设置child的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

i += getChildrenSkipCount(child, i);
}
}
}
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
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
  用大白话总结一下,从上面分析可以看出layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据第一步performMeasure,来获取子View所的布局大小和布局参数,将子View放在合适的位置上,不过这个方法没有再往外走,只是不断的往里面走。
5.View绘制流程第三步:performDraw
private void performDraw() {
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}

if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
// ... ...
mView.draw(canvas);
}
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
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
  draw()方法这里我不打算再啰嗦了,在我的印象中我已经讲过三次了,包括里面用到了 模板设计模式等等。View的绘制流程其实就三个步骤:onMeasure(测量) -> onLayout(摆放) -> onDraw(绘制)
所有分享大纲:Android进阶之旅 - 自定义View篇
视频讲解地址:http://pan.baidu.com/s/1kUO0F7h
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐