300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > View的事件体系之三 android事件分发机制详解(下)

View的事件体系之三 android事件分发机制详解(下)

时间:2024-06-11 13:07:04

相关推荐

View的事件体系之三 android事件分发机制详解(下)

接着上一篇来分析事件分发机制,在看了各位大牛的关于事件分发机制的分析后茅塞顿开,之前看过好几遍郭霖,弘扬以及玉刚大神关于事件体系的讲解,一直看不懂,比较模糊,最近复习时,看到一篇博文,写的相当精彩,看完后,再回看各位大神的博文,收获颇丰,记录一下自己的理解和感受,不喜勿喷。文尾会贴一下各位大牛相关博客地址,方便大家查看。首先看一下View事件体系相关的知识点

图片内容结构引用自“Carson_Ho —Android事件分发机制详解:史上最全面、最易懂”。

接下来我会从三个方面去分析事件分发机制

1.从整体理解事件分发机制的原理

手机系统由于手指点击触摸而产生一系列的回应称之为事件,而Android中将一系列的Touch事件细节(包括触摸事件,位置等等)封装为一个MotionEvent对象,而发生的主要的Touch事件有:

MotionEvent.ACTION_DOWN:按下事件MotionEvent.ACTION_MOVE:移动事件MotionEvent.ACTION_UP: 抬起事件MotionEvent.ACTION_CANCEL:当前事件已终止,可以看做一个UP事件,但是不执行任何操作。

而事件的分发的过程本质就是一个MotionEvent发生之后,在系统中传递最后至一个具体的View消费掉,即响应的过程。

一个完整的事件列由几部分组成:

一个事件在这几个对象中的传递过程是 Activity(PhoneWindow)–>ViewGroup–>View;

而事件在这几个对象之间传递时是由哪几个方法具体协调工作完成事件传递,最终被消费的呢?这里就需要介绍整个事件传递过程中的核心:dispatchTouchEvent(),onInterceptTouchEvent(),以及onTouchEvent(),三个方法。

1.2流程解析

前面已经介绍过,Android的事件分发是由Activity,ViewGroup,View三者之间的传递来构成的,通过dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()这三个核心的方法来配合完成的。而具体的分发过程是:Activity–>ViewGroup–>View,具体的流程是什么样?方法之间由于返回值的不同是如何响应的呢?我们从一张图入手。

根据这张图我们来详细的了解一下整个分发过程:

当你开始点击屏幕时,事件开始分发,首先会将按下事件即ACTION_DOWN进行分发,当传到Activity的dispatchTouchEvent()时,返回false,调用自身的onTouchEvent()方法,事件被消费,后续事件不再向下分发。返回true,事件将被消费,且后续事件(Move、Up)会继续分发到该View。默认情况下会调用Window类的superDispatchTouchEvent(),而Window为抽象类,它具体的实现类为PhoneWindow类(关于Activity,Window,PhoneWindow以及DecorView上面已经介绍过了,再不赘述),在PhoneWindow中我们可以看到,它又调用了DecorView中的superDispatchTouchEvent,再继续跟踪发现调用到了ViewGroup的dispatchTouchEvent()。

ViewGroup中的dispatchTouchEvent()返回true时,事件被消费,后续事件(Move、Up)会继续分发到该View,返回false时,事件没有被消费,但是也不会向下分发,会将事件回传给父控件的onTouchEvent()去处理同样后续事件(Move、Up)会继续分发到该View,倘若调用super即调用父类的方法,会调用自身的onInterceptTouchEvent(),(相比Activity和View唯一不同的就是ViewGroup有一个拦截事件分发的方法onInterceptTouchEvent()),当onInterceptTouchEvent()返回true时,事件被拦截,将不会再向下分发,直接调用自身的onTouchEvent()处理,同一事件列中的其他事件将由该View来处理,在同一事件列中该方法将不会在被调用。返回为false时将不拦截事件,事件继续向下分发

当事件分发至View中的dispatchTouchEvent()时,也是有三种情况,返回为true,即消费事件,事件不向下分发,后续事件会继续分发到该View,返回false,事件没有被消费,但是也不会向下分发,会将事件回传给父控件的onTouchEvent()去处理,同样后续事件(Move、Up)会继续分发到该View,,返回super时调用自身的onTouchEvent()

倘若View的onTouchEvent()返回为true,事件被消费,自己处理(消费)该事件,该事件列的后续事件(Move、Up)让其处理;返回false/super时,不处理该事件。调用父类onTouchEvent(),事件往上传递给父控件的onTouchEvent()处理,且不再接受此事件列中的后续事件。

当View中返回false时,事件被回传至ViewGroup的onTouchEvent(),接下来同View中的一样。

当事件传递完一圈没人消费时,最终会被Activity的oTouchEvent()处理。

注意:整个传递过程会有特别需要注意的部分:

1、onInterceptTouchEvent()方法对DOWN事件返回了false,后续的事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent(),一旦返回true,该事件列的其他事件(Move、Up)将不会再传递给B的onInterceptTouchEvent方法,在同一事件列此方法就再也不会被调用了,而onTouchEvent()对DOWN事件返回了false,将不会再接受事件列中的后续事件。

2、拦截DOWN的后续事件,这里需要注意,ViewGroup的onInterceptTouchEvent方法返回true拦截后续事件比如MOVE事件,该事件并没有传递给此ViewGroup的onTouchEvent();这个MOVE事件将会被系统变成一个CANCEL事件向下传递给的View的onTouchEvent方法。后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()。

到这基本已经能理解整个事件的传递过程了,接下来我们将走进源码,从源码中再次深入理解事件分发机制。

2.3 走进源码理解事件分发机制

2.3.1 Activity中事件分发源码分析

新建一个TouchActivity,让其继承自Activity,前面也说过,一个点击事件是从Activity的dispatchTouchEvent()开始的,进入Activity的源码,我们看到:

/*** 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);}

从上面源码中看:当点击事件发生传递时,第一个if条件肯定为真,那么肯定会进入onUserInteraction()

/*......* @see #onUserLeaveHint()*/public void onUserInteraction() {}

可以看到这个方法并没有实现,这个方法的主要作用是:只要用户与Activity有交互就会调用,Activity在分发各种事件的时候会调用它,与Android系统中辅助功能的相关的交互时会用到。

接着看Activity中dispatchTouchEvent()第二个if条件:

if (getWindow().superDispatchTouchEvent(ev)) {return true;}

调用了Window类的superDispatchTouchEvent()方法,而Window是抽象类,他的具体实现类是PhoneWindow,我们可以从PhoneWindow中找到:

@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}

可以看出又调用了DecorView的superDispatchTouchEvent()再往下追,来到DecorView的superDispatchTouchEvent():

public boolean superDispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);}

这里会有三种情况,前两种情况看下图:

第三种是默认情况,调用super.dispatchTouchEvent(event)进入ViewGroup的dispatchTouchEvent()。那么ViewGroup的dispatchTouchEvent()什么时候返回true什么时候返回fasle呢?请继续往下看。

2.3.2 ViewGroup事件分发源码分析

