Android进阶系列1—View的事件分发体系
来源:互联网 发布:三岛由纪夫 禁色 知乎 编辑:程序博客网 时间:2024/06/09 01:35
本文主要基于《 Android触摸屏事件派发机制详解与源码分析一(View篇)》系列,《 自定义View系列教程06–详解View的Touch事件处理》以及《Android开发艺术探索-View的事件分发机制》三部分的内容的自我总结。
本文自上而下阐述View的事件从Activity开始的分发过程,阐述过程和相关结论,对细节不做过多分析,感兴趣的同学可以参考上述三部分内容。
Activity的事件分发
当一个点击事件发生时,事件最先传递给当前Activity,由它的dispatchTouchEvent进行派发,具体的工作由Activity内部的Window完成。Window会将事件传递给DecorView,完成从Activity到ViewGroup的事件分发传递。
- 首先看下Activity内部的dispatchTouchEvent()方法:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction();//空方法 } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev);//如果一个屏幕触摸事件没有被这个Activity下的任何View所处理,Activity的onTouchEvent将会调用 }
getWindow()返回PhoneWindow的对象
- 查看PhoneWindow的superDispatchEvent方法
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
在PhoneWindow的superDispatchTouchEvent(ev)里又直接返回了另一个mDecor对象的superDispatchTouchEvent方法。mDecor是DecorView类的实例。
这里补充下Activity,Window,PhoneWindow和DecorView间的关系,如图所示:
Activity有一个成员——Window,它是一个抽象类,提供了绘制窗口的一组通用API。PhoneWindow是Window的一个具体实现,且PhoneWindow内部包含了一个DecorView内部类,同时DecorView也是FrameLayout的子类,是所有应用窗口的根View。四者之间的层次关系可以理解成:Window是一块电子屏,PhoneWindow是一块手机电子屏,DecorView就是电子屏要显示的内容,Activity就是手机电子屏安装位置。接着看DecorView的superDispatchTouchEvent()方法
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
由于DecorView是FrameLayout的子类,所以我们追溯到了ViewGroup的dispatchTouchEvent()。
可见事件分发从Activity交到了ViewGroup中
ViewGroup的事件分发
ViewGroup的dispatchTouchEvent逻辑大概是这样的:
public boolean dispatchTouchEvent(MotionEvent mv){ boolean consume=false; if(onInterceptTouchEvent(ev)){ cousume=onTouchEvent(ev); }else{ consume=child.dispatchTouchEvent(ev); } return consume;}
对比较重要的步骤进行梳理,状态初始化或者清空这些步骤就忽略不计。
第一步:
ViewGroup的分发机制要比上述伪代码复杂,是否拦截这部分涉及的判断就比较多。
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); } else { intercepted = false; } } else { intercepted = true; }
如果当前是ACTION_DOWN事件或者子View消费了Touch事件,则进入是否拦截的判断,否则直接拦截(即ACTION_DOWN事件没有被 子View消费,则ViewGroup直接拦截后续事件自行处理)。在拦截判断中,需要查看disallowIntercept值,这是由子View设置的禁止拦截标识,如果子View禁止拦截,则ViewGroup不能拦截。否则由onInterceptTouchEvent()的返回值决定。注意:子View中对ViewGroup的ACTION_DOWN禁止拦截设置不起作用,因为ViewGroup中调用会resetTouchState()重置标识位。
第二步
向子View分发ACTION_DOWN事件
if (!canceled && !intercepted) { ...... 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 (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child)||!isTransformedTouchPointInView(x,y,child,null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev,false,child,idBitsToAssign)) { mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { 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; } ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild,target.child,target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } if (canceled||actionMasked==MotionEvent.ACTION_UP||actionMasked==MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }
如果ACTION_DOWN的Touch事件没有被取消也没有被拦截,那么将事件分发给子View:
1. 依据坐标找到被点击的子View
2. 调用dispatchTransformedTouchEvent()将Touch分发给子View处理。这是个递归调用,如果子View为ViewGroup那么递归调用dispatchTouchEvent(),如果子View为View那么就会调用其onTouchEvent()或者onTouch(),返回处理结果。
3. 根据子View的处理的返回设置mFirstTouchTarget,如果未处理则为null。否则调用addTouchTarget置true;
第三步
继续向Touch分发处理
- 如果mFirstTouchTarget==null,就调用dispatchTransformedTouchEvent(null),由ViewGroup自行处理,执行super.dispatchTouchEvent(event)。此时,ViewGroup就调用View的onTouch(),onTouchEvent()中处理Touch。(比如第二步分发过ACTION_DOWN,但是没有被成功消费)。
- mFirstTouchTarget!=null,如果是ACTION_DOWN事件,则已成功被消费,不再做处理。如果不是ACTION_DOWN事件,则用dispatchTransformedTouchEvent(child)分发给子View。
小结:如果ViewGroup拦截了事件或者子View没有消耗事件,则ViewGroup自行处理事件;如果子View消耗了事件,那么父View不能再处理。
ViewGroup的详细分发流程图如图:
不管是ViewGroup自行处理还是进入到子View的dispatchTouchEvent,事件分发都要进入到View的dispatchTouchEvent方法中。下面就进入到View的事件分发函数中看看。
View的事件分发
Touch事件进入到一个View层级,首先调用dispatchTouchEvent();
public boolean dispatchTouchEvent(MotionEvent event) { ...... ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags&ENABLED_MASK)==ENABLED && li.mOnTouchListener.onTouch(this,event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } ...... }
如果View注册了TouchListener,View Enabled且onTouch返回true,则dispatchTouchEvent返回,不执行onTouchEvent()方法。否则执行onTouchEvent(),dispatchTouchEvent返回值和onTouchEvent相同。
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); }//如果一个View是disabled,只要clickable,longclickable,contextclickable一个为真,则返回真,表示消耗掉了此事件。 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: ...... if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { removeLongPressCallback(); if (!focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } ....... break; ...... } return true; } return false; }
ACTION_UP的处理时调用了performClick()。
⚠️:如果View是enable的,只要该View满足CLICKABLE和LONG_CLICKABLE以及CONTEXT_CLICKABLE这三者的任意一个onTouchEvent()返回的均是true而且会在ACTION_UP时处理click事件,否则返回false
总结下,dispatchTouchEvent返回值和onTouchEvent一致(如果 onTouch返回true,则dispatchTouchEvent返回true),无论View是否enabled,只要clickable,long_clickable或者context_clickable一项为真,则返回true,消耗掉事件。
View的dispatchTouchEvent流程如下:
乌啦啦,从Activity到ViewGroup,再到View的事件分发机制就总结完喽,dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent三种方法在不同层间的处理流程梳理了一遍(当然,View里面是没有onInterceptTouchEvent这个方法)。
- Android进阶系列1—View的事件分发体系
- Android进阶系列3—再说View的事件分发
- Android进阶-view 的事件分发机制
- Android开发进阶—View的事件体系
- Android——View的事件体系(二)View的事件分发机制
- Android中View的事件体系(2)——View滑动与事件分发
- 【Android系列】View的事件分发机制
- View的事件体系——事件分发机制
- View的事件体系之--View的事件分发机制
- Android View体系(五)从源码解析View的事件分发机制
- Android —View的事件分发机制
- 【android】View的事件体系1-基础
- Android View的事件分发
- Android View的事件分发
- Android View的事件分发
- android View的事件分发
- Android View的事件分发
- View的事件体系(下)(事件分发,滑动冲突)
- Linux Samba服务器配置
- 转-Spring Boot 快速入门
- angularjs 验证(w5cValidator 2.0 验证信息框架的封装)
- Hibernate3中持久化对象的状态
- 为什么adrl r2,mem_cfg_val这里不用ldr r2,=mem_cfg_val
- Android进阶系列1—View的事件分发体系
- logback 配置详解
- binutils 工具: strings 简单分析
- NSTimer
- 【PAT】(乙级)1013. 数素数 (20)
- UML解惑:图说UML中的六大关系
- LeetCode #349
- Can't call rollback when autocommit=true
- 如何通过maven库查找pom中dependency