Android事件分发机制示例分析

  • Android事件类型
public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return true;
    }

如上图所示android主要有四种事件类型,Down、Move、Up、Cancel。

在一次事件流中:

Down触发一次,手指在屏幕按下时。

Move事件会触发0次或多次,手指在屏幕上滑动时。

Up事件会触发0次或一次,手指在屏幕抬起时。

Cancel事件会触发0次或一次,滑动超出空间边界时。

  • android App层事件分发机制调用链是怎么样的?

App层事件分发的入口在Activity中,dispatchTouchEvent是在Framework层的WindowManagerService掉用的,WMS中的事件是来源于Native层,Linux层,这块的事件传递有机会在写文章进行分析,这篇文章主要分析Android事件分发机制在App成是如何展现的。

 public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

getWindow()返回的是PhoneWindow对象。

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

mDecor是DecorView,是PhoneWindow的一个内部类,继承FrameLayout。

FrameLayout继承自ViewGroup,接下来看事件传递在ViewGroup中是如何处理的。事件传递的主要逻辑就在ViewGroup中。

接下来看ViewGroup,dispatchToucheEvent(MotionEvent ev)是如何对事件进行分发的。

public class ViewGroup {
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
        // Check for interception.
        //按下后,第一次调用时actionMasked=down,mFirstTouchTarget=null
        //一直按着 actionMasked=move, 如果mFirstTouchTarget!=null说明有子view处理了touch事件
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                //调用子类复写的方法 如果为true表示拦截,false表示不拦截
                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.
            //如果没有子view处理touch事件,并且不是第一次按下的down事件,group会对touch事件进行拦截
            intercepted = true;
        }
        TouchTarget newTouchTarget = null;
        //第一次按下时,判断view是否处理事件的标志
        boolean alreadyDispatchedToNewTouchTarget = false;
        //如果不拦截,则进入事件分发
        if (!canceled && !intercepted) {
            //注意,只有ACTION_DOWN时才会遍历子View
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final View[] children = mChildren;
                    //开始遍历子view
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
                        //isTransformedTouchPointInView 通过比对坐标,来判断事件是否落在了child上,
                        // true是,false不是
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            // 如果事件没有落在child上,则跳过此次循环,执行下一次循环
                            continue;
                        }
                        //通过遍历TouchTarget链表查找TouchTarget,如果找到了,则break跳出循环
                        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;
                        }
                        //第一次进来newTouchTarget==null,进入下面的逻辑
                        //如果dispatchTransformedTouchEvent 返回true,表示子view对事件进行了处理,则跳出此循环,
                        // 不会再向子view进行分发
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // Child wants to receive touch within its bounds.
                            //将child缓存到TouchTarget的链表中
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            //注意这个标志位,true,下面的逻辑会用到
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                    }
                    if (preorderedList != null) preorderedList.clear();
                }
            }
        }
        // Dispatch to touch targets.
        //ACTION_DOWN和ACTION_MOVE 的时都会执行下面逻辑
        //mFirstTouchTarget==null 说明没有子View处理touch事件,dispatchTransformedTouchEvent的第三个参数为null。
        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 链表进行遍历,TouchTarget里缓存了处理事件的view,
            //一次完整事件,move会调用多次,所以在ACTION_DOWN时找到的对应的view,缓存起来,
            // 而不是每次都遍历ViewGroup的子View,这样太损坏性能
            TouchTarget target = mFirstTouchTarget;
            //开始对链表进行遍历,为啥是一个链表呢,这是为了进行多点触摸,会有多个view响应触摸事件。
            while (target != null) {
                final TouchTarget next = target.next;
                //alreadyDispatchedToNewTouchTarget ==true ,并且缓存的target和newTouchTarget是同一个
                //直接将handled置为true,防止ACTION_DOWN时dispatchTransformedTouchEvent方法重复调用
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                }
                target = next;
            }
        }
        //将handled 返回dispatchTouchEvent的调用者。
        return handled;
    }
    /**
     * Gets the touch target for specified child view.
     * Returns null if not found.
     * 通过遍历TouchTarget 链表,找到touch target
     */
    private TouchTarget getTouchTarget(@NonNull View child) {
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            if (target.child == child) {
                return target;
            }
        }
        return null;
    }
    /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     * 将TouchTarget和view进行关联,并且把这个TouchTarget放在链表的头部
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                                  View child, int desiredPointerIdBits) {
        final boolean handled;
        // 只看最关键的两行代码
        if (child == null) {
            //如果child为null,没有子view处理touch事件,则调用父类View的dispatchTouchEvent。
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            //如果child不为空,则调用child view 的dispatchTouchEvent
            //这行代码很关键,如果child是ViewGroup则继续在ViewGoup的进行事件分发,这会是一个递归调用
            //如果child 是一个View,则调用View的dispatchTouchEvent,进行事件的分发。
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        //以上两行代码最终会调用到View的dispatchTouchEvent方法。
        return handled;
    }
}

