Android——View事件分发机制
2016-05-21 23:16
441 查看
我们经常使用的控件,如:TextView,Button,ListVIew等控件,他们都是继承自View的,当我们点击一个View控件后,会触发控件的一些行为,那这些行为是如何通过Activity传递到View中的,下面我们通过源码来进行了解:
首先,当我们在一个Activity上点击一个控件的时候,会触发Activity.dispatchTouchEvent(),从方法的字面意思就可以看出此方法就是对触摸行为进行分发
当有ACTION_DOWN事件来的时候,会调用onUserInteraction
这个方法是空实现,回调的时机是当按键、触屏或者轨迹球事件初始化时
然后再看onUserLeaveHint,他们两个是相铺相成的
注解上说,这个方法调用的时机是activity有意图去进入后台中,比如:当用户按下“home”或者后退键,它会在onPause方法调用之前进行调用的,如果是因为一个Activity进入而使其进入到后台中,这种情况onUserLeaveHint是不会调用的。
mDecor就是DecorView的一个对象:
DecorView就是Activity中的顶级View,可以通过getWindow().getDecoreView()来获得
可以看到它是回到父类的方法,FrameLayout又是继承自GroupView的,我们直接看GroupView中的dispatchTouchEvent():
这一段逻辑是检查是否要中断,有两种情况会判断是否要进行中断检查,第一种是当触发MotionEvent.ACTION_DOWN,第二种是判断mFristTouchTarget是否为null,第一种好理解,第二种mFristTouchTarget这个字段,从后面源码我们会了解到,先在这里说一下,这个字段会在当有View消了MotionEvent.Action_DOWN事件,mFristTouchTarget就会不为null,如果没有View消耗Motion.ACTION_DOWN事件,那么mFristTouchTarget == null,在ACTION_MOVE、ACTION_UP事件中,就会执行else语句,也就是把intercepted设为ture,这样ACTION_DOWN之后的事件都会交由它处理,而不会向下进行分发。
这里面,就是重置触摸的一些状态,首先在clearTouchTargets()会把mFristTouchTarget制为null,然后会对FLAG_DISALLOW_INTERCEPT进行重置,这样就不会对ACTION_DOWN事件就行中断,而是调用onInterceptTouchEvent(),让ViewGroup来决定是否要中断。
可以看到,把target赋给了mFirstTouchTarget
首先,当我们在一个Activity上点击一个控件的时候,会触发Activity.dispatchTouchEvent(),从方法的字面意思就可以看出此方法就是对触摸行为进行分发
/** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * * @return boolean Return true if this event was consumed. */ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }注解说,这个方法是用来处理在屏幕上的触摸事件的,可以重写这个方法来让事件不能分发到window,如果这个事件被消耗掉的话,应该返回true。
当有ACTION_DOWN事件来的时候,会调用onUserInteraction
/** * Called whenever a key, touch, or trackball event is dispatched to the * activity. Implement this method if you wish to know that the user has * interacted with the device in some way while your activity is running. * This callback and {@link #onUserLeaveHint} are intended to help * activities manage status bar notifications intelligently; specifically, * for helping activities determine the proper time to cancel a notfication. * * <p>All calls to your activity's {@link #onUserLeaveHint} callback will * be accompanied by calls to {@link #onUserInteraction}. This * ensures that your activity will be told of relevant user activity such * as pulling down the notification pane and touching an item there. * * <p>Note that this callback will be invoked for the touch down action * that begins a touch gesture, but may not be invoked for the touch-moved * and touch-up actions that follow. * * @see #onUserLeaveHint() */ public void onUserInteraction() { }
这个方法是空实现,回调的时机是当按键、触屏或者轨迹球事件初始化时
然后再看onUserLeaveHint,他们两个是相铺相成的
/** * Called as part of the activity lifecycle when an activity is about to go * into the background as the result of user choice. For example, when the * user presses the Home key, {@link #onUserLeaveHint} will be called, but * when an incoming phone call causes the in-call Activity to be automatically * brought to the foreground, {@link #onUserLeaveHint} will not be called on * the activity being interrupted. In cases when it is invoked, this method * is called right before the activity's {@link #onPause} callback. * * <p>This callback and {@link #onUserInteraction} are intended to help * activities manage status bar notifications intelligently; specifically, * for helping activities determine the proper time to cancel a notfication. * * @see #onUserInteraction() */ protected void onUserLeaveHint() { }
注解上说,这个方法调用的时机是activity有意图去进入后台中,比如:当用户按下“home”或者后退键,它会在onPause方法调用之前进行调用的,如果是因为一个Activity进入而使其进入到后台中,这种情况onUserLeaveHint是不会调用的。
onUserInteraction 与 onUserLeaveHint方法一般是用于对通知栏进行管理。
好了,回过来继续看dispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) { return true; }通过getWindow得到当前Activity的window,window是抽象类,下面是Window的注解
* <p>The only existing implementation of this abstract class is * android.policy.PhoneWindow, which you should instantiate when needing a * Window. Eventually that class will be refactored and a factory method * added for creating Window instances without knowing about a particular * implementation. */从注解可以看到,Window的实现类是PhoneWindow,下面看看PhoneWindow.superDispathTouchEvent()
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
mDecor就是DecorView的一个对象:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {}
DecorView就是Activity中的顶级View,可以通过getWindow().getDecoreView()来获得
然后看DecorView中的superDispatchTouchEvent():
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
可以看到它是回到父类的方法,FrameLayout又是继承自GroupView的,我们直接看GroupView中的dispatchTouchEvent():
这个方法中的代码比较多,但我们只是分析View事件的分发机制,所有我就一部分一部分的截:
// Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; }
这一段逻辑是检查是否要中断,有两种情况会判断是否要进行中断检查,第一种是当触发MotionEvent.ACTION_DOWN,第二种是判断mFristTouchTarget是否为null,第一种好理解,第二种mFristTouchTarget这个字段,从后面源码我们会了解到,先在这里说一下,这个字段会在当有View消了MotionEvent.Action_DOWN事件,mFristTouchTarget就会不为null,如果没有View消耗Motion.ACTION_DOWN事件,那么mFristTouchTarget == null,在ACTION_MOVE、ACTION_UP事件中,就会执行else语句,也就是把intercepted设为ture,这样ACTION_DOWN之后的事件都会交由它处理,而不会向下进行分发。
要想执行onIntercepTouchEvent(),disallowIntercept得为false,disallowIntercept是通过FLAG_DISALLOW_INTERCEPT标记位进行控制的,这个标记位可以通过requestDisallowInterceptTouchEvent来进行设置,这个方法是ViewParent中的方法,在ViewParent中实现了这个方法,在子view中使用。当dispatchIntercept被设置了,viewGroup就中断不了除了ACTION_DOWN的其他事件,所以在这里给我们提供一个处理滑动冲突的解决方法。
为什么说除了ACTION_DOWN,看下面一段代码:
// Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); }这段代码是在上端代码之前执行的,当有ACTION_DOWN事件到来时,会调用resetTouchState()
/** * Resets all touch state in preparation for a new cycle. */ private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes = SCROLL_AXIS_NONE; }
这里面,就是重置触摸的一些状态,首先在clearTouchTargets()会把mFristTouchTarget制为null,然后会对FLAG_DISALLOW_INTERCEPT进行重置,这样就不会对ACTION_DOWN事件就行中断,而是调用onInterceptTouchEvent(),让ViewGroup来决定是否要中断。
接下来看一段代码:
final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }
}这一段代码,是当ViewGroup不中断事件,就会对ViewGroup中的子View进行遍历,要得到能够接受事件的子元素,必须要满足两个条件:
1,canViewReceivePointerEvents(child),判断子元素是否在用户点击的区域内
2,isTransformedTouchPointInView(),判断子元素是否在播放动画
resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; }这段代码也是在for语句里,先看 dispatchTransformedTouchEvent();
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } }如果child不为null,就调用子View的dispathchTouchEvent()来进行分发,完成一轮事件分发,否则就直接执行View类中的dispathTouchEvent来对事件进行处理,如果dispatchTransformedTouchEvent返回true,说明子View消耗了触摸事件,如果返回false,说明所有的子元素都没有被合适的处理,这种情况有两种:
1)ViewGroup中没有子元素
2)子元素处理了事件,但是在dispathTouchEvent中返回可false,一般都是在onTouchEvent中返回了false。
还记得上面我们说的,当子View消耗了触摸事件,就会对mFristTouchTarget进行赋值,这个过程就是在
addTouchTarget()方法中进行的
/** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. */ private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
可以看到,把target赋给了mFirstTouchTarget
可以看到,view事件分发是从Activity-->Window-->View
到此,view事件的分发机制差不多就分析完了,至于view对点击事件的处理过程以及view的onTouch、onTouchEvent、onClick调用的机制等在下一篇博文中进行分析。
感谢大家的阅读,如有不足之处,还望指出!
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories