Android 进阶——Framework 核心之Touch事件分发机制详细攻略(一)

news2025/1/19 3:23:40

文章大纲

  • 引言
  • 一、Android 事件分发概述
  • 二、Touch事件分发的对象MotionEvent
  • 三、分发事件的核心对象及方法
    • 1、分发事件的核心对象
    • 2、事件分发的核心方法
  • 四、Touch事件分发的流程
    • 1、Activity的事件处理流程
    • 2、ViewGroup&View的事件分发流程
  • 五、事件分发的应用
    • 1、事件拦截
    • 2、响应点击事件

引言

Android 事件详细总结。

该文章基于android-28,仅分析Framework java层代码逻辑,仅供参考,不敢保证百分百正确无误。

一、Android 事件分发概述

一般说来当用户触摸屏幕或者按键操作,首次触发的是底层硬件驱动,驱动收到事件后并将该相应事件写入到输入设备节点, 便产生最原生态的内核事件。接着,输入(Input)系统取出原生态的事件再经过层层封装后成为KeyEvent或者MotionEvent ,最后交付给相应的目标窗口(Window)来消费该输入事件。其中Input模块的主要组成有:

  • Native层的InputReader负责从EventHub取出事件并处理,再交给InputDispatcher
  • Native层的InputDispatcher接收来自InputReader的输入事件,并记录WMS(WindowManagerService)的窗口信息,用于派发事件到合适的窗口
  • Java层的InputManagerService跟WMS交互,WMS记录所有窗口信息,并同步更新到IMS,为InputDispatcher正确派发事件到ViewRootImpl提供保障。

简而言之,Android 中事件传递按照从上到下进行层级传递,事件处理从 Activity 开始到 ViewGroup 再到 View。事件传递方法包括dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent,其中前两个是 View 和 ViewGroup 都有的,最后一个是只有 ViewGroup 才有的方法。其中onTouch 方法要先于 onClick 触发执行,onTouch 在 dispatchTouchEvent 中被调用,而 onClick 在 onTouchEvent 方法中被调用,因此onTouchEvent 要后于 dispatchTouchEvent 方法的调用。
在这里插入图片描述

二、Touch事件分发的对象MotionEvent

MotionEvent 继承自InputEvent并实现Parcelable接口,作为事件分发机制的核心对象,在Android中当用户触摸屏幕上的View 或 ViewGroup(及其子View),将产生点击事件(Touch事件)并根据Touch事件的发生时触摸的位置、时间、类型等相关细节封装不同类型的MotionEvent对象进行传递,该对象用于记录所有与移动相关的事件信息(事件分发和处理由Activity去调用native层完成),比较典型的有以上四种:

事件ACTION触发场景单次事件流中触发的次数
MotionEvent.ACTION_DOWN在屏幕被按下时(所有事件的源头)1次
MotionEvent.ACTION_MOVE在屏幕上进行滑动时0次或者多次
MotionEvent.ACTION_UP从屏幕上抬起时0次或者1次
MotionEvent.ACTION_CANCEL结束事件(非人为原因)0或者1次

所以一次完整的MotionEvent事件,是从用户触摸屏幕到离开屏幕。整个过程的动作序列:ACTION_DOWN(1次) —> ACTION_MOVE(N次) -> ACTION_UP(1次)

而对于多点触摸,每一个触摸点Pointer会有一个id和index。对于多指操作,通过pointerindex来获取指定Pointer的触屏位置。比如单点操作时通过getX()获取x坐标,而多点操作时通过getX(pointerindex)获取x坐标。

MotionEvent 还有很多的ACTION类型,MotionEvent需要继承native 层InputEvent(其实是内核层的Input机制),因为Android Framework 通过JNI 进行处理,同时需要跨进程因而MotionEvent实现了Parcelable序列化接口。

三、分发事件的核心对象及方法

1、分发事件的核心对象

  • Activity——作为Android四大基本组件之一,当手指触摸到屏幕时,屏幕硬件逐行不断地扫描每个像素点,获取到触摸事件后,从底层产生中断上报。再通过native层调用Java层InputEventReceiver中的dispatchInputEvent方法。最后经过层层调用交由Activity的dispatchTouchEvent方法来处理。

  • View——作为所有视图对象的父类,实现了Drawable.Callback(动画相关的接口)、KeyEvent.Callback(按键相关的接口)和AccessibilityEventSource(交互相关的接口)。

  • ViewGroup——ViewGroup是一个继承了View并实现了ViewParent(用于与父视图交互的接口), ViewManager(用于添加、删除、更新子视图到Activity的接口)的抽象类,作为盛放其他View的容器,可以包含View和ViewGroup,是所有布局的父类或间接父类。