接下来看View dispatchTouchEvent如何处理的

public class View {
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean result = false;
        ListenerInfo li = mListenerInfo;
        //如果view设置了mOnTouchListener,先调用mOnTouchListener.onTouch,
        //如果返回true,则表示消费了此事件,不在向下传递,为啥这样说?那是因为,通过他的返回值,进行了逻辑判断
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //如果result为false,则调用View.onTouchEvent方法
        if (!result && onTouchEvent(event)) {
            result = true;
        }
        return result;
    }
    public boolean onTouchEvent(MotionEvent event) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                if (mPerformClick == null) {
                    mPerformClick = new PerformClick();
                }
                if (!post(mPerformClick)) {
                    //调用performClick mOnClickListener.onClick()方法
                    performClickInternal();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
        }
        return false;
    }
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        return result;
    }
}

如果仔细看了ViewGroup和View事件分发逻辑代码。我们可以总结为以下几点。

1.ViewGroup接收到DispatchTouchEvent调用后,先调用了onInterceptTouchEvent,通过返回值进行判断是否对事件进行拦截。我们可以在代码中复写这个方法来进行事件的拦截。

2.down事件后,ViewGoup会遍历子View,找到处理Touch事件的View,并且缓存到了TouchTarget链表中,一次事件流中,只有down的时候才会进行子View的遍历。

只有做的好处提升了性能,缓存的链表中也是为了多点触摸,会有多个view响应触摸事件。

move事件时,则会从这个链表中进行遍历,通过调用dispatchTransformedTouchEvent方法进行事件的传递。

  • 在dispatchTransformedTouchEvent方法中,根据child的值是否为null进入不同的分发流程。

1)如果为null说明没有子view没有响应事件,则调用父类View的dispatchTouchEvent--->onTouchEvent方法。

2)如果child值不为null。则调用child的dispatchTouchEvent方法,

此时如果child是一个ViewGoup则还是执行ViewGoup中的分发逻辑,进行递归调用。

如果child是一个View,则调用View的dispatchTouchEvent--->onTouchEvent方法。

  • 在View的dispatchTouchEvent方法中:

1)listener的onTouch优先级要高于View的onTouchEvent

2)会先调用mOnTouchListener.onTouch方法,并根据返回值进行判断是否消费事件。

3)如果上面的返回值为false,则会调用View的onTouchEvent方法。

5.在View的onTouchEvent方法中UP事件时,才会调用View设置的onClickListener的监听事件。

这就是为什么了,同时给一个View设置了onTouch监听和lonClickListener监听时,当onTouch返回true无法调用onClick事件的原因。

