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中分离了出来,代表他的生命周期已经结束。
本文如有错,烦请指出,谢谢,欢迎交流。