前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android事件分发简单梳理

Android事件分发简单梳理

作者头像
大话swift
发布2020-03-31 16:16:36
4370
发布2020-03-31 16:16:36
举报
文章被收录于专栏:大话swift大话swift

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

事件

触发场景

单词事件流中触发的次数

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

下面根据实际的代码看看

代码语言:javascript
复制
//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

代码语言:javascript
复制
 /**     * 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的事件分发

代码语言:javascript
复制
   @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;    }

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

代码语言:javascript
复制
// 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的事件分发

代码语言:javascript
复制
// 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方法。

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大话swift 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档