专栏首页大话swiftAndroid事件分发简单梳理

Android事件分发简单梳理

在说事件分发机制之前我们先来看看都有哪些基本的事件

事件

触发场景

单词事件流中触发的次数

MotionEvent.ACTION_DOWN

在屏幕上按下

1次

MotionEvent.ACTION_UP

在屏幕上抬起

0或1次

MotionEvent.ACTION_MOVE

在屏幕上移动

0或N次

MotionEvent.ACTION_CANCEL

滑动超出空间边界时

0或1次

当我们的手指在android手机上点按或者滑动时会触发一系列的事件流,你可能触摸到的是一个Button或者是一个Layout,那么这些事件是怎么一级级传递,哪些你看到的UI是怎么知道是否要响应你的指令操作呢?

我们先看一下我们看到的App的Activity上都包含哪些UI:

从图中我们基本看到,我们App展示的一个页面大致包含如下几部分: Activity:是我们看到的整屏幕范围 View Group:View子类,同时也是View的容器,可以包含多个View View:我们常规看到的各种UI组件的基类

分发机制的核心方法 对事件分发起关键作用的有三个方法

  • public boolean dispatchTouchEvent(MotionEvent ev)
  • public boolean onTouchEvent(MotionEvent event)
  • public boolean onInterceptTouchEvent(MotionEvent ev)

虽然说是三个核心关键所在,但是也并不是Activity ViewGroup 和View都同时具备

我们先整体看一下

组件

dispatchTouchEvent

onTouchEvent

onInterceptTouchEvent

Activity

ViewGroup

View

下面根据实际的代码看看

//Activityclass MyActivity extends  AppCompatActivity{    @Override    public boolean onTouchEvent(MotionEvent event) {        return super.onTouchEvent(event);    }
    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        return super.dispatchTouchEvent(ev);    }
}//Viewclass MyView extends View{    @Override    public boolean onTouchEvent(MotionEvent event) {        return super.onTouchEvent(event);    }
    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        return super.dispatchTouchEvent(event);    }
}//ViewGroupclass MyViewGroup extends ViewGroup{    @Override    public boolean onTouchEvent(MotionEvent event) {        return super.onTouchEvent(event);    }
    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        return super.dispatchTouchEvent(ev);    }
    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return super.onInterceptTouchEvent(ev);    }}

在实际的继承关系中ViewGroup继承于View因此ViewGroup的onTouchEvent本质是从VIew继承而来的

核心代码剥离 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);    }

新版本的Android源码与老版本的发生了变化,不过本质还是一样

首先事件传递给了Activity它会分派给子View来处理(一般Activity不处理)

  • 通过PhoneWindow传递给,PhoneWindow内部传递个DecorView,最终传递给ViewGroup内部进行处理
  • 加如子View未响应处理掉事件则交于Activity的onTouchEvent来处理
    1. /**
    2. * Called when a touch screen event was not handled by any of the views
    3. * under it. This is most useful to process touch events that happen
    4. * outside of your window bounds, where there is no view to receive it.
    5. *
    6. * @param event The touch screen event being processed.
    7. *
    8. * @return Return true if you have consumed the event, false if you haven't.
    9. * The default implementation always returns false.
    10. */
    11. public boolean onTouchEvent(MotionEvent event) {
    12. if (mWindow.shouldCloseOnTouch(this, event)) {
    13. finish();
    14. return true;
    15. }
    16. return false;
    17. }

至此Activity大致完成事件的处理,也就是Activity负责将事件传递下去,一般不做处理