2、事件分发的核心方法

Touch事件都源自按下屏幕里Activity中的View或者ViewGroup(及其子View),所以事件的处理都是由Activity、View或ViewGroup对象完成的,换言之,只在Activity、View或ViewGroup里拥有处理事件系列方法如下表所示:
在这里插入图片描述

从源码中可以得知在Activity、 ViewGroup 和View中都存在 dispatchTouchEvent 和 onTouchEvent 方法(但是在 ViewGroup 中还有一个 onInterceptTouchEvent 方法),他们都接受了一个MotionEvent类型的参数用于标记各种动作事件且返回值都是boolean型,true则代表不往下传递,false则表示继续往下传递,那么这些方法有何功能的呢?

  • boolean dispatchTouchEvent(MotionEvent event)——负责Touch事件的分发,Android 中所有的事件都必须经过此方法的分发,然后决定是自身消费当前事件还是继续往下分发给子View返回 true 则表示不继续分发,反之返回 false 则继续往下分发;而如果ViewGroup 则是先分发给 onInterceptTouchEvent 进行判断是否拦截该事件

  • boolean onTouchEvent(MotionEvent event)—— 负责Touch事件的处理,返回 true 则表示消费当前事件,反之返回 false 则不处理交给子View继续进行分发。

  • boolean onInterceptTouchEvent(MotionEvent event)—— 负责Touch事件的拦截 返回 true 则表示拦截当前事件就不继续往下分发,交给自身的 onTouchEvent 进行处理;而返回 false 则不拦截,继续往下传。这是 ViewGroup 特有的方法,因为 ViewGroup 作为容器可以存放其他子 View,而View 则不能,换言之,只有 ViewGroup才有拦截事件的能力

事件分发的核心流程都是围绕这些方法进行的,由不同的对象直接或者间接调用进行处理。

四、Touch事件分发的流程

事件分发有多种类型, 以下是Touch相关的事件分发大致流程:
Activity——>Window(PhoneWindow)——>DecorView——>ViewGroup——>View

1、Activity的事件处理流程

在这里插入图片描述
事件的分发是由Activity开始的,当用户点击屏幕的时候首先接触到的就是Activity,从而触发Activity#dispatchTouchEvent方法,

    /**
     * android.app.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) {
		//如果是DOWN类型则说明是一个全新的事件
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
			//默认为空实现,可以被重写,如果你想要知道用户已经与Activity进行交互
            onUserInteraction();
        }
		//调用Window的dispatchTouchEvent方法,并把事件分发至PhoneWindow(获取当前Activity的顶层窗口是PhoneWindow),返回false表示当前事件没有被消费。
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //如果PhoneWindow消费了事件则把事件分发至Activity的onTouchEvent
        return onTouchEvent(ev);
    }

Activity#dispatchTouchEvent返回结果之前,会先分发到ViewGroup&View,而最终的执行分为两个分支(抛开中间细节):

  • getWindow().superDispatchTouchEvent(ev)返回flase时,代表当前事件没有被Activity中的任何View或者ViewGroup处理(即DecorView #dispatchTouchEvent方法返回false),则此次事件最终是由Activity#onTouchEvent 去处理的。
    /**
     *	android.app.Activity#onTouchEvent
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     * @param event The touch screen event being processed.
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
    public boolean onTouchEvent(MotionEvent event) {
	    //判断是否屏幕出界,比如说点击到屏幕的非有效区域时,当窗口需要关闭时,消费掉当前event
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }
  • getWindow().superDispatchTouchEvent(ev)返回true时,代表事件已被Activity中的View或者ViewGroup消费了。

再回到分发流程,Activity#dispatchTouchEvent执行后,接着会通过getWindow()方法得到当前Activity的顶层窗口(即PhoneWindow)并调用它的superDispatchTouchEvent方法把事件分发到PhoneWindow,执行PhoneWindow#superDispatchTouchEvent方法传入到DecorView(根ViewGroup)…

DecorView继承自 FrameLayout 并实现了RootViewSurfaceTaker和WindowCallbacks接口,是当前界面的最外(顶)层容器,即setContentView方法所设置的View的父容器根ViewGroup。

//com.android.internal.policy.PhoneWindow#superDispatchTouchEvent
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
	//分发至DecorView
    return mDecor.superDispatchTouchEvent(event);
}

分发到DecorView,再通过DecorView#superDispatchTouchEvent方法

//com.android.internal.policy.DecorView#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
   return super.dispatchTouchEvent(event);//实际上调用的是ViewGroup#dispatchTouchEvent
}

接下来分发到了ViewGroup中,ViewGroup的分发流程比较复杂,篇幅有限会省略部分逻辑。

2、ViewGroup&View的事件分发流程

在这里插入图片描述
ViewGroup&View的事件分发流程的起点是从PhoneWindow#superDispatchTouchEvent开始的,首先分发至根ViewGroup(DecorView)并执行ViewGroup#dispatchTouchEvent方法。

//android.view.ViewGroup#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
    //辅助功能跨进程调用,是当前应用与系统层进行交互的,模拟的点击事件
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }
	...
	//是否消费了当前事件的标识,最终的返回值
    boolean handled = false;
    //根据隐私策略而来决定是否过滤本次触摸事件,相当于是检测当前事件,功能类似检测操作UI时是否在主线程中
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 发生ACTION_DOWN事件, 则取消并清除之前所有的触摸targets,可能是由于App切换、ANR或者其他原因抛弃了上一次的UP或者CANCEL,需要取消并移除所有目标
            cancelAndClearTouchTargets(ev);
            resetTouchState(); // 重置触摸状态为下一次循环做准备
        }

        // 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN;才进入此区域,主要功能是拦截器
        //第一次触摸屏幕时是DOWN,只有发生过ACTION_DOWN事件,则mFirstTouchTarget != null;
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            //可以通过在其子View中去调用父ViewGroup的requestDisallowInterceptTouchEvent方法设置是否允许拦截,让父View是否拦截事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //判断是否允许调用拦截器
            if (!disallowIntercept) {
                //!!调用拦截逻辑的方法!!
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            // 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
            intercepted = true;
        }
        ...

        //不取消事件,同时不拦截事件, 并且是Down事件时
        if (!canceled && !intercepted) {
            //把事件分发给所有的子视图,寻找可以获取焦点的视图。
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex(); // down事件等于0
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                removePointersFromTouchTargets(idBitsToAssign); //清空早先的触摸对象

                final int childrenCount = mChildrenCount;
                //第一次down事件,同时子视图不会空时
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
					// Find a child that can receive the event. Scan children from front to back.
					//创建可以接收事件的子View,并按照z轴值对子View进行排序,因为子View可能存在相互重叠的部分,从顶层到底层排序
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
					//当前ViewGroup 中所有的子View数组
                    final View[] children = mChildren;

                    /* 倒序遍历,从底层到顶层,从最底层的父视图开始遍历, ** 找寻newTouchTarget,并赋予view与 pointerIdBits; ** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。 */
                    for (int i = childrenCount - 1; i >= 0; i--) {
                         final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
						//获取当前ViewGroup中的子View
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // 如果当前视图无法获取用户焦点,则跳过本次循环
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }
                        //如果view不可见或正在执行动画,或者触摸的坐标点不在view的范围内,则跳过本次循环
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
						//获取当前的View的包装对象
                        newTouchTarget = getTouchTarget(child);
                        // 已经开始接收触摸事件,并退出整个循环。
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        //重置取消或抬起标志位
						resetCancelNextUpFlag(child);
                        //如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup,开始事件分发,child为null时传递到View#dispatchTouchEvent,而child不为null时则调用child自身的dispatchTouchEvent
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // [见小节2.4.4]
                            // 获取TouchDown的时间点
                            mLastTouchDownTime = ev.getDownTime();
                            // 获取TouchDown的Index
                            if (preorderedList != null) {
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }

                            //获取TouchDown的x,y坐标
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            //添加TouchTarget,则mFirstTouchTarget != null,给mFirstTouchTarget赋值
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            //表示以及分发给NewTouchTarget
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ev.setTargetAccessibilityFocus(false);
                    }
                    // 清除视图列表
                    if (preorderedList != null) preorderedList.clear();
                }

                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    //将mFirstTouchTarget的链表最后的touchTarget赋给newTouchTarget
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }///#endif  if (!canceled && !intercepted)
        
		//ViewGroup进行事件拦截之后直接越过前面的逻辑,执行到这里,mFirstTouchTarget赋值是在通过addTouchTarget方法获取的;
        // 只有处理ACTION_DOWN事件,才会进入addTouchTarget方法。这也正是当View没有消费ACTION_DOWN事件,则不会接收其他MOVE,UP等事件的原
        if (mFirstTouchTarget == null) {
            //没有触摸target,则由当前ViewGroup来处理
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            //如果View消费ACTION_DOWN事件,那么MOVE,UP等事件相继开始执行
            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;
            }
        }

        //当发生抬起或取消事件,更新触摸targets
        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 (onFilterTouchEventForSecurity(ev))的结尾

    //通知verifier由于当前时间未处理,那么该事件其余的都将被忽略
    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

