赞
踩
View的生命周期方法有什么作用呢?
其实这些方法在我们自定义View的时候发挥着很大的作用,我们来举几种应用场景。
场景1:在Activity启动时获取View的宽高,但是在onCreate
、onStart
和onResume
均无法获取正确的结果。这是因为在Activity的这些方法里,View的绘制可能还没有完成,我们可以在View的生命周期方法里获取。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
场景2:在Activity生命周期发生变化时,View也要做响应的处理,典型的有VideoView
保存进度和恢复进度。
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
//TODO do something if activity lifecycle changed if necessary
//Activity onResume()
if(visibility == VISIBLE){
}
//Activity onPause()
else {
}
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
//TODO do something if activity lifecycle changed if necessary
//Activity onResume()
if (hasWindowFocus) {
}
//Activity onPause()
else {
}
}

场景3:释放线程、资源
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
//TODO release resources, thread, animation
}
SurfaceFlinger
服 务,而SurfaceFlinger
服务再使用OpenGL
图形库API来将这个图形缓冲区渲染到硬件帧缓冲区中。View是一个矩形区域,它有自己的位置、大小与边距。
getLeft()
,getTop()
)决定,该坐标是以它的父View的左上角为坐标原点,单位是pixels
。getMeasuredWidth()/getMeasuredHeight()
这组值表示了该View在它的父View里期望的大小值,在measure()
方法完成后可获得。 getWidth()/getHeight()
这组值表示了该View在屏幕上的实际大小,在draw()
方法完成后可获得。padding
来表示,它表示View的内容距离View边缘的距离。通过getPaddingXXX()
方法获取。需要注意的是我们在自定义View的时候需要单独处理 padding
,否则它不会生效,这一块的内容我们会在View自定义实践系列的文章中展开。margin
来表示,它表示View的边缘离它相邻的View的距离。在介绍测量流程之前,我们先来介绍下MeasureSpec
,它用来把测量要求从父View传递给子View。我们知道View的大小最终由子View的LayoutParams
与父View的测量要求公共决定,测量要求指的 就是这个MeasureSpec
,它是一个32位int值。
MeasureSpec.getMode(measureSpec)
方法获取测量模式MeasureSpec.getSize(measureSpec)
方法获取特定测量模式下的大小普通View的MeasureSpec
的创建规则。
MeasureSpec
是什么,resultSize都是指定的宽高,resultMode都是MeasureSpec.EXACTLY
。match_parent
,当父容器是MeasureSpec.EXACTLY
,则View也是MeasureSpec.EXACTLY
,并且其大小就是父容器的剩余空间。当父容器是MeasureSpec.AT_MOST
则View也是MeasureSpec.AT_MOST
,并且大小不会超过父容器的剩余空间。wrap_content时
,不管父容器的模式是MeasureSpec.EXACTLY
还是MeasureSpec.AT_MOST
,View的模式总是MeasureSpec.AT_MOST
,并且大小都不会超过父类的剩余空间。了解了MeasureSpec的概念之后,我就就可以开始分析测量流程了。
DecorView
)其MeasureSpec
由窗口的尺寸和自身的LayoutParams
共同确定的。MeasureSpec
由父容器的Measure
和自身的LayoutParams
共同确定的。在做测量的时候,measure()
方法被父View调用,在measure()
中做一些准备和优化工作后,调用onMeasure()
来进行实际的自我测量。对于onMeasure()
,View和ViewGroup有所区别:
onMeasure()
中会计算出自己的尺寸然后保存;onMeasure()
中会调用所有子View的measure()
让它们进行自我测量,并根据子View计算出的期望尺寸来计算出它们的实际尺寸和位置然后保存。同时,它也会 根据子View的尺寸和位置来计算出自己的尺寸然后保存。View的onMeasure()
方法实现比较简单,它调用setMeasuredDimension()
方法来设置View的测量大小,测量的大小通过getDefaultSize()
方法来获取。
注:你可以自己尝试一下自定义一个View,然后不重写
onMeasure()
方法,你会发现只有设置match_parent
和wrap_content
效果是一样的,事实上TextView
、ImageView
等系统组件都在wrap_content
上有自己的处理,可以去翻一翻源码。
ViewGroup继承于View,是一个抽象类,它并没有重写onMeasure()
方法,因为不同布局类型的测量 流程各不相同,因此onMeasure()
方法由它的子类来实现,例如FrameLayout
。
以上便是Measure的整个流程,该流程完成以后,我们可以通过getMeasuredWidth()
与getMeasuredHeight()
来获得View的宽高。但是在某些情况下,系统需要经过多次Measure
才能确定 最终的宽高,因此在onMeasure()
方法中拿到的宽高很可能是不正确的,比较好的做法是在onLayout()
方法中获取View的宽高。
在进行布局的时候,layout()
方法被父View调用,在layout()
中它会保存父View传进来的自己的位置和尺寸,并且调用onLayout()
来进行实际的内部布局。对于onLayout()
,View和ViewGroup有所区别:
onLayout()
什么也不做。onLayout()
中会调用自己的所有子View的layout()
方法,把它们的尺寸和位置传给它们,让它们完成自我的内部布局。layout()
方法用来确定View本身的位置,onLayout()
方法用来确定子元素的位置。
onLayout()
的实现依赖于具体的布局,所以View/ViewGroup并没有实现这个方法,可由子类实现或者不实现,例如TextView/FrameLayout
,一些继承自View的控件并不实现onLayout()
,只需要绘制(draw)自己就可以。
绘制从ViewRoot.draw()
开始,它首先会创建一块画布,接着再在画布上绘制Android上的UI,再把画布的内容交给SurfaceFlinger
服务来渲染。
在介绍View的事件分发机制之前,我们要先了解两个概念。
ACTION_DOWN
、ACTION_MOVE
等,我们还可以通过它获取事件发生的坐标,getX/getY
获取相对于当前View左上角的坐标,getRawX/getRawY
获取相对于屏幕左上角的坐标。ViewConfiguration.get(context).getScaledTouchSlop()
方法获取。现在我们再来看看View里的事件分发机制,概括来说,可以用下面代码表示:
public boolean dispatchTouchEvent(MotionEvent event){
boolean consume = false;
//父View决定是否拦截事件
if(onInterceptTouchEvent(event)){
//父View调用onTouchEvent(event)消费事件
consume = onTouchEvent(event);
}else{
//调用子View的dispatchTouchEvent(event)方法继续分发事件
consume = child.dispatchTouchEvent(event);
}
return consume;
}
我们再来具体看看各个场景中的事件分发。
当点击事件发生时,事件最先传递给Activity,Activity会首先将事件将诶所属的Window进行处理,即调用superDispatchTouchEvent()
方法。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback {
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
}

