深入了解触摸事件的分发
1. 触碰姿势及事情编码序列
(1)触碰事情的姿势
触碰姿势一共有三种:ACTION_DOWN、ACTION_MOVE、ACTION_UP。当客户手指头触碰显示屏时,便造成一个姿势为ACTION_DOWN的触碰事情,这时若客户的手指头马上离去显示屏,会造成一个姿势为ACTION_UP的触碰事情;若客户手指头触碰显示屏后再次滚动,当滚动间距超出了系统软件中预订义的间距参量,则造成一个姿势为ACTION_MOVE的触碰事情,系统软件中预订义的用于分辨客户手指头在显示屏上的滚动是不是一个ACTION_MOVE姿势的这一间距变量定义称为TouchSlop,可根据ViewConfiguration.get(getContext()).getScaledTouchSlop()获得。
(2)事情编码序列
当客户的手指头触碰显示屏,在显示屏上滚动,又离去显示屏,这一全过程会造成一系列触碰事情:ACTION_DOWN–>数个ACTION_MOVE–>ACTION_UP。这一系列触碰事情即是一个事情编码序列。
2. 触碰事情的派发
(1)简述
当造成了一个触碰時间后,系统软件要承担把这个触碰事情给一个View(TargetView)来解决,touch事情传送到TargetView的全过程即是touch事情的派发。
触碰事情的派发次序:Activity–>顶尖View–>顶尖View的子View–>. . .–>Target View
触碰事情的回应次序:TargetView –> TargetView的父器皿 –> . . . –>顶尖View –>Activity
(2)toush事情派发的实际全过程
a. Activity对touch事情的派发
当客户手指头触碰显示屏时,便造成了一个touch事情,封裝了touch事情的MotionEvent最开始被传送给当今Activity,Activity的dispatchTouchEvent方式承担touch事情的派发。派发touch事情的具体工作中由当今Activity的Window进行,而Window会将touch事情传送给DecorView(当今操作界面顶尖View)。Activity的dispatchTouchEvent方式编码以下:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
依据之上编码能够了解,touch事情会交给Window的superDispatchTouchEvent开展派发,若这一方式回到true,寓意touch事情的派发全过程完毕,回到false则表明历经逐层派发,沒有子View对这一事情开展解决,即全部子View的onTouchEvent方式都回到false(即这一touch事情沒有被“耗费”)。这时候会启用Activity的onTouchEvent方式来解决这一touch事情。
在Window的superDispatchTouchEvent方式中,最先会把touch事情分发送给DecorView,因为它是当今操作界面的顶尖View。Window的superDispatchTouchEvent方式以下:
public abstract boolean superDispatchTouchEvent(MotionEvent ev);
是个抽象方法,这一方式由Window的完成类PhoneWindow完成,PhoneWindow的superDispatchTouchEvent方式的编码以下:
public boolean superDispatchTouchEvent(MotionEvent ev) { return mDecor.superDispatchTouchEvent(event); }
由之上编码必得,PhoneWindow的superDispatchTouchEvent方式事实上是根据DecorView的superDispatchTouchEvent方式来进行自身的工作中,换句话说,当今Activity的Window立即将这一touch事情传送给了DecorView。换句话说,现阶段touch事情早已历经了以下的派发:Activity–>Window–>DecorView。
b. 顶尖View对touch事情的派发
历经Activity与Window的派发,如今touch事情早已被传送到DecorView的dispatchTouchEvent方式中。DecorView实质上是一个ViewGroup(更实际的说是FrameLayout),ViewGroup的dispatchTouchEvent方式所做的工作中能够分成以下好多个环节,第一个环节的关键编码以下:
1 //Handle an initial down.
2 if (actionMasked == MotionEvent.ACTION_DOWN) {
3 //Throw away all previous state when starting a new touch gesture.
4 //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.
5 cancelAndClearTouchTargets(ev);
6 resetTouchState();
7 }
第一阶段的关键工作中有俩:一是在第六行的resetTouchState方式中完成了对FLAG_DISALLOW_INTERCEPT标识的重设;二是第5行的cancelAndClearTouchTargets方式会消除当今MotionEvent的touch target。有关FLAG_DISALLOW_INTERCEPT标识和touch target,在下文会出现有关表明。
第二阶段的关键工作中是决策当今ViewGroup是不是阻拦此次的touch事情,关键编码以下:
//Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWM || 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. intercept =true; }
由之上编码我们可以了解,当一个touch事情被传送到ViewGroup时,会先分辨这一touch事情的姿势是不是ACTION_DOWN,假如这一事情是ACTION_DOWN或是mFirstTouchTarget不以null,便会依据FLAG_DISALLOW_INTERCEPT标识决策是不是阻拦这一touch事情。那麼mFirstTouchTarget是什么呢?当touch事情被ViewGroup的子View取得成功解决时,mFirstTouchTarget便会被取值为取得成功解决touch事情的View,也就是上边提升的touch target。
小结一下所述编码的步骤:在子View不干涉ViewGroup的阻拦的状况下(所述编码中的disallowIntercept为false),若当今事情为ACTION_DOWN或是mFirstTouchTarget不以空,则会启用ViewGroup的onInterceptTouchEvent方式来决策最后是不是阻拦此事情;不然(沒有TargetView而且此事情并不是ACTION_DOWN),当今ViewGroup就阻拦下此事情。 一旦ViewGroup阻拦了一次touch事情,那麼mFirstTouchTarget就不容易被取值,因而当还有ACTION_MOVE或者ACTION_UP传送到该ViewGroup时,mTouchTarget就为null,因此 所述编码第三行的标准就为false,ViewGroup会阻拦出来。从而可获得的结果是:一旦ViewGroup阻拦了一次事情,则同一事情编码序列中的剩下事情也会它默认设置被阻拦而不容易再了解是不是阻拦(即不容易再启用onInterceptTouchEvent)。
这儿存有一种独特情况,便是子View根据requestDisallowInterceptTouchEvent方式设定父器皿的FLAG_DISALLOW_INTERCEPT为true,这一标识标示是不是不允许父器皿阻拦,为true表明不允许。那样做可以严禁父器皿阻拦除ACTION_DOWN之外的全部touch事情。往往不能够阻拦ACTION_DOWN事情,是由于每每ACTION_DOWN事情来临时,都是会重设FLAG_DISALLOW_INTERCEPT这一标识位为初始值(false),因此 每每逐渐一个新touch事情编码序列(即来临一个ACTION_DOWN姿势),都是会根据启用onInterceptTouchEven了解ViewGroup是不是阻拦此事情。当ACTION_DOWN事情来临时,重设标识位的工作中是在上面的第一阶段进行的。
下面,会进到第三阶段的工作中:
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 并不是ACTION_CANCEL而且不阻拦 if (actionMasked == MotionEvent.ACTION_DOWN) { // 若当今事情为ACTION_DOWN则寻找此次事情新出現的touch target final int actionIndex = ev.getActionIndex(); // always 0 for down ... final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { // 依据触碰的座标找寻可以接受这一事情的touch target final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final View[] children = mChildren; // 解析xml全部子View for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = i; final View child = children[childIndex]; // 找寻可接受这一事情而且touch事情座标在其地区内的子View if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); // 找到满足条件的子View,取值给newTouchTarget if (newTouchTarget != null) { //Child is already receiving touch within its bounds. //Give it the new pointer in addition to ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); // 把ACTION_DOWN事情传送给子部件开展解决 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(); //把mFirstTouchTarget取值为newTouchTarget,此子View变成新的touch事情的起始点 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } } }
当ViewGroup不阻拦此次事情,则touch事情会分发送给它的子View开展解决,有关编码从第21行逐渐:解析xml全部ViewGroup的子View,找寻可以解决此touch事情的子View,若一个子View不在播放动漫而且touch事情座标坐落于其地区内,则该子View可以解决此touch事情,而且会把该子View取值给newTouchTarget。
若当今解析xml到的子View可以解决此touch事情,便会进到第38行的dispatchTransformedTouchEvent方式,该方式事实上启用了子View的dispatchTouchEvent方式。dispatchTransformedTouchEvent方式中有关的编码以下:
if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); }
若dispatchTransformedTouchEvent方式传到的child主要参数不以null,则会启用child(即解决touch事情的子View)的dispatchTouchEvent方式。若该子View的dispatchTouchEvent方式回到true,则dispatchTransformedTouchEvent方式也会回到true,则表明取得成功找到一个解决该事情的touch target,会在第55行把newTouchTarget取值给mFirstTouchTarget(这一取值全过程是在addTouchTarget方式內部进行的),并跳出来对联View解析xml的循环系统。若子View的dispatchTouchEvent方式回到false,ViewGroup便会把事情分发送给下一个子View。
若解析xml了全部子View后,touch事情都没被解决(该ViewGroup沒有子View或者全部子View的dispatchTouchEvent回到false),ViewGroup会自身解决touch事情,有关编码以下:
if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
由之上编码得知,ViewGroup自身解决touch事情时,会启用dispatchTransformedTouchEvent方式,传到的child主要参数为null。依据前文的剖析,传到的chid为null时,会启用super.dispatchTouchEvent方式,即启用View类的dispatchTouchEvent方式。
c. View对touch事情的解决
View的dispatchTouchEvent方式的关键编码以下:
public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; . . . if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement 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; } . . . return result; }
由所述编码得知,View对touch事情的处理方式以下:因为View不包含子原素,因此 它只有自身事件处理。它最先会分辨是不是设定了OnTouchListener,若设定了,会启用onTouch方式,若onTouch方式回到true(表明该touch事情早已被耗费),则不容易再启用onTouchEvent方式;若onTouch方式回到false或沒有设定OnTouchListener,则会启用onTouchEvent方式,onTouchEvent对touch事情开展实际解决的有关编码以下:
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) { . . . 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) { mPerformClck = new PeformClick(); } if (!post(mPerformClick)) { performClick(); } } } . . . } break; } . . . return true; }
由之上编码得知,只需View的CLICKABLE特性和LONG_CLICKABLE特性有一个为true(View的CLICKABLE特性和实际View相关,LONG_CLICKABLE特性默认设置为false,setOnClikListener和setOnLongClickListener会各自全自动将之上俩特性设成true),那麼这一View便会耗费这一touch事情,即便这一View处在DISABLED情况。若当今事情是ACTION_UP,还会继续启用performClick方式,该View若设定了OnClickListener,则performClick方式会在其內部启用onClick方式。performClick方式编码以下:
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
之上就是我学习培训Android中触碰事情派发后的简易小结,许多地区描述的还不够清楚精确,如有什么问题热烈欢迎大伙儿在发表评论一起探讨 🙂