View的生命周期

2016年3月30日 偶尔的坚持比科学更重要

最近常常用到自定义的View和ViewGroup,且发现在自定义时总是出现一些莫名奇妙的问题,其大部分的原因可以归咎于View的生命周期。

View相当于一个Panel,所有的UI组件都继承了View。为了了解他,让我们先看看他的一些方法。

View常被重写的方法

void onFinishInflate():当应用程序通过XML布局文件加载该组件从而进行构建界面时,该方法将会被调用

onMeasure(int widthMeasureSpec, int heightMeasureSpec):用来检测View组件和其子组件的大小,其参数为为该View设置的长和宽

onLayout(boolean changed, int left, int top, int right, int bottom) :用于分配view在父控件的位置,即给view布局

onSizeChanged(int w, int h, int oldw, int oldh):当该组件的大小被改变时,调用该方法

onDraw(Canvas canvas):用于绘制组件

onKeyDown(int keyCode, KeyEvent event):当有按键被按下时出发该方法(可以监听返回键,home键)

onKeyUp(int keyCode, KeyEvent event):当有按键被松开时出发该方法

onTouchEvent(MotionEvent event):当发生触摸屏事件时触发该方法

onAttachedToWindow():当将这个组件放入某个窗口时调用该方法

onDetachedFromWindow():当将这个组件从某个窗口分离时调用这个方法

onWindowVisibilityChanged(int visibility):当包含该组件的窗口的可见性发生改变时调用该方法

onVisibilityChanged(View changedView, int visibility):当该view的是否可见发生变化时,调用该方法

onWindowVisibilityChanged和onVisibilityChanged中visibility参数的三个值: visible 0 代表view可见,是默认值 invisible 4 代表组件不可见,但保留控件的布局位置 gone 8 代表组件隐藏,不保留控件的布局位置,如果组建的visibility属性为gone,则代表该组件生命周期结束

测试View的生命周期

简单的介绍了这些方法之后,让我们再来看这些这些方法什么时候会被调用。

下面是我的测试代码,还是有必要贴出来的:

public class MyView extends View {
    public MyView(Context context) {
        super(context);
        Log.i("infoview", "==========MyView(context)");
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.i("infoview", "MyView(context,attrs)" + attrs.getAttributeName(0)
                + "===" + attrs.getAttributeName(2));
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Log.i("infoview", ==========onSizeChanged"
                + "===w:" + w + "===h" + h +"===oldw" + oldw + "===oldh" + oldh);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.i("infoview", ==========onLayout"
        + "===changed:" + changed +"====l t r b:" + left +"---"+ top +"---"+ right +"---"+ bottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.i("infoview", ==========onMeasure" + "===width:" + MeasureSpec.getSize(widthMeasureSpec)
                + "===height:" + MeasureSpec.getSize(heightMeasureSpec));
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        Log.i("infoview", ==========onFinishInflate");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.i("infoview", ==========ondraw");
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        Log.i("infoview", ==========onAttachedToWindow");
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        Log.i("infoview", ==========onDetachedFromWindow");
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        Log.i("infoview", ==========onWindowVisibilityChanged" + visibility);
    }

    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        Log.i("infoview", ==========onVisibilityChanged" + "====visibility" + visibility
                + "changedView.toString:" + changedView.toString());
    }
}

xml文件: 为了方便测试我使用了绝对布局:

<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <com.example.feathers.viewtest.MyView
        android:layout_height="50dp"
        android:layout_width="50dp"
        android:id="@+id/test"
        android:layout_x="50px"
        android:layout_y="50px"/>

</AbsoluteLayout>

通过自定义View并输出的Log信息: 刚刚创建应用程序输出的Log:

I/infoview: ==========MyView(context,attrs)id===layout_height
I/infoview: ==========onFinishInflate
I/infoview: ==========onVisibilityChanged====visibility4changedView.toString:com.android.internal.policy.impl.PhoneWindow$DecorView{267a1931 I.E..... R.....ID 0,0-0,0}
I/infoview: ==========onVisibilityChanged====visibility0changedView.toString:com.android.internal.policy.impl.PhoneWindow$DecorView{267a1931 V.E..... R.....ID 0,0-0,0}
I/infoview: ==========onAttachedToWindow
I/infoview: ==========onWindowVisibilityChanged0
I/infoview: ==========onMeasure===width:100===height:100
I/infoview: ==========onSizeChanged===w:100===h100===oldw0===oldh0
I/infoview: ==========onLayout===changed:true====l t r b:50---50---150---150
I/infoview: ==========onMeasure===width:100===height:100
I/infoview: ==========onLayout===changed:false====l t r b:50---50---150---150
I/infoview: ==========ondraw

从上面的Log信息可以看出,当应用第一次被打开时,首先会调用其构造方法,因为这个View是在XML中创建的,所以会调用MyView(context,attrs)这个构造器。 第二个参数代表XML传入的属性,比如:

attrs.getAttributeName(0)+ "===" + attrs.getAttributeName(2)

所获取的值为 id===layout_height 正好对应XML文件中设置的属性名称

<com.example.feathers.viewtest.MyView
        android:layout_height="50dp"
        android:layout_width="50dp"
        android:id="@+id/test"
        android:layout_x="50px"
        android:layout_y="50px"/>

然后调用了onFinishInflate方法,代表从XML文件加载该View完成 接着调用了onVisibilityChanged方法两次,第一次将View的visibility的属性设置为4 即不可见,第二次设置为0 即可见,这里不是很懂,为什么要调用两次。 接着调用了onAttachedToWindow,将其附加到窗口上。 onWindowVisibilityChanged0,窗口的Visibility属性为可见,这是我们可以看到窗口显示出来了。 调用了onMeasure方法,测量组件的大小,发现大小发生了变化,就调用了onSizeChanged方法,可以看见,View最初的w和h是0和0。 onMeasure方法结束后,就要为view设置他的布局了,所以这里调用了onLayout方法进行布局。 最后布局完成后,又再次调用onMeasure方法对组件的大小测量,因为在其他方法中view的组件的大小有可能再次发生变化。所以再次调用onMeasure和onLayout方法。 最后调用onDraw方法进行view的绘制。

当我点击home键时,View隐藏在了后台,其Log为:

I/infoview: ==========onWindowVisibilityChanged8
I/infoview: ==========onVisibilityChanged4

然后我再次返回到View中

I/infoview: ==========onWindowVisibilityChanged4
I/infoview: ==========onVisibilityChanged0
I/infoview: ==========onWindowVisibilityChanged0
I/infoview: ==========ondraw

当我点击返回键时

I/infoview: ==========onWindowVisibilityChanged8
I/infoview: ==========onDetachedFromWindow

view被从window中分离了出来,代表他的生命周期已经结束。

本文如有错,烦请指出,谢谢,欢迎交流。

最后更新于