接下来将走进ViewGroup的dispatchTouchEvent()方法,在这使用Android 7.1.1 (Nougat)版本的源码。

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {//(1)这部分是调试用的,忽略代码if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(ev, 1);}//确定事件分发的目标视图是不是一个可以访问的视图,如果是,则开始正常的事件调度if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {ev.setTargetAccessibilityFocus(false);}boolean handled = false;/**View中的方法,应用安全策略过滤触摸事件,有两个返回值,true表示这个事件被分发,false表示被过滤掉**/if (onFilterTouchEventForSecurity(ev)) {final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// 处理初始的Down事件.if (actionMasked == MotionEvent.ACTION_DOWN) {//在当开始一个新的触摸手势前,清除之前的Touch状态。//由其他状态的改变,比如切换app,ANR,系统会清除掉之前的UP和cancel事件。cancelAndClearTouchTargets(ev);resetTouchState();}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~超级无敌分割线~重点来了~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// ViewGroup中才会有的操作,检查是否要拦截事件分发final boolean intercepted;//倘若是按下事件或者时间分发目标的列表中的第一个触摸目标不为null,开始执行if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {//disallowIntercept 有两个值,false不允许拦截,true允许拦截,disallowIntercept 可以通过viewGroup.requestDisallowInterceptTouchEvent(boolean);进行设置final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {//不允许拦截//再去判断onInterceptTouchEvent方法,也有两返回值,true,拦截,false不拦截。intercepted = onInterceptTouchEvent(ev);ev.setAction(action); //在更改之后恢复action} else {intercepted = false;}} else {//后续事件就不会往子View传递了,并且DOWN,MOVE,UP子View都不会向下分发。intercepted = true;}//如果被拦截,启动正常的事件调度,即事件将不会向下分发 if (intercepted || mFirstTouchTarget != null) {ev.setTargetAccessibilityFocus(false);}// 去检查cancel事件。//重新设置下一次cancel的标记,如果之前设置过就会返回true。final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;// // 如果需要.更新指向Down的分发目标的指针列表,final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) {//不cancel,也不拦截,接下来就会去遍历整个ViewGroup,找到事件要传递到那的那个目标子View。View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()? findChildWithAccessibilityFocus() : null;if (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {//由于事件在MotionEvent中是以静态的整型存在的,这地方是为了得到分发事件的整型然后根据这个int类型的数值查找对应的pointerIdfinal int actionIndex = ev.getActionIndex(); // always 0 for downfinal int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex): TouchTarget.ALL_POINTER_IDS;//为了保证分发目标的同步,清除掉之前这个pointerId所对应的触摸目标。removePointersFromTouchTargets(idBitsToAssign);final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {final float x = ev.getX(actionIndex);final float y = ev.getY(actionIndex);//遍历ViewGroup,将接收该事件的子View返回 final ArrayList<View> preorderedList = buildTouchDispatchChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, 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;}newTouchTarget = getTouchTarget(child);if (newTouchTarget != null) { //如果子View可以接受事件,那么我们就给他一个触摸的标识。newTouchTarget.pointerIdBits |= idBitsToAssign;break;}resetCancelNextUpFlag(child);//接下来他会通过调用dispatchTransformedTouchEvent把事件分配给子View。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 indexfor (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;}//分发目标子View没有处理该事件,清除标记,想所有的子View分发,做一个正常的事件调度ev.setTargetAccessibilityFocus(false);}if (preorderedList != null) preorderedList.clear();}if (newTouchTarget == null && mFirstTouchTarget != null) {//没有发现子View去接收该事件,将指针指向上一次分发目标ViewnewTouchTarget = mFirstTouchTarget;while (newTouchTarget.next != null) {newTouchTarget = newTouchTarget.next;}newTouchTarget.pointerIdBits |= idBitsToAssign;}}}if (mFirstTouchTarget == null) {......} else {//如果事件的分发目标已经分发过了,将不会再分发......}}// 如果需要.更新指向UP和cancel的分发目标的指针列表,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;}

注意:

(1) dispatchTransformedTouchEvent方法说明:我们会发现,他先判断状态是否取消,如果取消了,把当前事件变成取消状态,然后在判断是否有子view。如果有子view的话直接调用子view的dispatch事件。下面就是多指了,一个pointer对应一个ID,防止处理冲突。我印象中能简单粗暴的处理多指,应该是ViewDragHelper了。具体,你们可以自己去看。后面就如之前一样,判断child是否为null。然后得到是执行自身的事件还是child的事件

(2) ViewGroup静态内部类TouchTarget说明:

private static final int MAX_RECYCLED = 32;private static final Object sRecycleLock = new Object[0];//保存着当前的所有touchtarget的对象private static TouchTarget sRecycleBin;//sRecycledCount来计数private static int sRecycledCount;public static final int ALL_POINTER_IDS = -1; // all ones// The touched child view.public View child;// The combined bit mask of pointer ids for all pointers captured by the target.public int pointerIdBits;// 指向目标列表中的下一个目标public TouchTarget next; //核心方法说明:public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {if (child == null) {throw new IllegalArgumentException("child must be non-null");}final TouchTarget target;synchronized (sRecycleLock) {if (sRecycleBin == null) {target = new TouchTarget();} else {//新生成的target 的中next指向以前的以前存在的对象,就是依次往回加的感觉target = sRecycleBin;sRecycleBin = target.next;sRecycledCount--;target.next = null;}}target.child = child;target.pointerIdBits = pointerIdBits;return target;}public void recycle() {if (child == null) {throw new IllegalStateException("already recycled once");}//把链表中最上面的去掉,是谁调用recycle()的这个对象在链表被去除了synchronized (sRecycleLock) {if (sRecycledCount < MAX_RECYCLED) {next = sRecycleBin;sRecycleBin = this;sRecycledCount += 1;} else {next = null;}child = null;}}

//事件拦截的方法

public boolean onInterceptTouchEvent(MotionEvent ev) {if (ev.isFromSource(InputDevice.SOURCE_MOUSE)&& ev.getAction() == MotionEvent.ACTION_DOWN&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)&& isOnScrollbarThumb(ev.getX(), ev.getY())) {//(1)按键处理判断,主要判断输入源是否是鼠标定位设备,也可用于其他的鼠标指向设备如触摸板,小红点等//(2)判断该事件是否是Down事件//(3)检测用户是否按下按钮return true;}return false;}

ViewGroup事件分发总结:

2.3.3 View事件分发源码分析

从上面ViewGroup事件分发机制知道,View事件分发机制从dispatchTouchEvent()开始,不重要的源码已经省略

public boolean dispatchTouchEvent(MotionEvent event) {......boolean result = false;......//应用安全策略过滤触摸事件if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {//判断View是可点击的,并且是通过鼠标输入或者指向设备来处理滑动的result = true;}//最主要点,ListenerInfo是View的静态内部类,主要封装了view中所有的点击事件的接口。ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {//(1)首先先确保ListenerInfo的对象不能为null,并且判断mOnTouchListener不为null,并且view是enable的状态//(2)然后li.mOnTouchListener.onTouch(this, event)返回true,也就是说这三个条件均成立,直接返回true,后面的额onTouchEvent(event)将不再执行。result = true;}if (!result && onTouchEvent(event)) {result = true;}}......return result;}

注意

(1)在View中onTouch的执行是在onTouchEvent之前的.

(2)li.mOnTouchListener分析:

//也就是我们在Activity中设置的setOnTouchListenerpublic void setOnTouchListener(OnTouchListener l) {getListenerInfo().mOnTouchListener = l;}

如果我们在onTouch()中返回true,那么View中的onTouchEvent就不会执行,事件分发结束,如果返回false,就会来到View的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) {//View的状态为DISABLED,也就是说View仍然可以接收到触摸事件,但是不会响应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);}//如果设置了mTouchDelegate,则会将事件交给代理者处理,直接return true,如果希望自己的View增加它的touch范围,可以尝试使用TouchDelegateif (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) {return true;}}//接下来到重点了,如果我们的View可以点击或者可以长按,进入if语句if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {switch (action) {case MotionEvent.ACTION_UP://判断mPrivateFlags是否包含PREPRESSEDboolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepresed) {// 如果包含PRESSED或者PREPRESSED则进入执行体boolean focusTaken = false;if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {focusTaken = requestFocus();}if (prepressed) {setPressed(true, x, y);}//如果mHasPerformedLongPress没有被执行,进入IFif (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {//移除长按的检测removeLongPressCallback();if (!focusTaken) {//如果mPerformClick为null,初始化一个实例,然后立即通过handler添加到消息队列尾部if (mPerformClick == null) {mPerformClick = new PerformClick();}if (!post(mPerformClick)) {//如果添加失败则直接执行 performClick(),否则,将执行PerformClick的run方法中performClick();performClick();}}}if (mUnsetPressedState == null) {mUnsetPressedState = new UnsetPressedState();}//我们的mPrivateFlags中的PRESSED取消,然后刷新背景,把setPress转发下去。if (prepressed) {postDelayed(mUnsetPressedState,ViewConfiguration.getPressedStateDuration());} else if (!post(mUnsetPressedState)) {mUnsetPressedState.run();}//如果mPendingCheckForTap不为null,移除;removeTapCallback();}mIgnoreNextUpEvent = false;break;case MotionEvent.ACTION_DOWN://标记是否为长按事件mHasPerformedLongPress = false;//检查Down事件是否被消耗,true是被消耗if (performButtonActionOnTouchDown(event)) {break;}boolean isInScrollingContainer = isInScrollingContainer();//对于滚动容器内的视图, 短时间内延迟按下的反馈, 以防这是滚动if (isInScrollingContainer) {//给mPrivateFlags设置一个PREPRESSED的标识mPrivateFlags |= PFLAG_PREPRESSED;if (mPendingCheckForTap == null) {mPendingCheckForTap = new CheckForTap();}//发送一个延迟为ViewConfiguration.getTapTimeout()的延迟消息,到达延时时间后会执行CheckForTap()里面的run方法//CheckForTap会在后面说明mPendingCheckForTap.x = event.getX();mPendingCheckForTap.y = event.getY();postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());} else {setPressed(true, x, y);checkForLongClick(0, x, y);}break;case MotionEvent.ACTION_CANCEL:......break;case MotionEvent.ACTION_MOVE://拿到当前触摸的x,y坐标;drawableHotspotChanged(x, y);// 判断当然触摸点有没有移出我们的Viewif (!pointInView(x, y, mTouchSlop)) {//如果移出了,移除DOWN触发时设置的PREPRESSED的检测removeTapCallback();//然后判断是否包含PRESSED标识if ((mPrivateFlags & PFLAG_PRESSED) != 0) {//如果包含,移除长按的检查,最后把mPrivateFlags中PRESSED标识去除,刷新背景removeLongPressCallback();setPressed(false);}}break;}return true;}return false;}

performClick()方法说明

public boolean performClick() {final boolean result;final ListenerInfo li = mListenerInfo;//从这里可以看出,onClick点击事件是在performClick方法中执行的,也就是说只要我们通过setOnClickListener()为控件View注册1个点击事件那么就会给mOnClickListener变量赋值(即不为空) 则会往下回调onClick()并且performClick()返回trueif (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);result = true;} else {result = false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);return result;}

view事件分发总结:

2.4 从Demo入手理解事件分发机制

首先创建一个项目。自定义一个TouchLayout的类,让其继承自LinearLayout,实现其构造方法,以及重写相应的dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent(),监听三个方法中相应的按下,抬起和移动事件。同样的再自定义一个TouchButton的类,让其继承自AppCompatButton,实现其构造方法,以及重写相应的dispatchTouchEvent(),onTouchEvent(),监听两个方法中相应的按下,抬起和移动事件然后我们在Activity的xml文件中布局如下

<widget.TouchLayoutandroid:id="@+id/vg_b"xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="@color/a"tools:context=".byd.viewtest.TouchActivity"><widget.TouchButtonandroid:id="@+id/btn_c"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="我是C"android:background="@color/c"/><widget.TouchButton1android:id="@+id/btn_c1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="我是C1"android:background="@color/c"/></widget.TouchLayout>

说明:TouchButton1和TouchButton代码一样。这里创建TouchButton1只是为了研究事件在ViewGroup中如何遍历子View找到事件分发的目标View的。

在Activity中重写dispatchTouchEvent()和onTouchEvent()方法,监听两个方法中相应的按下,抬起和移动事件。在Activity中对btn_c设置OnTouchListener和OnClickListener监听。接下来分好几种情况分别来看。

1.事件正常分发:

我们可以看到:

首先Down事件分发至Activity的dispatchTouchEvent()—–>ViewGroup()的dispatchTouchEvent()—–>ViewGroup的onInterceptTouchEvent()—–>View的dispatchTouchEvent()—–>Activity中的View的onTouch()—–>View的onTouchEvent()然后事件被消费。后面的Move和UP都是一样的,需要注意的是UP事件在执行完View的onTouchEvent()后执行了View的onClick(),这和我们上面的源码分析一致。

2.我们在Activity中的dispatchTouchEvent中返回true和false分别看一下。

可以看到事件并没有向下分发,直接到Activity的dispatchTouchEvent()结束。

3.在ViewGroup的TouchLayout中的dispatchTouchEvent()中返回true

可以看到事件分发至ViewGroup的dispatchTouchEvent()之后就结束了。并没有向下分发。

4..在ViewGroup的TouchLayout中的dispatchTouchEvent()中返回false

这里看到事件的分发流程为:Activity的dispatchTouchEvent()—–>ViewGroup()的dispatchTouchEvent()—–>Activity的onTouchEvent(),也就是说事件最后被Activity消费了。

5.在ViewGroup的onInterceptTouchEvent()中返回true,这里有两种情况,

(1) 首先我们拦截Down事件

可以看到事件传递流程:Activity的dispatchTouchEvent()—–>ViewGroup()的dispatchTouchEvent()—–>ViewGroup的onInterceptTouchEvent()—–>ViewGroup的onTouchEvent()—–>Activity的onTouchEvent().且事件列中的后续事件并未向下分发,直接由Activity的dispatchTouchEvent()—–>Activity的onTouchEvent(),由Activity消费. 且ViewGroup中的 onInterceptTouchEvent只调用了一次,后续事件列中并未调用。

(2)不拦截down事件,拦截同一事件列中的Move事件

可以看到Down事件完整的分发完了,Move事件被拦截,但是Up事件也没有向下分发,直接传递到ViewGroup的onTouchEven(),最后回传至Activity的onTouchEven()。

6.后续的View中的状况和ViewGroup类似,这就不赘述了。

2.4 总结

通过这次探索,发现有Android的事件分发机制相当复杂,理解起来也不太容易,如果有哪些点你觉得我的理解不对。欢迎留言指正,最近开通了自己的微信公众号,偶尔更新文章,生活感悟,好笑的段子,欢迎订阅

文章中所用到的demo的下载地址

参考资料:

《Android开发艺术探索》

鸿洋- Android View 事件分发机制 源码解析

Carson_Ho-Android事件分发机制详解:史上最全面、最易懂

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。