在ViewGroup每次调用ViewGroup#dispatchTouchEvent方法时,当requestDisallowInterceptTouchEvent方法返回true时则说明允许事件拦截,就会先执行ViewGroup#onInterceptTouchEvent判断是否进行事件拦截,

    /**
     * Implement this method to intercept all touch screen motion events. 
     *
     * @param ev The motion event being dispatched down the hierarchy.
     * @return Return true to steal motion events from the children and have
     * them dispatched to this ViewGroup through onTouchEvent().
     * The current target will receive an ACTION_CANCEL event, and no further
     * messages will be delivered here.
     */
    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())) {
            return true;
        }
        return false;
    }

返回true则进行拦截,就会越过 if (!canceled && !intercepted)分支,直接执行ViewGroup#dispatchTransformedTouchEvent方法

/**
 * Transforms a motion event into the coordinate space of a particular child view,
 * filters out irrelevant pointer ids, and overrides its action if necessary.
 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
* ViewGroup 真正分发事件的逻辑
* @param child null则调用View#dispatchTouchEvent;反之则调用child里的dispatchTouchEvent方法
 */
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;
    }

    // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    // If for some reason we ended up in an inconsistent state where it looks like we
    // might produce a motion event with no pointers in it, then drop the event.
    if (newPointerIdBits == 0) {
        return false;
    }

    // If the number of pointers is the same and we don't need to perform any fancy
    // irreversible transformations, then we can reuse the motion event for this
    // dispatch as long as we are careful to revert any changes we make.
    // Otherwise we need to make a copy.
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);

                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }

    // Perform any necessary transformations and dispatch.
    if (child == null) {
// 第一次触发Touch事件时执行到这里,去调用父类的dispatchTouchEvent
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    // Done.
    transformedEvent.recycle();
    return handled;
}

其核心逻辑取决于传入的child,当child =null则调用View#dispatchTouchEvent;反之则调用child自身(有可能是View或ViewGroup)的dispatchTouchEvent方法,假设调用的是View#dispatchTouchEvent方法

  /**
     * Pass the touch screen motion event down to the target view, or this view if it is the target.
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }
		//是否消费此事件标识,最终的返回值
        boolean result = false;
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //ListenerInfo(可从View#getListenerInfo方法获取)为所有View的事件监听封装类,包含了所有常见事件的监听接口
            ListenerInfo li = mListenerInfo;
			//如果外部调用了setOnTouchListener方法时,会先执行OnTouchListener#onTouch,当onTouch返回true时则不会执行View#onTouchEvent方法
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
			
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture; also cancel it if we tried an ACTION_DOWN but we didn't want the rest of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

如果外部调用了setOnTouchListener方法时设置onTouchListener监听时,会先执行View$$OnTouchListener#onTouch,当OnTouchListener#onTouch返回true时则不会执行View#onTouchEvent方法;反之还会去调用View#onTouchEvent方法

/**
 * Implement this method to handle touch screen motion events.
 *
 * @param event The motion event.
 * @return True if the event was handled, false otherwise.
 */
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
// 是否可点击的标志
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return clickable;
    }
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    handleTooltipUp();
                }
                if (!clickable) {
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                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, x, y);
                    }

                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // 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) {
					//PerformClick实现了Runnable接口
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
					//在该方法内处理Click事件触发OnClickListener.onClick方法
                                performClickInternal();
                            }
                        }
                    }

                    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();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                mHasPerformedLongPress = false;

                if (!clickable) {
                    checkForLongClick(0, x, y);
                    break;
                }

                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();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);
		//处理OnLongClickListener.onLongClick方法
                    checkForLongClick(0, x, y);
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;

            case MotionEvent.ACTION_MOVE:
                if (clickable) {
                    drawableHotspotChanged(x, y);
                }

                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    // Remove any future long press/tap checks
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                }
                break;
        }
        return true;
    }
    return false;
}

