Android UI绘制流程及原理详解
一、绘制流程源码路径
1、activity加载viewrootimpl
activitythread.handleresumeactivity() --> windowmanagerimpl.addview(decorview, layoutparams) --> windowmanagerglobal.addview()
2、viewrootimpl启动view树的遍历
viewrootimpl.setview(decorview, layoutparams, parentview) -->viewrootimpl.requestlayout() -->scheduletraversals() -->traversalrunnable.run() -->dotraversal() -->performtraversals()(performmeasure、performlayout、performdraw)
二、view绘制流程
1、measure
(1)measurespec是什么?
重写过onmeasure()方法都知道,测量需要用到measurespec类获取view的测量模式和大小,那么这个类是怎样存储这两个信息呢?
留心观察的话会发现,onmeasure方法的两个参数实际是32位int类型数据,即:
00 000000 00000000 00000000 00000000
而其结构为 mode + size ,前2位为mode,而后30位为size。
==> getmode()方法(measurespec --> mode):
private static final int mode_shift = 30; // 0x3转换为二进制即为:11 // 左移30位后:11000000 00000000 00000000 00000000 private static final int mode_mask = 0x3 << mode_shift; public static int getmode(int measurespec) { // 与mode_mask按位与运算后,即将低30位清零,结果为mode左移30位后的值 return (measurespec & mode_mask); }
getsize()方法同理。
==> makemeasurespec()方法(mode + size --> measurespec):
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); } }
这里解释一下,按位或左侧为size的高2位清零后的结果,右侧为mode的低30位清零后的结果,两者按位或运算的结果正好为高2位mode、低30位size,例:
01000000 00000000 00000000 00000000 | 00001000 00001011 11110101 10101101 = 01001000 00001011 11110101 10101101
二进制计算规则可参考:
==> 测量模式:
public static final int unspecified = 0 << mode_shift; public static final int exactly = 1 << mode_shift; public static final int at_most = 2 << mode_shift;
unspecified:父容器不对view作任何限制,系统内部使用。
exactly:精确模式,父容器检测出view大小,即为specsize;对应layoutparams中的match_parent和指定大小的情况。
at_most:最大模式,父容器指定可用大小,view的大小不能超出这个值;对应wrap_content。
(2)viewgroup的测量流程
回到viewrootimpl的performmeasure方法,这里传入的参数为顶层decorview的测量规格,其测量方式为:
private static int getrootmeasurespec(int windowsize, int rootdimension) { int measurespec; switch (rootdimension) { case viewgroup.layoutparams.match_parent: measurespec = measurespec.makemeasurespec(windowsize, measurespec.exactly); break; case viewgroup.layoutparams.wrap_content: measurespec = measurespec.makemeasurespec(windowsize, measurespec.at_most); break; default: measurespec = measurespec.makemeasurespec(rootdimension, measurespec.exactly); break; } return measurespec; }
match_parent和具体数值大小为exactly模式,wrap_content则为at_most模式。
往下走,performmeasure方法中调用了decorview的onmeasure方法,而decorview继承自framelayout,可以看到fl的onmeasure方法中调用了measurechildwithmargins方法,并传入自身的测量规格:
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); }
即测量子控件的大小,测量规则详情可看getchildmeasurespec方法,总结如下:
childlayoutparams\parentspecmode | exactly | at_most | unspecified |
---|---|---|---|
dp | exactly/childsize | exactly/childsize | excatly/childsize |
match_parent | exactly/parentsize | at_most/parentsize | unspecified/0 |
wrap_content | at_most/parentsize | at_most/parentsize | unspecified/0 |
回到onmeasure方法,测完子控件之后,viewgroup会经过一些计算,得出自身大小:
// 加上padding maxwidth += getpaddingleftwithforeground() + getpaddingrightwithforeground(); maxheight += getpaddingtopwithforeground() + getpaddingbottomwithforeground(); // 检查是否小于最小宽度、最小高度 maxheight = math.max(maxheight, getsuggestedminimumheight()); maxwidth = math.max(maxwidth, getsuggestedminimumwidth()); // 检查drawable的最小高度和宽度 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));
综上,viewgroup的测量需要先测量子view的大小,而后结合padding等属性计算得出自身大小。
(3)view的测量流程
view.performmeasure() -->onmeasure(int widthmeasurespec, int heightmeasurespec) -->setmeasureddimension(int measuredwidth, int measuredheight) -->setmeasureddimensionraw(int measuredwidth, int measuredheight)
可以看到setmeasureddimensionraw()方法:
private void setmeasureddimensionraw(int measuredwidth, int measuredheight) { // 存储测量结果 mmeasuredwidth = measuredwidth; mmeasuredheight = measuredheight; // 设置测量完成的标志位 mprivateflags |= pflag_measured_dimension_set; }
view不需要考虑子view的大小,根据内容测量得出自身大小即可。
另外,view中的onmeasure方法中调用到getdefaultsize方法:
protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { setmeasureddimension(getdefaultsize(getsuggestedminimumwidth(), widthmeasurespec), getdefaultsize(getsuggestedminimumheight(), heightmeasurespec)); } 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; }
这里看到精确模式和最大模式,最终测量的结果都是父容器的大小,即布局中的wrap_content、match_parent以及数值大小效果都一样,这也就是自定义view一定要重写onmeasure方法的原因。
2、layout
布局相对测量而言要简单许多,从viewrootimpl的performlayout方法出发,可以看到其中调用了decorview的layout方法:
// 实则为decorview的left, top, right, bottom四个信息 host.layout(0, 0, host.getmeasuredwidth(), host.getmeasuredheight());
进入layout方法,发现l、t、r、b被传递到了setframe方法中,并设置给了成员变量:
mleft = left; mtop = top; mright = right; mbottom = bottom;
所以,布局实际为调用view的layout方法,设置自身的l、t、r、b值。另外,layout方法中往下走,可以看到调用了onlayout方法,进入后发现为空方法。因而查看framelayout的onlayout方法:
@override protected void onlayout(boolean changed, int left, int top, int right, int bottom) { layoutchildren(left, top, right, bottom, false /* no force left gravity */); } 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(); // 省略 child.layout(childleft, childtop, childleft + width, childtop + height); } } }
可以看到,进行一系列计算后,调用了child的layout方法,对子控件进行布局,同时子控件又会继续往下对自己的子控件布局,从而实现遍历。
综上,布局实际为调用layout方法设置view位置,viewgroup则需要另外实现onlayout方法摆放子控件。
3、draw
(1)绘制过程入口
viewrootimpl.performdraw() -->viewrootimpl.draw() -->viewrootimpl.drawsoftware() -->view.draw()
(2)绘制步骤
进入到view的draw方法中,可以看到以下一段注释:
/* * 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) */
结合draw方法的源码,绘制过程的关键步骤如下:
- ==> 绘制背景:drawbackground(canvas)
- ==> 绘制自己:ondraw(canvas)
- ==> 绘制子view:dispatchdraw(canvas)
- ==> 绘制滚动条、前景等装饰:ondrawforeground(canvas)
感谢大家的阅读和对的支持。
推荐阅读