Android中的事件分发机制
2014-03-03 21:45
387 查看
1. 一个小问题引发的思考
2. 通过源码探索View中的事件分发机制
3.通过源码探索ViewGroup的事件分发机制
4.结论
5.参考文献
最近的一个项目中涉及到UGC的处理,大致布局为一个RelativeLayout包含了一个EditText和一个Button,当点击EditText时,弹出软键盘,点击RelativeLayout中除了EditText和Button之外其它的地方时,收起软键盘。实现起来很简单,为EditText和RelativeLayout分别注册一个onTouch事件,为Button注册一个click事件,一切Ok~ 但在业务层简单的实现背后,却给我带来了一些疑问:
1. EditText作为RelativeLayout的子元素,为何它的onTouch事件没有触发父元素(RelativeLayout)的onTouch事件,父子节点的同一事件的事件分发逻辑是怎样呢?
2. onClick事件和onTouch事件有和关联呢?
3. 我们既可以为控件注册onTouch事件(setOnTouchLisnter),也可以自定义控件实现onTouchEvent方法,onTouch方法和onTouchEvent方法有何区别呢,它们的执行时机是什么?
带着这三个疑问,我踏上了google, 度娘,以及Android源代码探索的不归路~
Android中,所有的操作类型事件都由如下三个部分作为基础:
按下(ACTION_DOWN)
移动(ACTION_MOVE)
抬起(ACTION_UP)
这三部分都寄生于onTouch事件中,由MontionEvent类中定义的三个常量进行区分。
Android中与Touch事件相关的方法为:
Touch事件相关方法 | 方法功能 | ViewGroup | View(子View) | Activity |
public boolean dispatchTouchEvnet(MotionEvent ev) | 事件分发 | Yes | Yes | Yes |
public boolean onInterceptTouchEvent(MotionEvent ev) | 事件拦截 | Yes | No | No |
public boolean onTouchEvent(MotionEvent ev) | 事件响应 | Yes | Yes | Yes |
为了证明这一逻辑,我们对代码一层层地分析,首先看下View中的dispatchTouchEvent方法:
View.java
public boolean dispatchTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
整个程序中有最关键的两处判断(第9行和14行),在第9行的if条件中,如果li.mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED 和 mOnTouchListener.onTouch(this, event) == true这三个条件同时满足时,就直接返回true。
第一个条件:mOnTouchListener在哪里赋值呢?CRTL+F搜索,发现了View.java中的如下方法:
View.java
public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }
太眼熟了,这不正是我们平时注册onTouch事件所使用的函数么,这下明白了,这要注册了onTouch事件,mOnTouchListener就一定不为空。好了,继续往下看。
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED 判断当前的View是否为ENABLED,一般控件默认都是ENABLED,因此这个条件也成立。
第三个条件:mOnTouchListener.onTouch(this, event) == true,即我们注册的onTouch方法中,如果返回true,就会使三个条件都成立,这样,就不会继续接下来的判断了。
在第14行的If判断中,只是简单的判断了onTouchEvent方法的返回是否为true。
上述短短的几行代码解开了我的第三个疑问,下面来总结一下:
在View的dispatchTouchEvent方法中,最先执行的是onTouch方法,只有当onTouch方法返回false,才有机会去执行onTouchEvent方法。
touch事件有层级传递机制,当我们为一个view注册touch事件,就会触发一系列的ACTION_DOWN,ACTION_MOVE_ACTION_UP,如果在执行ACTION_DOWN的时候返回false,则后面的ACTION_MOVE和ACTION_UP都得不到执行,即使用[b]dispatchTouchEvent进行分发的时候,只有前一个action返回false,后一个action才能得到执行。[/b]
在上述的的第二个结论中,我们可知,只有当onTouch方法和onTouchEvent都返回false,才能继续后面的操作,onTouch方法的返回一般是我们自己控制的,而onTouchEvent方法,若不自定义控件,则往往会使用它的默认实现,接下来通过源码分析一下方法:
View.java
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }
相关文章推荐
- Android 编程下 Touch 事件的分发和消费机制
- Android事件分发机制
- Android下的事件分发机制(结合源码分析)
- Android 事件分发机制
- Android开发总结笔记 ViewGroup的事件分发机制 3-10
- Android事件分发机制源码解析(二)-ViewGroup的事件分发机制
- Android 事件分发机制
- Android事件分发机制完全解析,带你从源码的角度彻底理解
- Android事件分发机制
- 浅谈android事件分发机制
- Android事件分发机制完全解析,带你从源码的角度彻底理解(一)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制详解
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android 编程下 Touch 事件的分发和消费机制
- (转载) Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制(一) Touch 事件的分发和消费机制
- Android中的事件分发传递机制
- Android 编程下 Touch 事件的分发和消费机制
- 有关Android的事件分发机制