我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android事件分发机制示例分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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;
    }
    
    private TouchTarget getTouchTarget(@NonNull View child) {
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            if (target.child == child) {
                return target;
            }
        }
        return null;
    }
    
    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事件分发内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android事件分发机制示例分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Flex事件机制的示例分析

这篇文章主要为大家展示了“Flex事件机制的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Flex事件机制的示例分析”这篇文章吧。什么是Flex事件机制Flex事件可以看作是一种触发机制
2023-06-17

Flex事件机制中Flex事件分发和监听的示例分析

这篇文章给大家分享的是有关Flex事件机制中Flex事件分发和监听的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。什么是Flex事件机制Flex事件可以看作是一种触发机制,当满足了一定的条件后,会触发这个
2023-06-17

Android事件分发机制

事件分发流程相关 一个事件发生后,首先从Acrtivity开始传递,然后一层一层往下传,从上往下调用dispatchTouchEvent方法传递事件: Activity——>PhoneWindow——>DecorView——>ViewGro
2022-06-06

【Android】事件分发机制源码解析

文章目录1. 分发顺序2.源码分析2.1 Activity中的分发流程dispatchTouchEventonTouchEvent总结2.2 ViewGroup中的分发流程dispatchTouchEventonInterceptTouch
2022-06-06

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

综述Android中的事件分发机制也就是View与ViewGroup的对事件的分发与处理。在ViewGroup的内部包含了许多View,而ViewGroup继承自View,所以ViewGroup本身也是一个View。对于事件可以通过View
2022-06-06

Android中自定义view中事件分发机制与处理的示例分析

这篇文章将为大家详细讲解有关Android中自定义view中事件分发机制与处理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。题引事件只有一个,多个人想要处理,处理的对象不是我们想给的对象就是事件
2023-06-25

Ext.js4.2.1中事件机制的示例分析

这篇文章主要为大家展示了“Ext.js4.2.1中事件机制的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Ext.js4.2.1中事件机制的示例分析”这篇文章吧。一: 简介Ext中的事件
2023-06-04

Android源码分析——ViewGroup的事件分发机制(二)

通过前一篇博客View的事件分发机制,从dispatchTouchEvent说起(一)的介绍相信大家对 Android View 事件的分发机制有了很深的理解。我们知道 Android 中 View 是存在于 Activity。 今天我们继
2022-06-06

Android事件分发之View事件处理关键及示例分析

这篇文章主要为大家介绍了Android事件分发之View事件处理关键及示例分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-14

Android View的事件分发机制

一.Android View框架提供了3个对事件的主要操作概念。 1、事件的分发机制,dispatchTouchEvent。主要是parent根据触摸事件的产生位置,以及child是否愿意负责处理该系列事件等状态,向其child分发事件的机
2022-06-06

Android 事件分发机制 讲解

1、分发事件的组件 分发事件的组件,也称为分发事件者,包括Activity、ViewGroup和View。它们三者的一般结构为:从上图中可以看出,Activity包括了ViewGroup,ViewGroup又可以包含多个View。 2、分发
2022-06-06

C#开发中事件与委托机制的示例分析

这篇文章给大家分享的是有关C#开发中事件与委托机制的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。概述C#中的委托类似于C或C++中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该
2023-06-17

Android点击事件派发机制源码分析

概述 一直想写篇关于Android事件派发机制的文章,却一直没写,这两天刚好是周末,有时间了,想想写一篇吧,不然总是只停留在会用的层次上但是无法了解其内部机制。我用的是4.4源码,打开看看,挺复杂的,尤其是事件是怎么从Activity派发出
2022-06-06

JS中事件循环机制的示例分析

小编给大家分享一下JS中事件循环机制的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1、JavaScript是单线程的JavaScript 是一种单线程的
2023-06-29

nodejs中事件循环机制的示例分析

这篇文章主要介绍了nodejs中事件循环机制的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。前端开发离不开JavaScript,Javascript是一种web前端语
2023-06-14

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录