到此这篇关于Android事件分发机制示例分析的文章就介绍到这了,更多相关Android事件分发内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • android事件分发机制的实现原理

    android中的事件处理,以及解决滑动冲突问题都离不开事件分发机制,android中的事件流,即MotionEvent都会经历一个从分发,拦截到处理的一个过程.即dispatchTouchEvent(),onInterceptEvent()到onTouchEvent()的一个过程,在dispatchTouchEvent()负责了事件的分发过程,在dispatchTouchEvent()中会调用onInterceptEvent()与onTouchEvent(),如果onInterceptEven

  • 聊聊Android中的事件分发机制

    View事件分发机制的本质就是就是MotionEvent事件的分发过程,即MotionEvent产生后是怎样在View之间传递及处理的. 首先介绍一下什么是MotionEvent.所谓MotionEvent,即用户手指触碰手机屏幕时产生的一系列触摸事件.典型的触摸事件有: ACTION_DOWN:手指刚接触屏幕的一瞬间. ACTION_MOVE:手指在屏幕上滑动. ACTION_UP:手指离开屏幕的一瞬间. ACTION_CANCLE:当前事件序列终止. 一个事件序列一般都是以DOWN事件开始,

  • Android View的事件分发机制

    一.Android View框架提供了3个对事件的主要操作概念. 1.事件的分发机制,dispatchTouchEvent.主要是parent根据触摸事件的产生位置,以及child是否愿意负责处理该系列事件等状态,向其child分发事件的机制. 2.事件的拦截机制,onInterceptTouchEvent.主要是parent根据它内部的状态.或者child的状态,来把事件拦截下来,阻止其进一步传递到child的机制. 3.事件的处理机制,onTouchEvent.主要是事件序列的接受者(可以是

  • 解析Android点击事件分发机制

    开头说说初衷 网上关于点击事件分发的文章一搜一大堆,标题一看,不是"30分钟让你弄明白XXX"就是"这是讲解XXX最好的文章",满怀憧憬与信心,忍不住兴奋的点进去一看,发现不是代码就全是图,我基本上看完了所有相关的文章,结果硬是看了三个小时也没搞懂.所以最后还是决定自己去试一试,看一看点击事件分发到底是怎么个流程,我写的肯定不会比其他文章好多少,但是呢,带着一个初学者的心,去分析这个东西,自己能弄明白的同时,也让想学习这个的人看了之后有些许收获,那就足够了. 运行的

  • Android之事件分发机制与冲突详解

    在日常的开发过程中,我们往往会在同一个界面中出现内外两层或者多层View同时滑动的现象,这个时候往往**会出现滑动冲突.面对滑动冲突,很多开发人员不知道从哪里入手,**即便稍微有点思路,也要费点时间才能解决.其实滑动冲突的解决是有一定的套路的. 下面主要针对Android开发中几种常见的滑动冲突及对应的解决方案来进行讲解 常见的滑动冲突场景! 目前常见的滑动冲突主要可以分为以下三种来概括: 外部滑动方向和内部View的滑动方向不一致,内部的View和外部的View的滑动方向是**互相垂直的,**

  • Android事件分发机制的详解

    Android事件分发机制 我们只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL.一个手势(gesture)是一个事件列,以一个DOWN事件开始(当用户触摸屏幕时产生),后跟0个或多个MOVE事件(当用户四处移动手指时产生),最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生).当我们说到"手势剩余部分"时指的是手势后续的MOVE事件和最后的UP或CANCEL事件. 在这里我也不考虑多点触摸手势(我

  • 谈谈对Android View事件分发机制的理解

    最近因为项目中用到类似一个LinearLayout中水平布局中,有一个TextView和Button,然后对该LinearLayout布局设置点击事件,点击TextView能够触发该点击事件,然而奇怪的是点击Button却不能触发.然后google到了解决办法(重写Button,然后重写其中的ontouchEvent方法,且返回值为false),但是不知道原因,这两天看了几位大神的博客,然后自己总结下. public class MyButton extends Button { private

  • Android事件分发机制全面解析

    事件分发机制 事件分发机制的两个阶段: 分发:事件从父视图往子视图分发,被拦截后不再传递,进入回溯阶段 回溯:事件从子视图往父视图回溯,被消费后不再回溯 关键方法: ViewGroup.dispatchTouchEvent 往子视图分发事件 ViewGroup.onInterceptTouchEvent 返回 true 表示拦截分发事件,不再传递,进入当前视图 onTouchEvent View.dispatchTouchEvent 默认事件分发,调用 onTouchEvent View.onT

  • Android事件分发机制示例分析

    Android事件类型 public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_CANCEL: break; } return tru

  • Android事件分发机制 ViewGroup分析

    目录 整体流程 源码分析 前言: 事件分发从手指触摸屏幕开始,即产生了触摸信息,被底层系统捕获后会传递给Android的输入系统服务IMS,通过Binder把消息发送到activity,activity会通过phoneWindow.DecorView最终发送给ViewGroup.这里就直接分析ViewGroup的事件分发 整体流程 配合图在看一段伪代码: public boolean dispatchTouchEvent(MotionEvent ev) :Boolean{ val result

  • Android事件分发机制(下) View的事件处理

    综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

  • Android事件分发机制(上) ViewGroup的事件分发

    综述 Android中的事件分发机制也就是View与ViewGroup的对事件的分发与处理.在ViewGroup的内部包含了许多View,而ViewGroup继承自View,所以ViewGroup本身也是一个View.对于事件可以通过ViewGroup下发到它的子View并交由子View进行处理,而ViewGroup本身也能够对事件做出处理.下面就来详细分析一下ViewGroup对时间的分发处理. MotionEvent 当手指接触到屏幕以后,所产生的一系列的事件中,都是由以下三种事件类型组成.

  • Android从源码的角度彻底理解事件分发机制的解析(上)

    其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等--对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机

  • Android从源码的角度彻底理解事件分发机制的解析(下)

    记得在前面的文章中,我带大家一起从源码的角度分析了Android中View的事件分发机制,相信阅读过的朋友对View的事件分发已经有比较深刻的理解了. 还未阅读过的朋友,请先参考Android从源码的角度彻底理解事件分发机制的解析. 那么今天我们将继续上次未完成的话题,从源码的角度分析ViewGroup的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,是And

随机推荐