View的绘制主要分为三步
这个流程的首先是由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(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) */ 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; 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()); }
可见,建议的大小就是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) { 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); }
- 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 MeasureSpec.AT_MOST MeasureSpec.UNSPECIFIED
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) { ...... 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); ...... } ...... }
ViewGroup的layout
@Override public final void layout(int l, int t, int r, int b) { ...... super.layout(l, t, r, b); ...... }
View的layout方法是可以在子类重写的,而ViewGroup的layout是不能在子类重写的,言外之意就是说ViewGroup中只能通过重写onLayout方法。那我们接下来看下ViewGroup的onLayout方法,如下:
@Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
这个方法是个抽象方法,也就是说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()方法提供不同顺序。
1 0
- Android实现机制(二)——View绘制机制
- Android View绘制机制基础(二)
- Android View绘制机制
- Android View绘制机制
- android View 绘制机制
- 【View】Android View绘制机制
- Android 绘制 View 机制(API 翻译)
- Android View绘制机制基础(一)
- Android View绘制机制基础(三)
- Android View绘制机制分析
- Android view绘制机制探究
- Android中View 绘制机制
- android View的绘制机制
- Android 中View的绘制机制源码分析 二
- Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解
- Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解
- Android实现机制(三)——View事件分发机制
- Android----View事件分发机制(二)
- ORACLE 左连接/右连接/全连接
- 中国这10家慕课网站,您需要知道
- Multipath TCP Port for Android
- Android Test(未完工)
- Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解
- Android实现机制(二)——View绘制机制
- js数据类型
- 电商设计架构
- 小试循环
- poj 1472 Instant Complexity
- OpenLDAP(2.4.3x)服务器搭建及配置说明
- svn log 无法显示最近的信息解决办法
- 关于delphi的PChar, PWideChar, pAnsiChar
- Google Chrome调试js代码