所以基本就是在View#onTouchEvent方法的处理onClickListener

private final class PerformClick implements Runnable {
    @Override
    public void run() {
        performClickInternal();
    }
}

public boolean performClick() {
   // We still need to call this method to handle the cases where performClick() was called
   // externally, instead of through performClickInternal()
   notifyAutofillManagerOnClick();
   final boolean result;
   final ListenerInfo li = mListenerInfo;
   if (li != null && li.mOnClickListener != null) {
       playSoundEffect(SoundEffectConstants.CLICK);
       //处理OnClickListener接口
       li.mOnClickListener.onClick(this);
       result = true;
   } else {
       result = false;
   }
   sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
   notifyEnterOrExitForAutoFillIfNeeded(true);
   return result;
}

onLongClickListener等其他监听接口也类似。
在这里插入图片描述

五、事件分发的应用

1、事件拦截

拦截事件可以通过两种形式:

  • 继承ViewGroup或其子类重写onInterceptTouchEvent方法并返回true

  • 子View中调用父ViewGroup的requestDisallowInterceptTouchEvent方法并返回true

2、响应点击事件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/106310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

刨根问底Kafka 40问,看看你能坚持到第几问

Kafka最初是由Linkedin公司开发的&#xff0c;是一个分布式的、可扩展的、容错的、支持分区的&#xff08;Partition&#xff09;、多副本的&#xff08;replica&#xff09;、基于Zookeeper框架的发布-订阅消息系统&#xff0c;Kafka适合离线和在线消息消费。它是分布式应用系…

【JavaSE】内部类

目录 前言&#xff1a; 内部类 什么是内部类&#xff1f; 内部类的分类 1、静态内部类 1、静态内部类&#xff0c;不能直接被实例化 2、 静态内部类当中&#xff0c;不能直接访问外部类的非静态成员&#xff0c;但是可以直接访问外部类的静态成员。 3、外部类可以访问静…

java -数据结构 - 集合框架及背后的数据结构

什么是集合框架 数据结构&#xff0c;我们知道数据结构就是组织数据的一种方式&#xff0c;比如所链表&#xff0c;就是将数据存储在链表的value域中&#xff0c;next域就是存储下一个节点的地址&#xff0c;通过这样的方式将数据组织起来。 在我们学习中&#xff0c;所知道 的…

接口幂等实现基于注解(适用于分布式系统中支持spEl动态过滤)

在网络卡顿时,容易出现在极短的时间内产生重复请求,或重复支付,一般我们会在数据操作时先通过数据查询是否存在,然后再进行业务逻辑操作的方式来进行避免,但是这种方式并不是原子性,很容易出现第一次请求未进行落表,第二次重复的请求就已经通过了数据库查询,可通过设置唯一索引…

用于开发语音 AI 应用程序的 GPU 加速 SDK

NVIDIA Riva 简介&#xff1a;用于开发语音 AI 应用程序的 GPU 加速 SDK 语音 AI 用于多种应用&#xff0c;包括联络中心的座席助理以增强人类座席的能力、智能虚拟助理 (IVA) 的语音界面以及视频会议中的实时字幕。 为了支持这些功能&#xff0c;语音 AI 技术包括自动语音识别…

基于springboot的社区团购管理系统的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

FineReport企业报表工具-JS根据条件显示参数控件

1. 概述 1.1 版本 报表服务器版本 功能变更 11.0 -- 1.2 问题描述 在使用参数控件时&#xff0c;有时我们希望部分参数控件在没满足条件时不显示&#xff0c;满足条件后再显示&#xff0c;如下图效果&#xff0c;只有前面的下拉框选择了内容之后&#xff0c;后一层下拉框控…

【C语言】重要函数qsort函数的用法

目录 一、qsort函数的介绍 1、整形数组 2、字符数组 3、字符串 4、结构体 二、qsort函数的使用 一、qsort函数的介绍 qsort函数是一种底层快速排序的函数&#xff0c;它的特点就是可以排序任意类型的数据&#xff0c;比如&#xff1a;整形、字符串、浮点型、以及结构体类型。 …