ViewGoup的事件分发

   @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }
        // If the event targets the accessibility focused view and this is it, start        // normal event dispatch. Maybe a descendant is what will handle the click.        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {            ev.setTargetAccessibilityFocus(false);        }
        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;
            // Handle an initial down.            if (actionMasked == MotionEvent.ACTION_DOWN) {                // Throw away all previous state when starting a new touch gesture.                // 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.                cancelAndClearTouchTargets(ev);                resetTouchState();            }
            // Check for interception.            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); // 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.                intercepted = true;            }
            // If intercepted, start normal event dispatch. Also if there is already            // a view that is handling the gesture, do normal event dispatch.            if (intercepted || mFirstTouchTarget != null) {                ev.setTargetAccessibilityFocus(false);            }
            // Check for cancelation.            final boolean canceled = resetCancelNextUpFlag(this)                    || actionMasked == MotionEvent.ACTION_CANCEL;
            // Update list of touch targets for pointer down, if needed.            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            if (!canceled && !intercepted) {
                // If the event is targeting accessibility focus we give it to the                // view that has accessibility focus and if it does not handle it                // we clear the flag and dispatch the event to all children as usual.                // We are looking up the accessibility focused host to avoid keeping                // state since these events are very rare.                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(); // always 0 for down                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)                            : TouchTarget.ALL_POINTER_IDS;
                    // Clean up earlier touch targets for this pointer id in case they                    // have become out of sync.                    removePointersFromTouchTargets(idBitsToAssign);
                    final int childrenCount = mChildrenCount;                    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.                        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 (!child.canReceivePointerEvents()                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                continue;                            }
                            newTouchTarget = getTouchTarget(child);                            if (newTouchTarget != null) {                                // Child is already receiving touch within its bounds.                                // Give it the new pointer in addition to the ones it is handling.                                newTouchTarget.pointerIdBits |= idBitsToAssign;                                break;                            }
                            resetCancelNextUpFlag(child);                            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();                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }
                            // The accessibility focus didn't handle the event, so clear                            // the flag and do a normal dispatch to all children.                            ev.setTargetAccessibilityFocus(false);                        }                        if (preorderedList != null) preorderedList.clear();                    }
                    if (newTouchTarget == null && mFirstTouchTarget != null) {                        // Did not find a child to receive the event.                        // Assign the pointer to the least recently added target.                        newTouchTarget = mFirstTouchTarget;                        while (newTouchTarget.next != null) {                            newTouchTarget = newTouchTarget.next;                        }                        newTouchTarget.pointerIdBits |= idBitsToAssign;                    }                }            }
            // Dispatch to touch targets.            if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);            } else {                // Dispatch to touch targets, excluding the new touch target if we already                // dispatched to it.  Cancel touch targets if necessary.                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;                }            }
            // Update list of touch targets for pointer up or cancel, if needed.            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;    }

按照老版本的源码可所简化为

// ViewGroup中该方法的核心部分伪代码public boolean dispatchTouchEvent(MotionEvent ev) {    if (!onInterceptTouchEvent(ev)) {        return child.dispatchTouchEvent(ev);    //不拦截,则传给子View进行分发处理    } else {        return onTouchEvent(ev);    //拦截事件,交由自身对象的onTouchEvent方法处理    }}

1 当ViewGroup 不进行拦截的话会一次遍历子View是否响应这个事件 2 加入ViewGroup拦截了此事件的话电泳自身的onTouch事件,而由于ViewGroup是View的子类因此实际此时走的是View的事件分发的处理逻辑了,进而我们来接着说View的时间分发机制

View的事件分发

// View中该方法的核心部分伪代码public boolean dispatchTouchEvent(MotionEvent ev) {    //如果该对象的监听成员变量不为空,则会调用其onTouch方法,    if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) {        return true;    //若onTouch方法返回TRUE,则表示消费了该事件,则dispachtouTouchEvent返回TRUE,让其调用者知道该事件已被消费。    }    return onTouchEvent(ev);    //若监听成员为空或onTouch没有消费该事件,则调用对象自身的onTouchEvent方法处理。}

从中我们可以看出在事件分发中 onTouch是有很高的优先级的

实际上,在View的onTouchEvent方法中,如果设置了onClickListener监听对象,则会调用其onClick方法。

本文只是村略的简易的缕缕没有太过认真的去细细的看有点粗浅,希望对大家对于事件分发的整体有个浅显的帮助

本文分享自微信公众号 - 大话swift(gh_ca2266b7cab0),作者:YuLongLi

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • UnsafePoint 指针

    从上看出数组名是对应的指针在swift中也是成立的,同时对于变量天际&即得到一个UnsafePointer

    大话swift
  • SwiftUI制作一个CardView展示

    var heading:String = "By default, both the text stack and the spacer occupy hal...

    大话swift
  • SwiftUI之List Group NavigationView ForEach

    public struct List<Selection, Content> where Selection : SelectionManager, Conte...

    大话swift
  • 监视我的手机:数据都去哪儿了?

    日常使用的手机可能比想象的更加活跃,当微信聊天、淘宝购物、抖音看视频甚至是喵的手机待机啥也不干,某些 App 都会悄悄地与服务器交换着数据。这些数据包括微信聊天...

    神奇的战士
  • Android系统层Watchdog机制源码分析

    一:为什么需要看门狗? Watchdog,初次见到这个词语是在大学的单片机书上, 谈到了看门狗定时器. 在很早以前那个单片机刚发展的时候, 单片机容易受到外界工...

    用户1269200
  • 彻底搞懂拖拽——基于鼠标事件的拖拽以及基于HTML5 API的拖拽完整实现

      一个典型的拖拽操作是这样的:用户用鼠标选中一个可拖动的(draggable)元素,移动鼠标到一个可放置的(droppable)元素,然后释放鼠标。 在操作期...

    从入门到进错门
  • Ryu的一些设计方法解读

    作为一个业余研究Ryu的软件工程师,一直惊叹于Ryu设计的优雅与简洁。一年多坚持下来,也有自己的一些收获,写出来和大家分享一下。 我们的故事从@set_ev_c...

    SDNLAB
  • 如何上手深度学习中的图像领域?有这个资源库就够了

    本页面收集了大量深度学习项目图像处理领域的代码链接。包括图像识别,图像生成,看图说话等等方向的代码,以便大家查阅使用。 图像生成 绘画风格到图片的转换:Neur...

    机器学习AI算法工程
  • 如何上手深度学习中的图像领域?有这个资源库就够了

    本页面收集了大量深度学习项目图像处理领域的代码链接。包括图像识别,图像生成,看图说话等等方向的代码,以便大家查阅使用。 图像生成 绘画风格到图片的转换:Neur...

    朱晓霞
  • TCGA数据库筛选出有生存意义的lncRNA

    长链非编码RNA(Long non-coding RNA), lncRNA是一类本身不编码蛋白、转录本长度超过200nt的长链非编码RNA分子,它可在多层面上(...

    用户1359560

扫码关注云+社区

领取腾讯云代金券