Android视图的绘制流程(上) View的测量
综述
view的绘制流程可以分为三大步,它们分别是measure,layout和draw过程。measure表示view的测量过程,用于测量view的宽度和高度;layout用于确定view在父容器的位置;draw则是负责将view绘制到屏幕中。下面主要来看一下view的measure过程。
测量过程
view的绘制流程是从viewroot的performtraversals方法开始的,viewroot对应viewrootimpl类。viewroot在performtraversals中会调用performmeasure方法来进行对根view的测量过程。而在performmeasure方法中又会调用view的measure方法。对于view的measure方法它是一个final类型,也就是说这个measure方法不能被子类重写。但是在measure方法中调用了onmeasure方法。所以view的子类可以重写onmeasure方法来实现各自的measure过程。在这里也就是主要对onmeasure方法进行分析。
measurespec
measurespec是view类中的一个静态内部类。一个measurespec封装了父布局传递给子布局的布局要求。每个measurespec都代表着一个高度或宽度的要求。每个mesurespec都是由specsize和specmode组成,它代表着一个32位的int值,其中高2位代表specsize,低30位代表specmode。
measurespec的测量模式有三种,下面介绍一下这三种测量模式:
unspecified
父容器对子view没有任何的限制,子view可以是任何的大小。
exactly
父容器为子view大小指定一个具体值,view的最终大小就是specsize。对应view属性match_parent和具体值。
at_most
子view的大小最大只能是specsize,也就是所子view的大小不能超过specsize。对应view属性的wrap_content.
在measurespec中可以通过specsize和specmode并使用makemeasurespec方法来创建一个measurespec,还可以通过getmode和getsize来获取measurespec的specmode和specsize。
view的测量过程
在上面已经说到,view的measure过程是由measure方法来完成的,而measure方法通过调用onmeasure方法来完成view的measure过程。那么就来看一下onmeasure方法。
protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { setmeasureddimension(getdefaultsize(getsuggestedminimumwidth(), widthmeasurespec), getdefaultsize(getsuggestedminimumheight(), heightmeasurespec)); }
在view的onmeasure方法中只是调用了setmeasureddimension方法,setmeasureddimension方法的作用就是设置view的高和宽的测量值。对于view测量后宽和高的值是通过getdefaultsize方法来获取的。下面就来一下这个getdefaultsize方法。
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; }
对于measurespec的at_most和exactly模式下,直接返回的就是measurespec的specsize,也就是说这个specsize就是view测量后的大小。而对于在unspecified模式下,view的测量值则为getdefaultsize方法中的第一个参数size。这个size所对应的宽和高是通过getsuggestedminimumwidth和getsuggestedminimumheight两个方法获取的。下面就来看一下这两个方法。
protected int getsuggestedminimumheight() { return (mbackground == null) ? mminheight : max(mminheight, mbackground.getminimumheight()); } protected int getsuggestedminimumwidth() { return (mbackground == null) ? mminwidth : max(mminwidth, mbackground.getminimumwidth()); }
在这里可以看到对于view宽和高的取值是根据view是否存在背景进行设置的。在这里以view的宽度来进行说明。若是view没有背景则是view的宽度mminwidth。对于mminwidth值得设置可以在xml布局文件中设置minwidth属性,它的默认值为0。也可以通过调用view的setminimumwidth()方法其赋值。若是view存在背景的话,则取view本身最小宽度mminwidth和view背景的最小宽度它们中的最大值。
viewgroup的测量过程
对于viewgroup的measure过程,viewgroup处理measure自己本身的大小,还需要遍历子view,并调用它们的measure方法,然后各个子元素再去递归执行measure过程。在viewgroup中并没有重写onmeasure方法,因为viewgroup它是一个抽象类,对于不同的具体viewgroup它的onmeasure方法中所实现的过程不一样。但是在viewgroup中提供了一个measurechildren方法,对子view进行测量。下面就来看一下这个measurechildren方法。
protected void measurechildren(int widthmeasurespec, int heightmeasurespec) { final int size = mchildrencount; final view[] children = mchildren; for (int i = 0; i < size; ++i) { final view child = children[i]; if ((child.mviewflags & visibility_mask) != gone) { measurechild(child, widthmeasurespec, heightmeasurespec); } } }
在这里获取viewgroup中所有的子view。然后遍历viewgroup中子view并调用measurechild方法来完成对子view的测量。下面看一下measurechild方法。
protected void measurechild(view child, int parentwidthmeasurespec, int parentheightmeasurespec) { final layoutparams lp = child.getlayoutparams(); final int childwidthmeasurespec = getchildmeasurespec(parentwidthmeasurespec, mpaddingleft + mpaddingright, lp.width); final int childheightmeasurespec = getchildmeasurespec(parentheightmeasurespec, mpaddingtop + mpaddingbottom, lp.height); child.measure(childwidthmeasurespec, childheightmeasurespec); }
在这段代码中通过getchildmeasurespec方法获取子view宽和高的measurespec。然后调用子view的measure方法开始对view进行测量。下面就来看一下是如何通过getchildmeasurespec方法来获取view的measurespec的。
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 = view.susezerounspecifiedmeasurespec ? 0 : size; resultmode = measurespec.unspecified; } else if (childdimension == layoutparams.wrap_content) { // child wants to determine its own size.... find out how // big it should be resultsize = view.susezerounspecifiedmeasurespec ? 0 : size; resultmode = measurespec.unspecified; } break; } return measurespec.makemeasurespec(resultsize, resultmode); }
在这段代码对于measurespec的获取主要是根据父容器的measurespec和view本身的layoutparams。下面通过一张表格来看一下它们之间的对应关系。
到这里通过getchildmeasurespec方法获取到子view的measurespec以后,便调用view的measure方法,开始对view进行测量。
正如刚才说的那样对于viewgroup它是一个抽象类,并没有重写view的onmeasure方法。但是到具体的viewgroup时,例如framelayout,linearlayout,relativelayout等,它们通过重写onmeasure方法来来完成自身以及子view的measure过程。下面以framelayout为例,看一下的measure过程。在framelayout中,它的measure过程也算是比较简单,下面就来看一下framelayout中的onmeasure方法。
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(); 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完成自身的测量过程,然后在遍历子view,执行view的measure方法,完成view的measure过程。在这里代码比较简单就不在进行详细描述。
总结
最后对view和viewgroup的measure过程做一下总结。对于view,它的measure很简单,在获取到view的高和宽的测量值之后,便为其设置高和宽。而对于viewgroup来说,除了完成自身的measure过程以外,还需要遍历子view,完成子view的测量过程。
上一篇: 微信小程序 跳转页面的两种方法详解