View的生命周期

Android面试技术要点汇总
2021-05-17 14:29 · 阅读时长7分钟
小课

和Activity一样,View也有自己的生命周期,在其渲染到屏幕上之前会经过一系列生命周期函数,每个生命周期函数都有不同的使命,下面就看看View的生命周期是如何调用以及每个生命周期函数都做了些什么。

View的生命周期
View的构造方法

首先是创建View,会调用View的构造方法,在这里可以设置View的一些属性、样式等,View有以下几个不同的构造方法。

  • View(Context),使用代码创建View时会用这个构造方法,这种方式创建View,需要手动调用方法设置属性,比较麻烦,一般很少用这种方法创建View。
  • View(Context, AttributeSet),当我们在XML中使用View时,会使用该构造方法来创建View,在第二个参数AttributeSet中保存着XML中为该View设置的属性,一般会在构造方法中取出这些属性赋值给当前View的属性变量。
  • View(Context, AttributeSet, int),这个构造方法中的第三个参数,主要用于定义View的默认属性,通过定义attr,然后再主题theme中为attr赋值。比如说Button这个组件,首先它在attrs.xml中定义了<attr name="buttonStyle" format="reference" />,然后在themes.xml中为buttonStyle赋值<item name="buttonStyle">@style/Widget.Button</item>,这样如果我们使用Button时未设置某些属性,它就会使用@style/Widget.Button中定义的值。
  • View(Context, AttributeSet, int, int),这个构造方法中的第四个参数,直接指向某个style,也是用于定义View的默认属性。
onAttachedToWindow

该方法表示当前View已经与对应的Window关联,我们可以把该回调当作View生命周期的开始,在这里面写一些业务逻辑,比如说启动一些任务,初始化一些资源等等,有以下几个时机会回调onAttachedToWindow方法。

  • 当Activity在第一次执行onResume后,会将Activity的decorView与Window关联起来,此时会回调decorView的dispatchAttachedToWindow方法。
  • 另外如果在Activity已经启动之后动态添加View,也会在ViewGroup的addView方法中回调dispatchAttachedToWindow方法。

dispatchAttachedToWindow方法会回调onAttachedToWindow方法,如果是ViewGroup,还会继续遍历所有的子View,调用子View的dispatchAttachedToWindow方法。

onDetachedFromWindow

该方法表示当前View已经与对应的Window取消关联,对应的我们可以把该回调当作View生命周期的结束,在这里可以停止某些任务和释放资源等等,有以下几个时机会回调onDetachedFromWindow方法。

  • 当Activity执行onDestroy时,会将Activity对应的decorView从window中移除,此时会回调decorView的dispatchDetachedFromWindow方法。
  • 另外当调用removeView将View从其父View中移除时,也会回调dispatchDetachedFromWindow方法。

dispatchDetachedFromWindow方法会回调onDetachedFromWindow方法,如果是ViewGroup,还会继续遍历所有的子View,调用子View的dispatchDetachedFromWindow方法。

measure

这一阶段主要用于测量当前的View需要占用多大的空间,如果是ViewGroup的话,还会遍历子View,调用子View的measure方法,并且根据子View需要占用的空间来调整自身需要的空间。

在measure方法中,会回调onMeasure(int, int)方法,这两个参数分别包含了宽和高的测量模式及大小,这里的测量模式和大小都是父View根据自身的测量模式以及子View设置的宽高参数来决定的,在onMeasure里面会真正的设置测量大小。

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

如果是自定义View的话,我们可以重写onMeasure,实现自己的测量逻辑。

layout

这一阶段主要用于确定当前的View显示的位置,layout(int, int, int, int)中包含四个参数,分别代表View的左,上,右,下四个顶点相对于父View的坐标。layout方法会回调onLayout(boolean, int, int, int, int)方法并且把坐标参数传过来,该方法在View中默认实现是空的,如果是ViewGroup则必须重写实现该方法。

通过坐标参数,就能得到自身的宽和高,然后再通过子View的测量得到的宽和高以及布局策略计算出子View在父View中的坐标,再调用子View的layout(int, int, int, int)方法。

draw

这一阶段开始绘制,draw方法会依次绘制

  1. View的背景background,绘制方法drawBackground。
  2. View的内容,通过onDraw进行绘制。
  3. 如果是ViewGroup的话,通过dispatchDraw绘制子View。
  4. 如果有的话,绘制fading效果。
  5. 如果有的话,绘制decorations,比如foreground、scrollbars等。
  6. 如果有的话,绘制聚焦高亮效果。

父View通过dispatchDraw绘制子View时,会将自己使用过的canvas传递给子View,但是我们在onDraw(Canvas)方法中使用canvas进行绘制时,却是以当前View左上角的坐标为原点,这是因为在dispatchDraw会调用子View的draw(Canvas, ViewGroup, long)方法,这个方法中会在当前View执行绘制之前,通过调用canvas的translate(float dx, float dy)方法,改变canvas的原点坐标,并且在执行绘制之前将canvas的当前状态保存,绘制完成之后再恢复原来的状态。

invalidate

用于请求重新绘制View,会触发onDraw,如果调用的的View,则会重新绘制当前View,如果是ViewGroup,则会重新绘制当前View以及其所有的子View。

requestLayout

用于重新对View进行排版,会触发onMeasure和onLayout,当执行layout时发现View的宽高发生变化,也会触发onDraw,该方法会向上调用其父View的requestLayout方法直到ViewRootImpl。

在执行layout方法时,会调用setFrame方法,该方法中会判断顶点坐标是否发生变化,如果变化了,则会调用invalidate从而触发onDraw。

注释
viewlifecycleonMeasureonLayoutonDraw