Android实现机制(二)——View绘制机制
2016-08-01 17:41
423 查看
View的绘制主要分为三步
measure
layout
draw
这个流程的首先是由ViewRootImpl类的performTraversals()方法开始的,这个方法会判断是否需要mesure、layout、draw。
View的measure过程从root view开始,下面是其流程
RootViewViewGroup measureViewGroup onMeasureis View?View measureView onMeasureis loop okMeasure overyesyesno
看一下View类的measure方法
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
根据注释可以发现,View的实际宽高是在父视图和onMeasure中决定的,由于measure方法为final的,所以实现自己的测量逻辑就需要在onMeasure回调中完成。
换言之即父View调用子View的measure,告诉了宽、高,而子View只能通过自己的回调去改变父View通知的宽高。
再看一下onMeasure的源码
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
setMeasuredDimension是在对View的mMeasuredWidth和mMeasuredHeight赋值,所以View的测量的终点就是为这俩成员变量赋值。默认的回调会将default的值作为宽高,通过下面源码看看这些默认值是什么。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如果specMode等于AT_MOST或EXACTLY就返回specSize,这就是系统默认的规格。
其中getDefaultSize参数的widthMeasureSpec和heightMeasureSpec都是由父View传递进来的。getSuggestedMinimumWidth与getSuggestedMinimumHeight都是View的方法,看一下源码
2
3
4
5
6
7
8
可见,建议的大小就是background的大小或者minWidth、minHeight的大小决定的。
上面是View的measure过程,而ViewGroup嵌套着View,所以ViewGroup还要去对子View进行测量,因此定义了measureChildren, measureChild, measureChildWithMargins,measureChildren内部实质只是循环调用measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也作为子视图的大小,看一下measureChildWithMargins的源码
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
可以看出来,该过程就是结合父View对子View的参数做了调整,然后调用子View的measure。
通过上面的分析,View的测量过程就是root view递归子View的measure方法。
MeasureSpec(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。其中specMode只有三种值:
2
3
View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。
最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。
ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。
只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。
View的布局大小由父View和子View共同决定。
使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值
当measure过程完成后,就会执行layout过程,layout接收四个参数,即左、上、右、下坐标,左上都是0,右下为上面测量出来的值。
其过程与measure类似
RootViewViewGroupLayoutViewGroup onLayoutis View?View LayoutView onLayoutis loop okLayout overyesyesno
先从View的layout方法分析
2
3
4
5
6
7
8
9
10
11
12
13
14
ViewGroup的layout
2
3
4
5
6
View的layout方法是可以在子类重写的,而ViewGroup的layout是不能在子类重写的,言外之意就是说ViewGroup中只能通过重写onLayout方法。那我们接下来看下ViewGroup的onLayout方法,如下:
2
3
这个方法是个抽象方法,也就是说ViewGroup的子类必须实现这个方法,所以自定义的ViewGroup的onLayout和onMeasure配合实现复杂的布局效果。
整个layout过程比较容易理解,从上面分析可以看出layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。具体layout核心主要有以下几点:
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流程之后被调用才能返回有效值。
从上面分析,当measure和layout完后,就会draw了。
draw过程也是在ViewRootImpl的performTraversals()内部调运的,其调用顺序在measure()和layout()之后,这里的mView对于Actiity来说就是PhoneWindow.DecorView,ViewRootImpl中的代码会创建一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工。所以又回归到了ViewGroup与View的树状递归draw过程。
RootViewViewGroupDrawViewGroup onDrawViewGroup dispatchDrawViewGroup onDrawScrollbarsis View?View DrawView onDrawView dispatchDrawView onDrawScrollbarsis loop okLayout overyesyesno
View的draw方法很复杂,总结一下,分为6个步骤,其中第二步和第五步不是必须的
1. 背景进行绘制
2. 内容进行绘制
调用了onDraw方法
3. 对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制
调用dispatchDraw,这个方法是ViewGroup调用的
4. 对View的滚动条进行绘制
可以看见其实任何一个View都是有(水平垂直)滚动条的,只是一般情况下没让它显示而已。
绘制过程就是把View对象绘制到屏幕上,整个draw过程需要注意如下细节:
如果该View是一个ViewGroup,则需要递归绘制其所包含的所有子View。
View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。
View的绘制是借助onDraw方法传入的Canvas类来进行的。
区分View动画和ViewGroup布局动画,前者指的是View自身的动画,可以通过setAnimation添加,后者是专门针对ViewGroup显示内部子视图时设置的动画,可以在xml布局文件中对ViewGroup设置layoutAnimation属性(譬如对LinearLayout设置子View在显示时出现逐行、随机、下等显示等不同动画效果)。
在获取画布剪切区(每个View的draw中传入的Canvas)时会自动处理掉padding,子View获取Canvas不用关注这些逻辑,只用关心如何绘制即可。
默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。
measure
layout
draw
这个流程的首先是由ViewRootImpl类的performTraversals()方法开始的,这个方法会判断是否需要mesure、layout、draw。
第一步:measure
View的measure过程从root view开始,下面是其流程RootViewViewGroup measureViewGroup onMeasureis View?View measureView onMeasureis loop okMeasure overyesyesno
看一下View类的measure方法
/** * <p> * This is called to find out how big a view should be. The parent * supplies constraint information in the width and height parameters. * </p> * * <p> * The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only * {@link #onMeasure(int, int)} can and must be overridden by subclasses. * </p> * * * @param widthMeasureSpec Horizontal space requirements as imposed by the * parent * @param heightMeasureSpec Vertical space requirements as imposed by the * parent * * @see #onMeasure(int, int) */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ...... //回调onMeasure() onMeasure(widthMeasureSpec, heightMeasureSpec); ...... }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
根据注释可以发现,View的实际宽高是在父视图和onMeasure中决定的,由于measure方法为final的,所以实现自己的测量逻辑就需要在onMeasure回调中完成。
换言之即父View调用子View的measure,告诉了宽、高,而子View只能通过自己的回调去改变父View通知的宽高。
再看一下onMeasure的源码
/** * <p> * Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and * should be overriden by subclasses to provide accurate and efficient * measurement of their contents. * </p> * * <p> * <strong>CONTRACT:</strong> When overriding this method, you * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the * measured width and height of this view. Failure to do so will trigger an * <code>IllegalStateException</code>, thrown by * {@link #measure(int, int)}. Calling the superclass' * {@link #onMeasure(int, int)} is a valid use. * </p> * * <p> * The base class implementation of measure defaults to the background size, * unless a larger size is allowed by the MeasureSpec. Subclasses should * override {@link #onMeasure(int, int)} to provide better measurements of * their content. * </p> * * <p> * If this method is overridden, it is the subclass's responsibility to make * sure the measured height and width are at least the view's minimum height * and width ({@link #getSuggestedMinimumHeight()} and * {@link #getSuggestedMinimumWidth()}). * </p> * * @param widthMeasureSpec horizontal space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * @param heightMeasureSpec vertical space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * * @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) */ //View的onMeasure默认实现方法 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }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
setMeasuredDimension是在对View的mMeasuredWidth和mMeasuredHeight赋值,所以View的测量的终点就是为这俩成员变量赋值。默认的回调会将default的值作为宽高,通过下面源码看看这些默认值是什么。
public static int getDefaultSize(int size, int measureSpec) { int result = size; //通过MeasureSpec解析获取mode与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; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如果specMode等于AT_MOST或EXACTLY就返回specSize,这就是系统默认的规格。
其中getDefaultSize参数的widthMeasureSpec和heightMeasureSpec都是由父View传递进来的。getSuggestedMinimumWidth与getSuggestedMinimumHeight都是View的方法,看一下源码
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }1
2
3
4
5
6
7
8
可见,建议的大小就是background的大小或者minWidth、minHeight的大小决定的。
上面是View的measure过程,而ViewGroup嵌套着View,所以ViewGroup还要去对子View进行测量,因此定义了measureChildren, measureChild, measureChildWithMargins,measureChildren内部实质只是循环调用measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也作为子视图的大小,看一下measureChildWithMargins的源码
/** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding * and margins. The child must have MarginLayoutParams The heavy lifting is * done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param widthUsed Extra space that has been used up by the parent * horizontally (possibly by other children of the parent) * @param parentHeightMeasureSpec The height requirements for this view * @param heightUsed Extra space that has been used up by the parent * vertically (possibly by other children of the parent) */ protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { //获取子视图的LayoutParams final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //调整MeasureSpec //通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格 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的measure方法,子View的measure中会回调子View的onMeasure方法 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }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
可以看出来,该过程就是结合父View对子View的参数做了调整,然后调用子View的measure。
通过上面的分析,View的测量过程就是root view递归子View的measure方法。
MeasureSpec(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。其中specMode只有三种值:
MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定; MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值; MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定;1
2
3
View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。
最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。
ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。
只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。
View的布局大小由父View和子View共同决定。
使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值
第二步:layout
当measure过程完成后,就会执行layout过程,layout接收四个参数,即左、上、右、下坐标,左上都是0,右下为上面测量出来的值。 其过程与measure类似
RootViewViewGroupLayoutViewGroup onLayoutis View?View LayoutView onLayoutis loop okLayout overyesyesno
先从View的layout方法分析
public void layout(int l, int t, int r, int b) { ...... //实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量 //判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新layout boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //需要重新layout if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //回调onLayout onLayout(changed, l, t, r, b); ...... } ...... }1
2
3
4
5
6
7
8
9
10
11
12
13
14
ViewGroup的layout
@Override public final void layout(int l, int t, int r, int b) { ...... super.layout(l, t, r, b); ...... }1
2
3
4
5
6
View的layout方法是可以在子类重写的,而ViewGroup的layout是不能在子类重写的,言外之意就是说ViewGroup中只能通过重写onLayout方法。那我们接下来看下ViewGroup的onLayout方法,如下:
@Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);1
2
3
这个方法是个抽象方法,也就是说ViewGroup的子类必须实现这个方法,所以自定义的ViewGroup的onLayout和onMeasure配合实现复杂的布局效果。
整个layout过程比较容易理解,从上面分析可以看出layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。具体layout核心主要有以下几点:
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
从上面分析,当measure和layout完后,就会draw了。 draw过程也是在ViewRootImpl的performTraversals()内部调运的,其调用顺序在measure()和layout()之后,这里的mView对于Actiity来说就是PhoneWindow.DecorView,ViewRootImpl中的代码会创建一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工。所以又回归到了ViewGroup与View的树状递归draw过程。
RootViewViewGroupDrawViewGroup onDrawViewGroup dispatchDrawViewGroup onDrawScrollbarsis View?View DrawView onDrawView dispatchDrawView onDrawScrollbarsis loop okLayout overyesyesno
View的draw方法很复杂,总结一下,分为6个步骤,其中第二步和第五步不是必须的
1. 背景进行绘制
2. 内容进行绘制
调用了onDraw方法
3. 对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制
调用dispatchDraw,这个方法是ViewGroup调用的
4. 对View的滚动条进行绘制
可以看见其实任何一个View都是有(水平垂直)滚动条的,只是一般情况下没让它显示而已。
绘制过程就是把View对象绘制到屏幕上,整个draw过程需要注意如下细节:
如果该View是一个ViewGroup,则需要递归绘制其所包含的所有子View。
View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。
View的绘制是借助onDraw方法传入的Canvas类来进行的。
区分View动画和ViewGroup布局动画,前者指的是View自身的动画,可以通过setAnimation添加,后者是专门针对ViewGroup显示内部子视图时设置的动画,可以在xml布局文件中对ViewGroup设置layoutAnimation属性(譬如对LinearLayout设置子View在显示时出现逐行、随机、下等显示等不同动画效果)。
在获取画布剪切区(每个View的draw中传入的Canvas)时会自动处理掉padding,子View获取Canvas不用关注这些逻辑,只用关心如何绘制即可。
默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。
相关文章推荐
- View 绘制机制 -- How Android Draws Views
- Android 绘制 View 机制(API 翻译)
- Android 中View的绘制机制源码分析 二
- Android View的绘制机制流程深入详解(四)
- android View 绘制机制
- Android开发:ImageView上绘制旋转圆环(透明度不同的旋转圆环,利用canvas.drawArc实现)
- Android View绘制机制
- Android 中View的绘制机制源码分析 三
- Android View 与ViewGroup绘制即事件分发机制
- Android View的绘制机制流程深入详解(三)
- Android UI 之居中绘制文本内容的正确方法——实现自定义一个TextView
- Android View绘制机制分析
- Android实现自定义view---绘制图片
- Android 中View的绘制机制源码分析 四
- Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理:底层SurfaceView+上层绘制ImageView)
- Android UI 之居中绘制文本内容的正确方法——实现自定义一个TextView
- Android一种View动态折线绘制的实现实战
- Android UI 绘制机制之View创建过程
- Android View的绘制机制流程深入详解(一)
- 从源码角度分析Android View的绘制机制(一)