android开发分享Android UI绘制流程及原理详解

一、绘制流程源码路径 1、activity加载viewrootimpl activitythread.handleresumeactivity() —

一、绘制流程源码路径

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方法,总结如下:

childlayoutparamsparentspecmode 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方法的源码,绘制过程的关键步骤如下:

  1. ==> 绘制背景:drawbackground(canvas)
  2. ==> 绘制自己:ondraw(canvas)
  3. ==> 绘制子view:dispatchdraw(canvas)
  4. ==> 绘制滚动条、前景等装饰:ondrawforeground(canvas)

感谢大家的阅读和对<计算机技术网(www.ctvol.com)!!>的支持。

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/addevelopment/900840.html

(0)
上一篇 2021年10月24日
下一篇 2021年10月24日

精彩推荐