View绘制流程源码解析
2017-08-27 16:52
766 查看
View绘制流程源码解析
标签: android源码绘制流程2017-05-23 17:37 219人阅读 评论(0) 收藏 举报
![](http://static.blog.csdn.net/images/category_icon.jpg)
分类:
自定义View(25)
![](http://static.blog.csdn.net/images/arrow_triangle%20_down.jpg)
版权声明:本文为博主原创文章,未经博主允许不得转载。
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 的布局,下面是我们的显示效果,布局我写得非常简单,这样有利于我们分析源码。
![](http://upload-images.jianshu.io/upload_images/4314397-ecaa9cd7256136dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这有什么好分析的就是三个 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,上次说的其实有点小瑕疵,我们其实需要根据具体的源码来。
![](http://upload-images.jianshu.io/upload_images/4314397-f1baa5d8bfb90bbb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
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
相关文章推荐
- Android 开源项目源码解析 -->公共技术点之 View 绘制流程(三)
- 安卓5.1源码解析 : RecyclerView解析 从绘制流程,ViewHolder复用机制,LayoutManger,ItemAnimator等流程全面讲解
- View绘制流程及源码解析(一)——performTraversals()源码分析 96 游骑兵810 关注 2017.02.01 11:07* 字数 5024 阅读 945评论 3喜欢 6 本篇文
- 源码解析---第一个view的绘制流程
- View绘制流程源码解析
- Android View绘制流程与源码解析
- 安卓5.1源码解析 : RecyclerView解析 从绘制流程,ViewHolder复用机制,LayoutManger,ItemAnimator等流程全面讲解
- 从源码解析RecyclerView绘制流程
- android应用程序窗口框架学习(2)-view绘制流程源代码解析-setContentView与LayoutInflater加载解析机制源码分析
- 安卓5.1源码解析 : RecyclerView解析 从绘制流程,ViewHolder复用机制,LayoutManger,ItemAnimator等流程全面讲解
- 安卓5.1源码解析 : RecyclerView解析 从绘制流程,ViewHolder复用机制,LayoutManger,ItemAnimator等流程全面讲解
- 安卓5.1源码解析 : RecyclerView解析从绘制流程,ViewHolder复用机制,LayoutManger,ItemAnimator等流程全面讲解
- 开源项目源码解析-View 绘制流程
- Android从源码解析三:View绘制流程
- Android Render(四)supportVersion 27.0.0源码RecyclerView绘制流程解析
- View的绘制流程源码分析
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- android源码解析(二十二)-->Toast加载绘制流程
- android应用程序窗口框架学习(1)-view绘制流程源代码解析