和Activity一样,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的默认属性。该方法表示当前View已经与对应的Window关联,我们可以把该回调当作View生命周期的开始,在这里面写一些业务逻辑,比如说启动一些任务,初始化一些资源等等,有以下几个时机会回调onAttachedToWindow
方法。
dispatchAttachedToWindow
方法。addView
方法中回调dispatchAttachedToWindow
方法。dispatchAttachedToWindow
方法会回调onAttachedToWindow
方法,如果是ViewGroup,还会继续遍历所有的子View,调用子View的dispatchAttachedToWindow
方法。
该方法表示当前View已经与对应的Window取消关联,对应的我们可以把该回调当作View生命周期的结束,在这里可以停止某些任务和释放资源等等,有以下几个时机会回调onDetachedFromWindow
方法。
dispatchDetachedFromWindow
方法。dispatchDetachedFromWindow
方法。dispatchDetachedFromWindow
方法会回调onDetachedFromWindow
方法,如果是ViewGroup,还会继续遍历所有的子View,调用子View的dispatchDetachedFromWindow
方法。
这一阶段主要用于测量当前的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,实现自己的测量逻辑。
这一阶段主要用于确定当前的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方法会依次绘制
父View通过dispatchDraw绘制子View时,会将自己使用过的canvas传递给子View,但是我们在onDraw(Canvas)
方法中使用canvas进行绘制时,却是以当前View左上角的坐标为原点,这是因为在dispatchDraw会调用子View的draw(Canvas, ViewGroup, long)
方法,这个方法中会在当前View执行绘制之前,通过调用canvas的translate(float dx, float dy)
方法,改变canvas的原点坐标,并且在执行绘制之前将canvas的当前状态保存,绘制完成之后再恢复原来的状态。
用于请求重新绘制View,会触发onDraw,如果调用的的View,则会重新绘制当前View,如果是ViewGroup,则会重新绘制当前View以及其所有的子View。
用于重新对View进行排版,会触发onMeasure和onLayout,当执行layout时发现View的宽高发生变化,也会触发onDraw,该方法会向上调用其父View的requestLayout方法直到ViewRootImpl。
在执行layout方法时,会调用setFrame方法,该方法中会判断顶点坐标是否发生变化,如果变化了,则会调用invalidate从而触发onDraw。
注释