VUE3-组件之间传值《四》

目录 一.父传子&#xff0c;父组件向子组件传值 二.子传父&#xff0c;子组件向父组件传值 三.兄弟组件之间互传&#xff0c;2个组件之间是平级关系&#xff0c;互相传值 组件之间的传值&#xff0c;分为3种方式 一.父传子&#xff0c;父组件向子组件传值 1.建立一个默认的…

零信任深入理解--概念,架构和实现方案

1. 零信任究竟解决了什么问题&#xff1f; 很多人在初步了解“零信任”概念&#xff0c;乃至进一步研究一些技术和产品方案之后&#xff0c;会有种“不过如此”的感觉。 毕竟&#xff0c; IAM&#xff08;Identity and Access Management&#xff0c;身份识别与访问管理&…

6. 【gRPC系列学习】Balance原理详解

本节开始分析Balance执行原理,通过上面流程可知,Balance Build的创建来自于Resolver的解析完成后调用updateResolverState方法,我们从updateResolverState方法开始分析Balance执行流程。 1. Balance调用流程 1)updateResolverState(s resolver.State, err error) error 方…

Java培训MySQL之全局序列

1.本地文件 不推荐&#xff0c;存在宕机序列丢失问题。 2.数据库方式 数据库序列方式原理 利用数据库一个表 来进行计数累加。但是并不是每次生成序列都读写数据库&#xff0c;这样效率太低。 mycat会预加载一部分号段到mycat的内存中&#xff0c;这样大部分读写序列都是在…

第二章:Linux的目录结构-[基础篇]

一&#xff1a;基础介绍 linux的文件系统是采用级层式的数状目录结构&#xff0c;在此结构中的最上层是根目录“/”&#xff0c;然后在此目录下再创建其他的目录。 深刻理解linux树状文件目录是非常重要的&#xff0c;这里我给大家说明一下。 记住一句经典的话&#xff1a;在Li…

通信原理循环码

目录 码多项式 码多项式的按模运算 循环码的码多项式 循环码的生成矩阵 如何寻求任一循环码循环码的生成多项式​ 码多项式 一个长度为的码组可表示成如下多项式形式&#xff1a; 多项式的系数就是码组中的各码元&#xff0c;仅是码元位置标记 。 n7 时&#xff1a; 例&…

Activiti任务的处理以及进阶使用

1.什么是流程实例 流程实例&#xff08;ProcessInstance&#xff09;代表流程定义的执行实例 一个流程实例包括所有的运行节点Task&#xff0c;所以我们一般使用来了解当前流程的进度信息 taskService.createTaskQuery().processDefinitionKey(key)例如&#xff1a;用户或者程…

力扣11.盛最多水的容器(双指针解法)

问题描述: 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容…

Mini MP3 Player播放器简介与STC12例程

文章目录1、DFRobot arduino DFPlayer官方资料1-1、简介2-2、 基本功能详述1-3、 用途1-4、 引脚说明1-5、工作模式1-5-1、 串口工作模式1-5-2、 ADC按键工作模式1-5-3、 普通按键工作模式2、串口模式电路搭建与例程参考文献1、DFRobot arduino DFPlayer官方资料 1-1、简介 Th…

KingbaseES运维案例之---服务进程(backend process)终止

​ 案例说明&#xff1a; 如下图所示&#xff1a;KingbaseES服务进程结构 KingbaseES使用客户端/服务器的模型。 对于每个客户端的连接&#xff0c;KingbaseES主进程接收到客户端连接后&#xff0c;会为其创建一个新的服务进程。 KingbaseES 用服务进程来处理连接到数据库服务的…

Java Swing JTextField:单行文本框组件

Swing 中使用 JTextField 类实现一个单行文本框&#xff0c;它允许用户输入单行的文本信息。该类的常用构造方法如下。 JTextField()&#xff1a;创建一个默认的文本框。JTextField(String text)&#xff1a;创建一个指定初始化文本信息的文本框。JTextField(int columns)&…

Nacos学习笔记 (4)Nacos整合SpringBoot流程

前提&#xff0c;先下载Nacos并启动 Nacos Server。 1. Nacos 融合 Spring Boot 为注册配置中心 实现&#xff1a; 通过 Nacos Server 和 nacos-config-spring-boot-starter 实现配置的动态变更&#xff1b;通过 Nacos Server 和 nacos-discovery-spring-boot-starter 实现服…