深入理解View知识系列四-View的测量规则以及三大方法流程
2018-02-07 10:54
686 查看
通过前面几篇的深入分析,相信大家对View的理解已经很深了,我们说了setContentView背后做了什么,说了View从xml加载到通过WindowManager添加View后的一系列操作,说了Android的事件由来,Canvas的由来等等,这一篇我们将来分析View绘制的三大方法,即measure、layout、draw的工作过程以及一些相关参数的产生规则
深入理解View知识系列一- setContentView和LayoutInflater源码原理分析深入理解View知识系列二- View底层工作原理以及View的绘制流程
深入理解View知识系列三-Window机制、Canvas的由来、Android事件的由来
深入理解View知识系列四-View的测量规则以及三大方法流程
本篇你会学到什么
什么时候需要重新onMeasureView的测量规则
三大方法的流程
一些相关参数是如何产生的
上一篇回顾
上一篇我们只要说了一堆WindowXXX都是干什么的,之前有什么区别和联系,因为涉及了太多,所以详细的请看上一篇还说了Window的类型和级别,简单说Window包括三种类型,分别是:应用Window、子Window和系统Window,应用Window的级别对应1-99,子Window的级别对应1000-1999,系统Window对应2000-2999,级别越高的会覆盖在级别底Window上方,在WMS添加Window时对各种Window进行了检查,如果是系统Window则需要申请权限;如果是子Window,要求父Window必须存在;在7.1以后还专门对Toast类型的Window做了限制
我们还说了Surface是什么,它在ViewRootImpl中被创建,但是这只是一个空壳而已,是在绘制之前通过relayoutWindow方法去native层真正的创建Surface并填充到ViewRootImpl中的Surface里
还说了Canvas是怎么产生的,它是通过Surface被创建的,也就是说其实真正的画布应该是Surface
还有Android的事件是在哪里开发分发的,在ViewRootImpl中的setView方法中,创建了一个事件通道,并在添加Window的时候将这个通道传递到WMS中,WMS中也会创建一个通道并建立联系,然后WMS会通过InputManagerService将通道注册到native层,接着ViewRootImpl创建了一个用来接收事件的监听类,这个类叫WindowInputEventReceiver,所有的事件都是从这个类中开始分发,在分发的过程中会判断是按键事件还是触摸事件。
本编源码基于 7.1.1
测量规则
其实在View的三大方法中,最复杂的就是measure的过程,因为在这个过程中设计了多种模式的测量规则,下面进入正文,通过之前我们分析,我们知道View的真正绘制起始点是在ViewRootImpl中的performTraversals方法中,在这里会相应的调用performMeasure、performLayout、performDraw来分别对应View中的三大方法,在这三个方法中分别会调用View的measure、layout、draw方法,以measure为例,measure中会调用onMeasure方法,在onMeasure中会会所有的子元素进行measure过程,而所有的子元素又会重复这一操作,直到整个View树遍历完成,layout和draw的过程和measure是一样的,只不过他们中间执行的是onLayout和onDraw
理解MeasureSpec
这个类应该不会太陌生,它是View的静态内部类,从字面翻译是测量规格,不过确实也是这样。在重写一个View的onMeasure的时候,会给我们提供两个int参数,他们便是MeasureSpec,一个代表宽度的测量规格,一个代表高度的测量规格,这里说的MeasureSpec是通过MeasureSpec类生成的int值并不是MeasureSpec这个类。这个值是由父View传递到子View中的,MeasureSpec这个类通过计算后得出的int值是包装了View测量模式和尺寸的,这个得出的值一个32位的整型数,高两位代表了测量模式,后30位代表了测量尺寸,View的测量的模式分为3种,分别对应了UNSPECIFIED、EXACTLY、AT_MOST,我们可以通过这个int值获取到当前View的测量模式和尺寸。下面看一下这个类MeasureSpec值的产生有两种情况,一种是顶层View,对于顶层的DecorView计算MeasureSpec值是通过屏幕的宽高和自身的LayoutParams来决定的,另一种普通View是通过父View的MeasureSpec值和View本身的LayoutParams来决定的
public static class MeasureSpec { //用来标记需要位计算需要位数 private static final int MODE_SHIFT = 30; //可以理解为用来计算的key //0x3是16进制,对应的二进制是11,左移30位变成了 //1100 0000 0000 0000 0000 0000 0000 0000,最左端的数字代表符号位,0代表正数,1代表1负数 // 那么最终结果计算的10进制结果是 -1073741824 private static final int MODE_MASK = 0x3 << MODE_SHIFT; //无约束模式:计算的最终值是0 public static final int UNSPECIFIED = 0 << MODE_SHIFT; //精确模式: //1左移30位后的值 0100 0000 0000 0000 0000 0000 0000 //转换成10进制是 1073741824 public static final int EXACTLY = 1 << MODE_SHIFT; //最大模式: //2左移30位后 1000 0000 0000 0000 0000 0000 0000 //转换成10进制是 -2147483648 public static final int AT_MOST = 2 << MODE_SHIFT; //用来计算包装尺寸和测量模式的方法 //我们可以看到这里对传入的size做了限制,只能是0 - (1 << MeasureSpec.MODE_SHIFT) - 1)的值 public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int makeSafeMeasureSpec(int size, int mode) { if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { return 0; } //调用了上面的makeMeasureSpec return makeMeasureSpec(size, mode); } //从measurespec值中获取测量模式 public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } //从measurespec值中获取尺寸 public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } .... }
MeasureSpec这个类算上注释一共130多行代码,最主要的就上面几个常量和方法,可以看到MeasureSpec声明的几个常量,有一个用来计算的Key,还有三个测量模式,在注释中也给出了计算后的值,而且可以看到用来计算makeMeasureSpec的方法限制了传入的尺寸大小,只能是0 - (1 << MeasureSpec.MODE_SHIFT)
- 1)的值,那么也就是说这个View的最大尺寸也就只能是它,因为需要将高两位让出来作为测量模式,那么后30位最大也就是全是1,也就是0011 1111 1111 1111 1111 1111 1111 1111,这个数换算后的值为1073741823。
三种测量模式:UNSPECIFIED : 无约束模式:该模式下,父View不会施加任何的约束,也就是说想要多大给多大,一般情况下我们很少用到
EXACTLY : 精确模式:该模式下,一般对应于View的精确尺寸 或者 MATCH_PARENT,为什么说一般呢?这是因为还需要看父View是什么测量模式
AT_MOST : 最大模式:该模式下,子View可获得最大尺寸是父View的可用大小,一般对应于 WRAP_CONTENT
DecorView的测量规则
MeasureSpec值是通过父View传递过来的,而我们在之前的分析中说过,所有的View全部都存在ViewParent,哪怕是DecorView,它的Parent是ViewRootImpl,那么下面我们就先来看看ViewRootImpl对DecorView的测量规则,在ViewRootImpl中调用performMeasure之前会先计算出宽高MeasureSpec值。源码位置:/frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() { .... //计算MeasureSpec值 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); //测量宽高并传入刚刚计算的MeasureSpec值 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); .... }
我们可以看到宽高的MeasureSpec值都是调用了getRootMeasureSpec方法,而传入的两个值一个是屏幕的尺寸,另一个是Window的LayoutParams中的宽高尺寸,其实也就是DecorView自己的LayoutParams,进去看一下这个方法
源码位置:/frameworks/base/core/java/android/view/ViewRootImpl.java
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; //判断宽高 switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: //如果是MATCH_PARENT的情况 //创建measureSpec值,高度指定为屏幕宽高,模式为精确模式 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: //如果是WRAP_CONTENT //创建measureSpec值,宽度为屏幕宽高,模式为最大模式 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: //如果是其他的情况,宽高为DecorView的LayoutParams的宽高,模式为精确模式 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } //返回measureSpec值 return measureSpec; }
以上就是DecorView的测量规则,通过上面的代码我们可以看出来,会先判断DecorView自己的LayoutParams宽高尺寸,根据不同情况计算measureSpec值,这里说一下为什么会有这个判断,前面几篇我们介绍过,在DecorView创建的时候首先会获取Window的参数,根据不同的参数选择不同的布局加载,那么不同的布局可能设置的宽高并不一样
View的测量规则
接着上面说,DecorView我们应该都知道View的测量是调用了measure方法,并且这个方法是final的,也就是说不可以被重写的方法,但是不同的View样式也不同,那么测量的规则和计算逻辑也肯定不一样,所以android提供了onMeasure方法,这个方法会在measure中被调用,我们可以通过重写onMeasure来实现自己的测量规则并通过setMeasuredDimension(int measuredWidth, int measuredHeight)方法确定View的测量宽高。
那么能作为父View的只能是ViewGroup,可以通过查看源码可以看出来,View也确实没有实现ViewParent接口,而ViewGroup却实现了,可是通过查看源码发现ViewGroup是一个抽象类,并且没有重写onMeasure方法,不过想想这是正常的,毕竟每个ViewGroup的特性都不同,是没有办法统一实现的,例如FrameLayout、LinearLayout、RelativeLayout,都有属于自己的特性,所以他们只能根据自己的特性去实现测量规则,虽然ViewGroup没有重写onMeasure方法,但是它却提供了measureChildWithMargins、measureChild方法来来对子View进行测量,这两个方法类似,只不过是一个包含了子View的Margins一个不包含,我们我们看一下measureChildWithMargins。
源码位置:/frameworks/base/core/java/android/view/ViewGroup.java
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { //获取子View的LayoutParams final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //获取子View的MeasureSpec值 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方法 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
我们可以看到子View的MeasureSpec值是通过getChildMeasureSpec方法得到的,传入的三个参数分别为父View的measureSpec值,自身的padding值和子View的margin值相加,子View的尺寸,我们再看看measureChildWithMargins方法
源码位置:/frameworks/base/core/java/android/view/ViewGroup.javapublic static int getChildMeasureSpec(int spec, int padding, int childDimension) { // 获取父View的测量模式和尺寸 int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); //计算最大的可用尺寸 int size = Math.max(0, specSize - padding); //定义最终的尺寸和模式 int resultSize = 0; int resultMode = 0; //判断父View的模式 switch (specMode) { // 如果父View是精确模式 case MeasureSpec.EXACTLY: //再判断子View自己的尺寸 if (childDimension >= 0) { //如果大于0,说明是具体的尺寸,那么定义最终的尺寸就是子View声明的尺寸,模式为精确模式 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 如果是MATCH_PARENT,定义最终的尺寸为父View的最大可用尺寸,模式为精确模式 resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // 如果是WRAP_CONTENT,定义最终的尺寸为父View的最大可用尺寸,模式为最大模式 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 如果父View是最大模式 case MeasureSpec.AT_MOST: //再判断子View的尺寸 if (childDimension >= 0) { //如果大于0,说明是具体的尺寸,那么定义最终的尺寸就是子View声明的尺寸,模式为精确模式 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 如果是MATCH_PARENT,定义最终的尺寸为父View的最大可用尺寸,模式为最大模式 resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // 如果是WRAP_CONTENT,定义最终的尺寸为父View的最大可用尺寸,模式为最大模式 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 如果是UNSPECIFIED模式 case MeasureSpec.UNSPECIFIED: //再判断子View的尺寸 if (childDimension >= 0) { //如果大于0,说明是具体的尺寸,那么定义最终的尺寸就是子View声明的尺寸,模式为精确模式 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 如果是MATCH_PARENT,在判断View中sUseZeroUnspecifiedMeasureSpec的值,true则尺寸为0,否则为父View的最大可用尺寸,模式为UNSPECIFIED resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // 如果是WRAP_CONTENT,在判断View中sUseZeroUnspecifiedMeasureSpec的值,true则尺寸为0,否则为父View的最大可用尺寸,模式为UNSPECIFIED resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //通过MeasureSpec类生成子View的measureSpec值 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
getChildMeasureSpec的方法看着很长,其实逻辑很简单,主要就是根据父View的测量模式和子View的尺寸最终生成子View自己的MeasureSpec值,我们可以看出来,父View的测量模式不同,那么计算规则也不同,但是总结一下,就只有如下几种情况,这里我们我们不分析UNSPECIFIED模式,这种模式很少用
不管父View是哪种模式,只要子View的尺寸大于0,那么它最终的尺寸就是子View自己的尺寸,并且模式为精确模式如果子View的尺寸是MATCH_PARENT,那么就要看父View是哪种模式的,子View的模式和父View相同,并且尺寸为父View的最大可用尺寸(不包括父View为UNSPECIFIED模式时)
如果子View的尺寸是WRAP_CONTENT,那么子View的的模式为AT_MOST最大模式,尺寸为父View的最大可用尺寸(不包括父View为UNSPECIFIED模式时)
measure过程
上面说了需要通过onMeasure方法来完成View的测量,如果只是一个普通View的话通过onMeasure方法就可以确定自己的尺寸了,但是如果是ViewGroup的话还需要在这里对所有的子View进行遍历并执行measure方法,现在我们先来看一下View中的onMeasure方法源码位置:/frameworks/base/core/java/android/view/View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //直接设置View的宽高尺寸,但是传入的参数是getDefaultSize方法 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
我们看到View的onMeasure方法直接调用了setMeasuredDimension方法来设置View的宽高,方法中传入的是getDefaultSize方法,我们看看getDefaultSize方法做了什么。
源码位置:/frameworks/base/core/java/android/view/View.java
public static int getDefaultSize(int size, int measureSpec) { //声明最后需要返回的尺寸为传入的尺寸 int result = size; //获取父View的模式和尺寸 int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { //如果是UNSPECIFIED模式的话则返回的是传入的尺寸 case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: //其他情况,将尺寸赋值为父View的可用大小尺寸 result = specSize; break; } return result; }
我们看到了,View设置默认大小的方法中,会判断父View的测量模式,如果是UNSPECIFIED模式,最终的尺寸是传进来的尺寸,否则其他两种情况全部将最终的尺寸指定为父容器的最大可用大小,因为这里的measureSpec值是父View传递过来的。size的值是通过一个是getSuggestedMinimumWidth或者一个是getSuggestedMinimumHeight方法生成的,我们来看看做了什么
源码位置:/frameworks/base/core/java/android/view/View.java
protected int getSuggestedMinimumHeight() { //判断是否存在背景 return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
上面两个方法都是判断了mBackground是否为空,这个mBackground是Drawable类型,如果不存在背景则直接返回mMinHeight或者mMinWidth,这两个值对应于android:minHeight和android:minWidth两个属性,如果不指定,默认为0。如果mBackground不为空,则会返回View的最小尺寸和Drawable的最小尺寸的最大值,这里需要注意一点ShapeDrawable默认没有原始的宽高。通过View中的onMeasure方法我们可以得出一个结论,直接继承View的控件一般情况下需要重写onMeasure方法,因为View的默认实现中在View设置为wrap_content的时候返回的大小是父View的最大可用尺寸,当然不排除你本身就想要这种效果!
下面我们在看一下ViewGroup的measure过程,View是一个抽象类,它并没有重写onMeasure方法,因为什么上面已经说过了,每种ViewGroup都有自己的特性,无法统一实现,这里我们就拿FrameLayout来分析,因为DecorView就继承了FrameLayout,这样对整个流程会更清晰,DecorView也重写的onMeasure方法,但是在onMeasure中又调用了super.onMeasure方法,最终还是走到FrameLayout中。
源码位置:/frameworks/base/core/java/android/weight/FrameLayout.java@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //获取子元素的个数 int count = getChildCount(); //判断父View的宽高测量模式中是否不为精确模式 final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; //遍历所有的子元素 for (int i = 0; i < count; i++) { //获取每一个子元素 final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { //使用ViewGroup提供的测量方法,这个方法我们上面分析过,该方法会调用子元素的measure方法,如果子元素还是ViewGroup会再次重复整个逻辑 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); //计算宽高 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); //父View的宽高测量模式至少有一个不为精确模式 if (measureMatchParentChildren) { //自己的尺寸至少有一个为MATCH_PARENT则添加到集合中 if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } // 调整宽高 maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // 调整宽高 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // 调整宽高 final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } //确定宽高 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); //如果父View的测量模式至少有一个为不是精确模式,并且子元素的宽高为MATCH_PARENT,则进行重新测量计算 if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } final int childHeightMeasureSpec; if (lp.height == LayoutParams.MATCH_PARENT) { final int height = Math.max(0, getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }
这里我们可以看到,FrameLayout会遍历所有的子元素调用ViewGroup提供的测量子元素的方法,如果被测量的子元素还是ViewGroup则会重复执行measure-onMeasure方法,直到整个View树遍历完成
layout过程
layout的过程主要是ViewGroup用来确定子元素位置的一个过程,ViewGroup重写了View的layout方法,并且加入了final,不允许自雷重写,并且抽象了onLayout方法,继承ViewGroup的子类必须重写onLayout方法,这也更能说明layout过程的作用。当ViewGroup的位置确定以后会在onLayout方法中遍历所有的子元素并调用子元素的layout方法,而View会通过layout方法来确定自己的位置。在ViewRootImpl中会在performLayout方法中调用DecorView的layout方法,而DecorView继承FrameLayout,也就是一个ViewGroup,在ViewGroup的layout方法中又调用了super.layout方法,也就是说我们只需要看View的layout方法就好了源码位置:/frameworks/base/core/java/android/view/View.java
public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } //声明view的四个坐标点 int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; //最终都会调用setFrame方法,setFrame方法确定了View自身的四个坐标点的位置 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //调用onLayout方法 onLayout(changed, l, t, r, b); .... } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }
我们看到上面首先通过setFrame方法来确定了本身的4个坐标点,接着会调用onLayout方法,当View确定了坐标点后,我们就可以通过getWidth,getHeight方法来获取View的宽高的
protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; ... //主要来确定4个坐标点的位置 mLeft = left; mTop = top; mRight = right; mBottom = bottom; ... return changed; } public final int getWidth() { return mRight - mLeft; } public final int getHeight() { return mBottom - mTop; }
现在我们应该知道View的宽高尺寸必须要在View的layout方法走完了以后才可以获取的到。接着分析View的layout方法,这里假设这就是DecorView的流程,它在ViewRootImpl中被调用了mView.layout方法,这个方法最终会执行到View中,也就是上边的layout方法,那么它首先会通过setFrame方法确定自己的4个坐标点,接着调用自己的onLayout方法,而DecorView又继承FragmentLayout,FrameLayout继承了ViewGroup,是必须重写onLayout的,记下来我们就去看一下FrameLayout中的onLayout方法
源码位置:/frameworks/base/core/java/android/weight/FrameLayout.java
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { //逻辑全部封装到了layoutChildren方法中 layoutChildren(left, top, right, bottom, false /* no force left gravity */); } // FrameLaoyut中onLayout的所有逻辑 void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { //获取子元素的个数 final int count = getChildCount(); ... for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; ... //调用子元素的layout方法,如果子元素还是ViewGroup的话会重复以上过程 child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
整个逻辑主要就是为了遍历所有的子元素,然后按照属于自己的特性和规则来调用子元素的layout方法,当子元素还是ViewGroup的话,会继续重复这一过程,知道整个View树全部layout完成
drawa过程
draw过程主要就是将View绘制到屏幕上的一个过程,上次我们也说过了draw方法中的Canvas是通过Surface创建并传递过来的,在ViewRootImpl中会通过performDraw方法开始draw的过程,最后通过drawSoftware方法创建Canvas并调用DecorView的draw方法,来看一下View的draw方法@CallSuper public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * 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 int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // we're done... return; } ... }
通过上面的注释也可以看出来,View的绘制主要分为6步,而且注释中还有一句 skip step 2 & 5 if possible (common case),多数情况下会跳过2和5,我们也只说其他几部
绘制背景调用onDraw方法,如果有自己的绘制规则的话
调用dispatchDraw方法来绘制所有的子View
绘制装饰(例如滚动条)
这里我们接着说绘制的过程,可以看到draw方法的第4步,调用了dispatchDraw方法,这个方法在View中是一个空实现,但是在ViewGroup中可就不是了,来看看
protected void dispatchDraw(Canvas canvas) { .... for (int i = 0; i < childrenCount; i++) { while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { final View transientChild = mTransientViews.get(transientIndex); if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) { //绘制子元素 more |= drawChild(canvas, transientChild, drawingTime); } transientIndex++; if (transientIndex >= transientCount) { transientIndex = -1; } } final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { //绘制子元素 more |= drawChild(canvas, child, drawingTime); } } //省略的代码也是一堆绘制子元素的逻辑 .... ... } //drawChild方法 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
其实最终还是要调用子元素的draw方法,当被调用的子元素还是一个ViewGroup的时候还会继续重复这一动作,直到整个View树绘制完成。
相关文章推荐
- 深入理解View知识系列二- View底层工作原理以及View的绘制流程
- 深入理解View知识系列三-Window机制、Canvas的由来、Android事件的由来
- 深入理解View知识系列一- setContentView和LayoutInflater源码原理分析
- 深入理解Android中View绘制的三大流程
- Android中View绘制流程以及invalidate()等相关方法分析
- 深入理解mysql之BDB系列(1)---BDB相关基础知识
- Android中View绘制流程以及invalidate()等相关方法分析
- Android中View绘制流程以及invalidate()等相关方法分析
- 个人总结如何学习新知识与对知识技术进行深入理解的方法
- Android中View绘制流程以及invalidate()等相关方法分析
- Android中View绘制流程以及invalidate()等相关方法分析
- 深入理解JavaScript系列(41):设计模式之模板方法
- [C# 基础知识系列]专题七: 泛型深入理解(一)
- Android中View绘制流程以及invalidate()等相关方法分析
- Android中View绘制流程以及invalidate()等相关方法分析
- Android中View绘制流程以及invalidate()等相关方法分析
- Android中View绘制流程以及invalidate()等相关方法分析
- Android中View绘制流程以及invalidate()等相关方法分析
- Android中View绘制流程以及invalidate()等相关方法分析
- Android View绘制流程以及invalidate()等相关方法分析