Android: 事件分发

192 阅读5分钟

一个图就看懂了

view_dispatch_event.png

1.requestDisallowInterceptTouchEvent

  • 子控件不想让 父控件拦截事件
/**
 * Called when a child does not want this parent and its ancestors to
 * intercept touch events with
 * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
 *
 * <p>This parent should pass this call onto its parents. This parent must obey
 * this request for the duration of the touch (that is, only clear the flag
 * after this parent has received an up or a cancel.</p>
 * 
 * @param disallowIntercept True if the child does not want the parent to
 *            intercept touch events.
 */
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);

2. 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;
        }
        //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;
        }
    }

    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;
}

3.onInterceptTouchEvent

/**
 * Implement this method to intercept all touch screen motion events.  This
 * allows you to watch events as they are dispatched to your children, and
 * take ownership of the current gesture at any point.
 *
 * <p>Using this function takes some care, as it has a fairly complicated
 * interaction with {@link View#onTouchEvent(MotionEvent)
 * View.onTouchEvent(MotionEvent)}, and using it requires implementing
 * that method as well as this one in the correct way.  Events will be
 * received in the following order:
 *
 * <ol>
 * <li> You will receive the down event here.
 * <li> The down event will be handled either by a child of this view
 * group, or given to your own onTouchEvent() method to handle; this means
 * you should implement onTouchEvent() to return true, so you will
 * continue to see the rest of the gesture (instead of looking for
 * a parent view to handle it).  Also, by returning true from
 * onTouchEvent(), you will not receive any following
 * events in onInterceptTouchEvent() and all touch processing must
 * happen in onTouchEvent() like normal.
 * <li> For as long as you return false from this function, each following
 * event (up to and including the final up) will be delivered first here
 * and then to the target's onTouchEvent().
 * <li> If you return true from here, you will not receive any
 * following events: the target view will receive the same event but
 * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
 * events will be delivered to your onTouchEvent() method and no longer
 * appear here.
 * </ol>
 *
 * @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;
}

case 1: return false

  • For as long as you return false from this function, each following * event (up to and including the final up) will be delivered first here * and then to the target's onTouchEvent().

case 2: return true

  • If you return true from here, you will not receive any * following events: the target view will receive the same event but * with the action {@link MotionEvent#ACTION_CANCEL}, and all further * events will be delivered to your onTouchEvent() method and no longer * appear here.

小疑问:requestDisallowInterceptTouchEvent

1. android中,当view调用requestDisallowInterceptTouchEvent() 函数,事件是如何分发的啊?

  • 在 Android 中,当一个 View 调用 requestDisallowInterceptTouchEvent() 函数时,这个调用会影响父 View 是否要拦截(intercept)后续的触摸事件。这个函数通常用于在子 View 中控制父 View 是否要拦截触摸事件。

  • 具体来说,当一个子 View 调用 requestDisallowInterceptTouchEvent(true) 时,它告诉其父 View,不要拦截后续的触摸事件,让这些事件继续传递给子 View 处理。这样,子 View 可以自行处理这些事件,而不会被父 View 中断。

  • 事件分发的过程如下:

    1. 事件序列开始:触摸事件通常从外部的父 View 开始传递,一路传递给内部的子 View。

    2. 子 View 请求不要被拦截:当一个子 View 调用 requestDisallowInterceptTouchEvent(true) 时,它请求父 View 不要拦截后续的触摸事件。

    3. 父 View 不拦截:如果父 View 收到子 View 的请求不要拦截,那么接下来的触摸事件将直接传递给该子 View,并继续由该子 View 处理。

    4. 事件传递给子 View:这样,后续的触摸事件会直接传递给子 View 处理,父 View 不会拦截这些事件。

2.当使用requestDisallowInterceptTouchEvent(true)时,父控件的onTouchEvnet() 函数,会被调用吗?

  • 在 Android 中,当子 View 调用 requestDisallowInterceptTouchEvent(true) 时,这个调用会告诉父 View 不要拦截后续的触摸事件,但是父 View 的 onTouchEvent() 方法仍然会被调用。父 View 的 onTouchEvent() 方法用于处理触摸事件,而不是用于拦截事件。

  • 如果子 View 请求不要被父 View 拦截触摸事件,父 View 依然会接收到这些事件并尝试处理它们。即使子 View 请求父 View 不要拦截,父 View 的 onTouchEvent() 方法仍然会被调用,可以在该方法中处理触摸事件。

  • 所以,调用 requestDisallowInterceptTouchEvent(true) 只影响了父 View 是否拦截后续触摸事件,但不会影响父 View 的 onTouchEvent() 方法被调用。父 View 仍然可以处理这些事件,除非父 View 自己也调用了 requestDisallowInterceptTouchEvent(true),才会完全不处理这些事件。

    • 记住啊,上面的那个除非🤣🤣🤣🤣🤣

底层

  • INotify机制
    • INotify是Linux内核提供的一种文件系统变化通知机制。它可以为应用程序监控文件系统的变化,如文件的新建,删除等。
//创建INotify对象,并用描述符inotifyFd 描述它

int inotifyFd = inotify_init();

/*

    添加监听

    inotify_add_watch函数参数说明

     inotifyFd:上面建立的INotify对象的描述符,当监听的目录或文件发生变化时记录在INotify对象

    “/dev/input”:被监听的文件或者目录

  IN_CREATE | IN_DELETE:事件类型

综合起来下面的代码表示的意思就是当“/dev/input”下发生IN_CREATE | IN_DELETE(创建或者删除)时即把这个事件写入到INotify对象中

*/

int wd = inotify_add_watch(inotifyFd, "/dev/input", IN_CREATE|IN_DELETE )`

  • Epoll机制
    • Epoll机制简单的说就是使用一次等待来获取多个描述的可读或者可写状态。这样我们不必对每一个描述符创建独立的线程进行阻塞读取,在避免了资源浪费的同时获得较快的相应速度。

参考