欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android UI绘制流程及原理详解

程序员文章站 2022-07-06 14:34:27
一、绘制流程源码路径 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方法,总结如下:

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

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

感谢大家的阅读和对的支持。