View的绘制机制

  • Android 视图的构成
  • ViewRoot
  • View树的绘制流程
  • Measure
  • Layout
  • Draw


Android 视图的构成

Android 遍历系统权限 安卓遍历view_Layout

ViewRoot

View树的绘制流程

View树的绘制流程,其实就是一个递归的过程
过程: measure->layout->draw
①对所有子元素进行测量, 测量过程从父ViewGroup传到View中,测量好了所有的子元素之后,进行递归,反复之后,就完成了父元素ViewGroup的测量。
②layout相类似

Measure

Android 遍历系统权限 安卓遍历view_子视图_02


遍历过程

根据父容器对子容器的一些测量参数获取到子容器的长宽高,把子容器测量的长宽高返给父容器进行统一的测量。树的遍历过程,从上到下进行遍历。

重要参数:

1.ViewGroup.LayoutParams:指定视图的宽高.

三种值:

①具体值

②match-parent:不包括padding值,子视图大小和父控件大小一样大。

③wrap-content:包括子控件大小即可。能包含子控件即可。

2.MeasureSpec:测量规格(32位int值)

高两位是模式占位符,表示的是测量模式。

三种模式:

①不确定的(不用的)

②EXACTLY:父容器为子视图确定一个大小,无论子视图需要多大,都必须要在这个范围内

③AT_MOST:父容器为子视图规定的最大的尺寸,必须在这个范围内,父控件没有办法获取子控件的尺寸,只能是子控件根据自己的需求自己去测量自己的尺寸。

容器在布局时调用子view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。而当设置为wrap_content时,容器传进去的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY, 而MeasureSpec的UNSPECIFIED模式目前还没有发现在什么情况下使用。

后30位表示在这种测量模式下视图尺寸的大小。

在Measure过程中,系统会将这个View的LayoutParams结合父容器生成一个MeasureSpec,MeasureSpec测量规格规定好怎样测量空间的大小,所有大小都会被包装成MeasureSpec测量规格,返还给父容器,告诉如何测量控件的大小。

重要方法

1.measure

final方法,通过MeasureSpec获得宽高的测量规格,最终调用onMeasure方法进行测量,自定义的时候只需要复写onMeasure方法即可。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ....
  }

2.onMeasure:自定义视图当中,实现测量逻辑的方法。
setMeasuredDimension 测量阶段的终极也就是实现他的方法,测量阶段结束的方法,必须被调用,否则会报异常。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  }

总结: measure方法调用onMeasure方法,将所有的长宽高传给setMeasuredDimension方法,最后完成测量。通过MeasureSpec约束子视图的宽高,保证所有父视图能够接受子视图传递的测量,如果父视图觉得子视图传递的长高不对,会再次请求子视图再次进行测量,进行第二次Measure,如果最终的子视图还是超出了约束,或者是过于小了,那么父视图就会给定一个固定的大小,将其设置为Exactly或者AT_MOST再次对子视图进行测量。

Layout

是否需要重新安置试图位置
自上而下的遍历过程,Layout根据测量所得到的尺寸摆放子视图的位置(子视图的具体位置都是相互对于父视图而言的)。
View的onLayout()是一个空实现,如果要自定义View的话,继承ViewGroup时,必须要重写onLayout方法,重新摆放自己自定义View的位置

public void layout(int l, int t, int r, int b) {
    onLayout(changed, l, t, r, b);
  }

  // 空方法,子类如果是 ViewGroup 类型,则重写这个方法,实现 ViewGroup 中所有 View 控件布局
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  }

总结: Layout也是一个树形的结构,当他需要进行数据摆放的时候,依次从Group调用View,依次进行摆放。

Draw

是否需要重绘
两个容易混淆的方法:
1.invalidate():请求安卓系统,视图大小没有发生变化,不会调用Layout放置过程。
2.requestLayout():当布局发生变化的时候,自定义视图的时候经常调用(需要重新测量视图尺寸的大小),调用之后它就会触发Measure和Layout方法,不会调用Draw方法。