生命周期在 Android 开发中是非常重要的内容,在学习自定义 ViewGroup 时,生命周期也必不可少。本文就从执行顺序角度,阐述一下自己的观点。
ViewGroup 常用的生命周期回调:构造方法、onFinishInflate、onMeasure、onSizeChanged、onLayout、onDraw、dispatchDraw。后两个是为 ViewGroup 绘制背景以及绘制子 View 的方法,这里不做过多讨论。
首先,自定义类 public ViewTestLayout extends ViewGroup
,实现前五个方法,并打印日志。如下:
public class ViewTestLayout extends FrameLayout{
// 索引标识
int index = 0;
public ViewTestLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView("context attrs defStyle");
}
public ViewTestLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initView("context attrs");
}
public ViewTestLayout(Context context) {
super(context);
initView("context");
}
// 当构造方法被执行调用
private void initView(String from){
System.out.println("我来自:" + from + " = " + (++index) + "==" +getMeasuredWidth() + "==" + getWidth());
}
// 当布局加载完成调用
@Override
protected void onFinishInflate() {
super.onFinishInflate();
System.out.println("onFinishInflate"+ " = " + (++index)+ "==" +getMeasuredWidth()+ "==" + getWidth());
}
// 当尺寸改变调用
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
System.out.println("onSizeChanged"+ " = " + (++index)+ "==" +getMeasuredWidth()+ "==" + getWidth());
}
// 当测量时调用
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
System.out.println("onMeasure"+ " = " + (++index)+ "==" +getMeasuredWidth()+ "==" + getWidth());
}
// 当布局时调用
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
System.out.println("onLayout"+ " = " + (++index)+ "==" +getMeasuredWidth()+ "==" + getWidth());
}
}
分别在这三种情况下输出日志:
<com.example.customeview.ViewTestLayout android:id="@+id/vtl" android:layout_width="100dip" android:layout_height="100dip" android:layout_centerInParent="true" android:background="#6600ff00" >
</com.example.customeview.ViewTestLayout>
setContentView( new ViewTestLayout(this) )
new ViewTestLayout(this)
并不直接添加到任何父布局可以得出的结论是
这两个都是获得自身的宽度和测量后的宽度,那么我们在自定义 ViewGroup 时,应该在什么时机使用,才能保证取到值。
将上面的日志改为:System.out.println("onFinishInflate"+ " = " + (++index)+ "==" +getMeasuredWidth()+ "==" + getWidth());
(1). 在 activity_main.xml 中定义控件。
(2). 在 MainActivity 中 setContentView( new ViewTestLayout(this) )
可以得出如下结论:
强烈注意:
getMeasuredWidth() 是实际测量的宽度(真实)
getWidth() 是实际显示的宽度
上面的第 2 点,getWidth() 方法虽然在 onMeasure() 执行完就有值,但是现在的 getWidth() 只是临时值(和 getMeasuredWidth() 相等的临时值)。要经过 onLayout 方法的影响,getWidth() 才是真实显示的值,这个时,可能已经和 getMeasuredWidth() 不一样了。这就是这两个宽度的本质区别。
有了以上的实验,我们知道这些生命周的执行顺序,就可以知道在何时应该做何事。
方法 | 建议 |
---|---|
构造方法 | 解析 attrs 的属性,初始化成员等 |
onFinishInflate | 初始化成员,通过 getChildAt 初始化布局中的子 View |
onSizeChanged | 获取自身或子 View 的实际宽高(建议在此时将宽高抽取成全局变量) |
onMeasure | 测量,不必说 |
onLayout | 布局,不必说 |
附上 Android View 的生命周期全图,作为参考
方法 | 作用 |
---|---|
onFinishInflate() | 当View中所有的子控件均被映射成xml后触发 |
onMeasure( int , int ) | 确定所有子元素的大小 |
onLayout( boolean , int , int , int , int ) | 当View分配所有的子元素的大小和位置时触发 |
onSizeChanged( int , int , int , int ) | 当view的大小发生变化时触发 |
onDraw(Canvas) | view渲染内容的细节 |
onKeyDown( int , KeyEvent) | 有按键按下后触发 |
onKeyUp( int , KeyEvent) | 有按键按下后弹起时触发 |
onTrackballEvent(MotionEvent) | 轨迹球事件 |
onTouchEvent(MotionEvent) | 触屏事件 |
onFocusChanged( boolean , int , Rect) | 当View获取或失去焦点时触发 |
onWindowFocusChanged( boolean ) | 当窗口包含的view获取或失去焦点时触发 |
onAttachedToWindow() | 当view被附着到一个窗口时触发 |
onDetachedFromWindow() | 当view离开附着的窗口时触发,该方法和 onAttachedToWindow() 是相反的 |
onWindowVisibilityChanged( int ) | 当窗口中包含的可见的view发生变化时触发 |