Android View的绘制过程
首先是view的绘制过程~最主要的分三部分measure layout draw看字面意思,计算,布局,画~android中控件相当于是画在一个无限大的画布上的,那就产生了几个问题画布无限大,但是画的内容肯定是有限的,即我们只需要画布的一小部分,那这部分有多大呢?measure就是计算这个画布所需部分有多大的决定好我们需要的画布部分,我们可能会在上面画很多内容,每个内容都画在什么位置呢?layout就是决定在选定范围内画在什么位置的最后,决定好画在具体位置时,我们到底画什么内容呢?draw自然就是决定画什么具体内容的了而三个步骤对应的处理我们都可以在对应的on...方法中实现即onMeasure onLayout onDraw本系列会逐一分析三者的原理以及我们如何去使用,全部分析完以后会将三者整合起来详细分析-----------------------------------------------------------------------------------------------------------本章,我们研究measure部分~也是绘制三部曲滴第一部开始之前先解释几个重要的概念,一定要理解这几个概念,不然后面分析会让人无法理解~1.measure过程中使用的宽高值是一个特殊的类型,由数值和类型共同组成在下面分析代码中看到的,比如onMeasure方法的参数width/heightMeasureSpec都是这种特殊值,虽然是int值,但并非只是个简单数字,而是包含期望类型的一个特殊宽高值~即由宽高数值+宽高期望类型值共同拼接而成这个期望类型有三种模式,分别是MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY和MeasureSpec.AT_MOST下面是介绍
- EXACTLY表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
- AT_MOST表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
- UNSPECIFIED表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
类型实质上也都是数字,数值和类型都是int,如何组成的呢?原理是类型为左移30位的高位数字,数值为不移位的低位数字,最后拼成一个带有类型和大小数值的特殊int看2进制结果比较明显(如果直接打印int值看10进制数字可能会奇怪,我操这么大的数字是怎么回事)比如宽度数值为800,期望类型为EXACTLY=1,最终组成的结果就是1即右起数第31位是类型值1,加粗部分右边的1100100000就是数值大小部分,换成10进制就是800如何将数值和类型具体组装成一个这样完整的特殊值呢~用MeasureSpec.makeMeasureSpec(数值, 类型)方法生成即可2.一个View的最终大小不是简单的由布局文件中的width和height决定的,还需要考虑parent和child的情况诸如WRAP_CONTENT和MATCH_PARENT都是需要根据parent和child的情况共同决定的,而作为parent对child的期望值,也是无法直接决定控件最终大小的,切记,期望值只个参考补充说明下:1) 期望值这种东西并不能直接决定child的实际值,只是对child的一个期望~换句话说,child可以根据情况自行判断遵从期望或者无视parent对你的期望,自行设置宽高2) 而不同的child对待同一个期望类型的处理方法也是不同的3) 对child期望类型的设置一般也要根据child的LayoutParams.width/height等情况来设定4) 一个ViewGroup有可能作为其他View的parent也可能作为其他view的child(View只能作为child)所以设计自定义ViewGroup时需要又考虑parent的期望又要考虑对child的期望设置举个例子对比下,帮助理解比如parent父控件相当于父母,parent的期望类型就相当于父母希望你发展的方向,上面几点就可以这么理解1) 父母希望你以后能往艺术方向发展,那你偏不,就非要当程序猿~虽然不按套路来但谁也管不了你,最终决定权还是在你手里的2) 父母有三个孩子,希望他们都往艺术方向发展,熊孩子们也遵从了,但是个人理解不同熊孩子一号搞了美术,二号整了音乐,三号玩起了人体艺术但是如果父母希望熊孩子搞经济方面,也就是赚钱为主呢~每个人也还是有不同理解熊孩子一号和二号都选了开咖啡店,三号则开了洗脚城- -3) 父母对孩子的期望也看孩子,会在一定意义上遵从孩子的意见如果孩子从小脑子聪明,那自然就希望他赚钱~如果孩子从小就对艺术感兴趣,自然就期望他在艺术方面有发展了~4) 你可能是当儿女的,又可能是当爹妈的,所有两者都要考虑到------------------------------------------------------------------------------------------------------------如果自定义控件需要确定自己的大小,可以复写onMeasure方法在其中处理,若是继承ViewGroup则即可能作为parent又可能作为child,需要处理两部分,对自己和对child的measure若为View的子类,则只用处理自己的measure即可,没有child所以不用考虑对其的measure一. 对自己的measure(View和ViewGroup都需要)是计算控件自己的大小,布局中参数只是一个你想要的值,实际控件的size需要结合其parent的期望大小一起决定,对于match_parent和wrap_content情况更是需要结合parent和child才能共同决定处理方法:1. 自己直接设置处理 setMeasureDimension(int measureWidth, int measureHeight);该方法是直接设置控件宽高的,比如自定义View的onMeasure中直接setMeasureDimension(任意值, 任意值)那无论xml布局文件中的宽高写多少,最终size都还是setMeasureDimension中传入的固定值了~当然肯定是不能这么乱来的,按照方法注释里面的说法是,开发者有义务遵循几个原则,要按照套路来,不能瞎搞,不但要考虑到parent对你的期望值,即复写onMeasure方法时的两个参数, 还有要有最小值等其他处理2.调用父类处理方法 super.onMeasure();直接交给parent处理,自己不做处理一般来说直接继承ViewGroup的,比如LinearLayout,RelativeLayout,Framelayout等都是自己处理的(方法1)而间接继承ViewGroup的,即是继承于LinearLayout等这样ViewGroup子类的,比如ScrollView,那如果没有特殊需要则多是直接交给父类处理(方法2)二. 对child的measure(只有ViewGroup需要)对child的measure即你对于child的期望值设置,child.measure()这样child再根据你传给他的期望值结合child自己的情况决定他的最终大小注意对child的measure处理是需要自己处理的,换句话说,如果你复写了一个ViewGroup只在里面measure了自己,那child的onMeasure方法是不会自动调用的,一定要手动调用child.measure方法才能触发,但若是间接继承,比如继承了FrameLayout,那只需super.onMeasure就行了,因为FrameLayout自己已经for循环measure过所有child了这样ViewGroup中measure自身,然后measure child,如果child也是一个ViewGroup又要measure child自身,然后measure child的child,这是毅种循环,最终完成对整个View树结构所有控件的measure------------------------------------------------------------------------------------------------到这里可能有细心的同学会有发现一个疑惑我们知道onMeasure方法的参数是parent传过来的,那问题来了,如果FrameLayout是作为布局文件里的根布局,那它的parent是谁呢~他的onMeasure方法中的width/heightMeasureSpec是多少呢?谁赋予的呢?原理就不扯太远了,参考了<Android内核剖析>书中的13.8章节(从书里学到了很多,推荐大家去看看)直接定位到ViewRootImpl类中的getRootMeasureSpec方法,看注释就知道是计算根view的measureSpec值的,方法如下<span style="font-size:18px;">/** * Figures out the measure spec for the root view in a window based on it's * layout params. * * @param windowSize * The available width or height of the window * * @param rootDimension * The layout params for one dimension (width or height) of the * window. * * @return The measure spec to use to measure the root view. */ private static int getRootMeasureSpec( int windowSize , int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }</span>总结起来就是如果某个ViewGroup子类作为布局文件中的根布局时,布局参数和onMeasure中的measure值对照关系就如下
- match对应 EXACTLY+windowSize(屏幕分辨率大小)
- wrap对应 AT_MOST+windowSize
- 有size时对应 EXACTLY+size(自定义的大小)
实验下,自定义一个继承View的MyView,作为布局文件的根元素,依次将宽高改为 数值/wrap_content/match_parent运行并打印onMeasure的width/heightMeasureSpec值<span style="font-size:18px;"><?xml version="1.0" encoding= "utf-8"?> <com.boredream.view.MyView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width= "wrap_content/match_parent" android:layout_height= "200dp/400dp" /> @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); System.out.println(MeasureSpec. toString(widthMeasureSpec) + ":" + MeasureSpec.toString(heightMeasureSpec)); System.out.println("---------------------"); }</span>我是在分辨率为800 600的模拟器上跑根自定义布局参数width=wrap_content/height=200dp时打印内容为MeasureSpec: AT_MOST 800:MeasureSpec: EXACTLY 200根自定义布局参数width=match_parent/height=400dp时打印内容为MeasureSpec: EXACTLY 800:MeasureSpec: EXACTLY 400
-------------------------------------------------------------------------------------------------
好了,知道要做什么了,不光要设置自身宽高还要设置child的期望值,那如何根据parent的期望值,结合自身情况(包括child的布局参数值)综合条件来准确获取到一个合适的值呢~尤其是类型我用哪个呢~系统已经考虑到这些了,为我们提供了几个实用有效的方法,比如getChildMeasureSpec还有resolveSizeAndState等等,问题又来了,什么时候用什么方法呢?
挑常用的分析下1.MeasureSpec.makeMeasureSpec(int size, int mode)上面提过,最简单的,直接将数值size和类型mode拼装成一个所需的measureSpec值~在你明确知道自己需要的类型时,直接使用该方法生成2.getChildMeasureSpec(int spec, int padding, int childDimension)是ViewGroup类中定义的,用于处理child的MeasureSpec值的,child的处理也是measure中的难点对应代码为<span style="font-size:18px;">/** * Does the hard part of measureChildren: figuring out the MeasureSpec to * pass to a particular child. This method figures out the right MeasureSpec * for one dimension (height or width) of one child view. * * The goal is to combine information from our MeasureSpec with the * LayoutParams of the child to get the best possible results. For example, * if the this view knows its size (because its MeasureSpec has a mode of * EXACTLY), and the child has indicated in its LayoutParams that it wants * to be the same size as the parent, the parent should ask the child to * layout given an exact size. * * @param spec The requirements for this view * @param padding The padding of this view for the current dimension and * margins, if applicable * @param childDimension How big the child wants to be in the current * dimension * @return a MeasureSpec integer for the child */ public static int getChildMeasureSpec( int spec, int padding, int childDimension) { int specMode = MeasureSpec. getMode(spec); int specSize = MeasureSpec. getSize(spec); int size = Math. max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec. EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec. EXACTLY; } else if (childDimension == LayoutParams. MATCH_PARENT) { // Child wants to be our size. So be it. 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. resultSize = size; resultMode = MeasureSpec. AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec. AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec. EXACTLY; } 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. 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; // Parent asked to see how big we want to be case MeasureSpec. UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec. EXACTLY; } else if (childDimension == LayoutParams. MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec. UNSPECIFIED; } else if (childDimension == LayoutParams. WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec. UNSPECIFIED; } break; } return MeasureSpec. makeMeasureSpec(resultSize, resultMode); }</span>注释大概意思是计算child的MeasureSpec比较麻烦,这里提供了一个有效滴方法可以准确计算目标是根据MeasureSpec值和child的LayoutParams值算出一个"最可能"的结果这个可以理解为一个默认处理,没有特殊要求的话一般就直接用这个~下面贴一个该方法处理child的对应的关系图, 出自<Android内核剖析>书中的13.8章节
3.resolveSizeAndState(int size, int measureSpec, int childMeasuredState)在FrameLayout和LinearLayout中的onMeasure中都可以看到使用过此方法,一般对自己进行Measure计算时经常使用该方法~<span style="font-size:18px;">/** * Utility to reconcile a desired size and state, with constraints imposed * by a MeasureSpec. Will take the desired size, unless a different size * is imposed by the constraints. The returned value is a compound integer, * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting * size is smaller than the size the view wants to be. * * @param size How big the view wants to be * @param measureSpec Constraints imposed by the parent * @return Size information bit mask as defined by * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}. */ public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { 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: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; } return result | (childMeasuredState& MEASURED_STATE_MASK); }</span>三个参数看注释就明白意思依次为1.sizeview控件想要多大~比如LinearLayout中垂直塞了仨child,每个高度都是200,那简单来说就是view控件希望要600即size=600size值根据不同控件需要自己计算~2.measureSpecparent对你的限定,这个解释过很多遍了,view最终大小不但要考虑自己希望多大还要考虑parent的期望一般直接使用onMeasure的参数作为measureSpec的值3.childMeasuredStatechild的measure类型,该参数的生成处理比较麻烦,会在后面实例介绍中分析如何生成处理
再回头看方法代码size相当于child自己想要的大小,specSize为parent的期望大小
当期望类型是UNSPECIFIED时,即你爱咋地咋地,那就是child希望多大就多大,直接结果用child的size了
期望类型是AT_MOST时,即你最多只能specSize这么大,那child的size大于parent的specSize时,结果还是只能用specSize,因为规则是"你最多只能这么大,不能超过它",但是child没有拿到自己想要的size,就添加了一个标志位MEASURED_STATE_TOO_SMALL打个记号,原理还是利用移位或运算把状态保存至高位期望类型是EXACTLY时,代表我就想要你这么大,那child就直接取parent期望的specSize了最后把数值size和类型state用按位或运算| 合并起来总结就像2,3方法注释中说明的那样,这些都是系统提供的一些实用的方法,言外之意不用一定按照方法中的规则处理,只是提供一个辅助工具~在你不知道如何处理时就可以用2,3的方法,即给他们当做是默认处理方式通常使用场景是2是针对自己的默认处理方法3是针对child的默认处理方法1是特殊处理,按照需要直接自己拼装一个measureSpec值进行对自己或者对child的值设置
-------------------------------------------------------------------------------------------------好了,以上:算的单位(数值+期望类型的特殊合成值),算什么(对自己的measure和设置, 对child的期望值设置),用什么方法算(MeasureSpec.makeMeasureSpec,getChildMeasureSpec,resolveSizeAndState等)都依次介绍过了~然后是重中之重,具体怎么算~measure过程每个类基本都不同,没有固定套路,但可以参考系统源码找到一些常用的处理方-------------------------------------------------------------------------------------------------首先研究ViewGroup,需要同时考虑measure自己部分和measure child,虽然LinearLayout最常用,但是牵涉到weight,算法比较复杂,所以挑了一个简单的帧布局FrameLayout<span style="font-size:18px;">/** * {@inheritDoc} */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); 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 ) { 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()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width 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(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren .get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int childWidthMeasureSpec; int childHeightMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } if (lp.height == LayoutParams.MATCH_PARENT) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } } </span>首先回忆下FrameLayout的特性,帧布局所有放在布局里的控件,都按照层次堆叠在屏幕的左上角,后加进来的控件覆盖前面的控件
第一次for循环mMeasureAllChildren标记值无视~ 只计算非gone的child首先先用measureChildWithMargins计算一下child,点开这个方法内部发现是调用的getChildMeasureSpec方法即用上面表格的那个默认处理方法对所有的child都measure一遍~对child measure以后就可以获取到child的measuredWidth/Height/State了然后根据获取到的宽高参数计算maxWidth/Height和childState
其中maxWidth/Height计算很简单,直接用Math.max所有的child,最终就获取到child里最大的宽高了childState的计算是利用combineMeasureStates方法,将child的宽高的期望类型也用移位或的方法合并,即childState是由width和heigh的期望类型共同组成的
每次循环最后,判断如果onMeasure的宽高模式其中有一个不是EXCTLY的,并且child的宽高参数类型都是MATCH_PARENT,就将这个child保存至集合measureMatchParentChildren中,用作后面child的特殊measureSpec设置
第一轮循环结束开始对自己进行measureSpec设置,直接用setMeasuredDimension方法,而宽高的measureSpec计算需要利用resolveSizeAndState方法~在上一章中介绍过这个方法,作用就是将parent的期望类型和期望值width/heightMeasureSpec和自己算出来的大小maxWidth/Height结合起来考虑,决定最终child的大小和类型
再下面是特殊child的measure处理了~
这里可能会有疑惑,上面所有的child其实都默认measureChildWithMargins处理过一遍了,这里为什么又要measure一次呢?想象下特殊情况,如果FrameLayout宽或高是WRAP_CONTENT的,即这个宽或高将由child中最大的值决定,而其中有一个child是MATCH_PARENT的,意思是大小和FrameLayout一样~FrameLayout的大小只能在for循环所有child的以后才能确定maxWidth/Height,即无法在第一轮for循环进行时就获取到parent的最终大小,自然没办法决定MATCH_PARENT的child大小了
所以在第一轮循环时先将特殊的MATCH_PARENT属性的child保存到一个集合里,在第一轮循环结束后算出parent宽高以后,再开始对特殊的child进行二次循环处理,对child的特殊处理为:如果child是MATCH_PARENT,则利用FrameLayout自己的宽高去除padding和margin后生成一个EXACTLY的measureSpec,还要加个else判断不是MATCH_PARENT的情况,因为上面是宽高其中有一个属性满足要求,就添加到集合里需要特殊处理,另一个参数可能是正常的所以宽高都在else中添加上默认处理getChildMeasureSpec
最终用生成的measureSpec值再child.measure设置一次,搞定~
------------------------------------------------------------------------------------------------------------
下面研究View的子类,举个最基本的例子TextView由于是非ViewGroup,所以只有对自己身measure部分
代码为了理解方便,会减去child自己想要宽高的计算过程~即根据textSize,lines,drawableTop等综合算出大小,这个算法比较复杂,我们也暂时没有研究必要宽高同理,所以只把宽度计算部分抽取出来,简化后代码如下
<span style="font-size:18px;">@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec. getMode(widthMeasureSpec); int widthSize = MeasureSpec. getSize(widthMeasureSpec); int width; if (widthMode == MeasureSpec. EXACTLY) { // Parent has told us how big to be. So be it. width = widthSize; } else { width = .......... // 根据TextView一些特性如hint,ems,drawable等计算width值 // Check against our minimum width width = Math. max(width, getSuggestedMinimumWidth()); if (widthMode == MeasureSpec. AT_MOST) { width = Math. min(widthSize, width); } } setMeasuredDimension(width, height); }</span>同样是按照套路来的如果是EXACTLY模式,那告诉我多大我就多大widthSize如果是AT_MOST模式,那取计算出来的宽度(根据字体大小和字数多少)和parent告诉我的宽度中小的那个即取一个不超过AT_MOST模式parent的期望数值如果是UNSPECIFIED模式,则无视parent直接用child自己算出来的大小width
这一段if else的处理其实就等同于FrameLayout中的resolvSizeAndState方法处理,但由于是View只能作为child,所以setMeasuredDimension就不需要考虑类型问题了------------------------------------------------------------------------------------------------------------以上最基本的ViewGroup和View子类都介绍完了作为android sdk中自带的类,肯定都是很靠谱按套路来的,即遵从规则的孩纸,相当于几个具有不同特性的标杆如果再有特殊需要,可以考虑继承这几个实现子类,而不用直接继承View/ViewGroup,这样就更加简单了,不用面面俱到,只用考虑自己所需的特殊实现,下面举几个间接继承的例子ScrollView,可以滚动的,child只有一个且高度可以大于自己(此时可以上下滚动)从标杆中找~ 果断用FrameLayout,这个类申明中有说明一般来说只持有一个child(当然可以add多个view,但这个是推荐的最常见应用场景)
特性呢? child的height我们希望不做限定,即child想多高就多高,如果上面已经理解了,这里肯定能直接想到用UNSPECIFIED模式回忆下上面的FrameLayout,第一次for循环的时候用measureChildWithMargins计算了所有的child,而measureChildWithMargins中用的是默认处理方式getChildMeasureSpec方法,即上一章中的那个表格明显不是我们要的,那复写measureChildWithMargins之~(这里如果直接复写onMeasure方法也是一样道理,最终目的都是为了让child的height类型设为UNSPECIFIED)再查看ScrollView源码的measureChildWithMargins可以看到系统的具体处理<span style="font-size:18px;">@Override protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, intwidthUsed, 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 = MeasureSpec.makeMeasureSpec( lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }</span>宽度不变还是用默认方法getChildMeasureSpec高度部分则用宽度则直接用MeasureSpec.makeMeasureSpec方法生成了一个UNSPECIFIED的宽度measure值注:UNSPECIFIED的意思是child你想多大就多大,不用考虑我~所以如果用这个模式的话,数值部分就没用了,一般直接makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);即可,看默认处理方法getChildMeasureSpec也可以发现,child期望类型定为UNSPECIFIED时,size就直接设为0了
但是要再次强调,模式只是parent对child的一个期望,虽然UNSPECIFIED的意思是不限定按照child的自己来,但万一child非要用到margin值呢,那就传吧~------------------------------------------------------------------------------------------------------------总结
研究了很长时间,网上详细分析的资料也很少,文章来来回回修改了N次
每次觉得已经看懂了过两天又觉得又问题,最后终于勉强懂了大概
现在从头到尾梳理了一遍发出来,可能还是存在很多问题希望大家共同讨论,可能有不太准确的地方,
发表以后可能还会补充说明修改~android的view显示是最核心的内容,主要分measure layout draw三个部分,而measure又是最复杂最难理解的文章看懂以后不求能完全写出来一个LinearLayout这样sdk级别的控件,能在写自定义控件知道onMeasure如何处理就可以了~不会疑惑"诶我操!我的控件为什么不显示"就行了更主要的是能理解android中view的measure原理,知道大概流程是什么~
Android View的绘制过程的更多相关文章
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
Android中View的绘制过程 onMeasure方法简述 附有自定义View例子 Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android fr ...
- 【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布 ...
- android 中view的绘制过程
view的绘制过程中分别会执行:onMeasure(会多次)计算view的大小,OnLayout(),确定控件的大小和位置 onDraw()绘制view 当Activity获得焦点时,它将被要求绘制自 ...
- Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)
View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇 ...
- Android - View的绘制流程一(measure)
该博文所用的demo结构图: 相应的代码: MainActivity.java: [java] view plain copy <span style="font-family:Mic ...
- Android View的绘制机制前世今生---前世
就像上个文章说的,触摸事件的传递机制是从外层到内层的过程. 我们想来看看这个页面里面的层级关系: 以下我们就用what-how-why三部曲的方式来分析View的绘制过程. 由于篇幅很大,所以分几篇来 ...
- Android View 如何绘制
上文说道了Android如何测量,但是一个漂亮的控件我只知道您长到哪儿,这当然不行.只需要简单重写OnDraw方法,并在Canvas(画布)对象上调用那根五颜六色的画笔就能够画出这控件"性感 ...
- Android View的绘制机制流程深入详解(四)
本系列文章主要着重深入介绍Android View的绘制机制及流程,第四篇主要介绍Android自定义View及ViewGroup的实现方法和流程. 主要介绍了自绘控件.自定义组合控件.自定义继承控件 ...
- Android View的绘制机制流程深入详解(三)
本系列文章主要着重深入介绍Android View的绘制机制及流程,第三篇主要介绍并分析视图状态以及重绘流程,首先剖析了 视图的几种状态,然后在深入分析视图的重绘机制流程. 真题园网:http://w ...
随机推荐
- [SDOI2010]古代猪文
题目背景 “在那山的那边海的那边有一群小肥猪.他们活泼又聪明,他们调皮又灵敏.他们自由自在生活在那绿色的大草坪,他们善良勇敢相互都关心……” ——选自猪王国民歌 很久很久以前,在山的那边海的那边的某片 ...
- codeforces 651C Watchmen
Watchmen are in a danger and Doctor Manhattan together with his friend Daniel Dreiberg should warn t ...
- C++traits——STL源码剖析
有时候我们希望知道迭代器所指的元素类型. 以迭代器所指声明对象: template<typename Iterator, typename T> void func_impl(Iterat ...
- [Apio2012]dispatching 左偏树
题目描述 在一个忍者的帮派里,一些忍者们被选中派遣给顾客,然后依据自己的工作获取报偿.在这个帮派里,有一名忍者被称之为 Master.除了 Master以外,每名忍者都有且仅有一个上级.为保密,同时增 ...
- hdu 4031 attack 线段树区间更新
Attack Time Limit: 5000/3000 MS (Java/Others) Memory Limit: 65768/65768 K (Java/Others)Total Subm ...
- Python【第一课】 Python简介和基础
本节内容 Python安装(windows) 第一个程序(windows中的python) 变量 字符编码 注释 用户输入 模块初步认识 数据类型 数据运算 表达式if...else 表达式for l ...
- candy(动态规划)
题目描述 There are N children standing in a line. Each child is assigned a rating value. You are giving ...
- 计科1702冯亚杰C语言程序设计预备作业
阅读邹欣老师的博客--师生关系,针对文中的几种师生关系谈谈你的看法,你期望的师生关系是什么样的? 答:首先老师和学生之间要互相尊重,我认为这是必要的.在第一点的基础上师生要互相帮助,互相配合,共同进步 ...
- 利用Express模拟web安全之---xss的攻与防
一.什么是XSS? 跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS.恶意 ...
- javascript templating
JavaScript Micro-Templating I’ve had a little utility that I’ve been kicking around for some time no ...