对 ViewGroup 生命周期执行顺序的理解

生命周期在 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());
    }   
}

分别在这三种情况下输出日志:

  1. 在 activity_main.xml 中定义控件。
<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>

对 ViewGroup 生命周期执行顺序的理解_第1张图片

  1. 在 MainActivity 中 setContentView( new ViewTestLayout(this) )

  1. 直接在 MainActivity new ViewTestLayout(this) 并不直接添加到任何父布局

可以得出的结论是

  1. 直接 new ,不添加到任何父布局,只会调用构造方法
  2. onFinishInflate 只会在从 xml 中加载时调用,并且只调用一次。
  3. 初始化 ViewGroup 的流程大致为:构造方法创建对象->从布局加载(xml中定义时)->第一遍测量->开始改变尺寸->第一遍布局->第二遍测量->第二遍布局
  4. 从 xml 加载第一遍测量和第二遍测量,都会调用两次 onMeasure(值得注意)

对 ViewGroup 生命周期执行顺序的理解_第2张图片

getWidth() 和 getMeasuredWidth()

这两个都是获得自身的宽度和测量后的宽度,那么我们在自定义 ViewGroup 时,应该在什么时机使用,才能保证取到值。

将上面的日志改为:System.out.println("onFinishInflate"+ " = " + (++index)+ "==" +getMeasuredWidth()+ "==" + getWidth());

(1). 在 activity_main.xml 中定义控件。

对 ViewGroup 生命周期执行顺序的理解_第3张图片

(2). 在 MainActivity 中 setContentView( new ViewTestLayout(this) )

对 ViewGroup 生命周期执行顺序的理解_第4张图片

可以得出如下结论:

  1. getMeasuredWidth(): 只要一执行完 setMeasuredDimension() 方法,就有值了,并且不再改变。
  2. getWidth():必须执行完 onMeasure() 才有值,可能发生改变。
  3. 如果 onLayout 没有对子 View 实际显示的宽高进行修改,那么 getWidth() 的值 == getMeasuredWidth() 的值。

强烈注意:

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发生变化时触发

你可能感兴趣的:(android,ViewGroup)