通过观察superDispatchTouchEvent()
方法的调用链,我们可以发现事件的传递顺序:
事件一层层传递到了ViewGroup里,关于ViewGroup对事件的处理,我们下面会说,如果superDispatchTouchEvent()
方法返回false,即没有 处理该事件,则会继续调用Activity的onTouchEvent(ev)
方法来处理该事件。可见Activity的onTouchEvent(ev)
在事件处理的优先级是最低的。
ViewGroup作为View容器,它需要考虑自己的子View是否处理了该事件,具体说来:
onInterceptTouchEvent()
返回true,则该事件由ViewGroup处理,如果ViewGroup调用了setOnTouchListener()
则该接口的onTouch()
方法会被调用 否则会调用onTouchEvent()
方法。dispatchTouchEvent()
会被调用,View.dispatchTouchEvent()
的处理流程前面我们已经分析过。View没有子元素,无法向下传递事件,它只能自己处理事件,所以View的事件传递比较简单。
如果外界设置了OnTouchListener
且OnTouchListener.onTouch(this, event)
返回true,则表示该方法消费了该事件,则onTouchEvent(event)
不再被调用。 可见OnTouchListener
的优先级高于onTouchEvent(event)
,这样是为了便于外界处理事件。
关于onTouchEvent(MotionEvent event)
,有两点需要说明一下:
disable
属性不会影响onTouchEvent()
方法的返回值,哪怕View是disable
的,只要 View的clickable
或者longClickable
为true,onTouchEvent()
方法还是会返回true。clickable
或者longClickable
为true,onTouchEvent()
方法就会消费这个事件OnClickListener
,则performClick()
会调用它的onClick
方法。上面我们提到了viewFlags
里的CLICKABLE
与LONG_CLICKABLE
,也就是xml或者代码里可以设置的clickable
与longClickable
,View的LONG_CLICKABLE
默认为 true,CLICKABLE
默认为false,值得一提的是setOnClickListener()
方法和setOnLongClickListener()
会将这两个值设置为true。
通过对源码的分析,我们已经掌握了各种场景下事件分发的规律,我们再来总结一下View事件分发的相关结论。
Activity -> Window -> View
的顺序进行的onInterceptTouchEvent()
方法,一但有点击事件传递给它,它的ouTouchEvent()
方法就会被调用。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。