【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 )(一)

简介: 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 )(一)

文章目录

Android 事件分发 系列文章目录

前言

一、ViewGroup | dispatchTransformedTouchEvent 方法返回值含义

二、记录被消费的触摸事件

三、触摸事件没有被消费的调用链分析

四、触摸事件被拦截的调用链分析

五、ViewGroup 事件分发相关源码

六、View 事件分发相关源码

总结

前言

接上一篇博客 【Android 事件分发】事件分发源码分析 ( View 事件传递机制 ) , 继续分析 ViewGroup 的事件分发机制后续代码 ;


Activity 事件传递 , 最终调用到了 ViewGroup 的 dispatchTouchEvent 方法 , 在该方法中有调用了 View 的 dispatchTouchEvent 方法 ;


View 的 dispatchTouchEvent 调用完毕后 , 会返回 true 或 false 结果 ;


那么继续回到 ViewGroup 的 dispatchTransformedTouchEvent 方法 , 在该方法中 , 也就是遍历 ViewGroup 子组件时 , 调用遍历的子组件 View 的 dispatchTouchEvent 方法 , 处理相关触摸和点击相关的操作 ;


本博客继续分析 ViewGroup 的后续调用链 ;






一、ViewGroup | dispatchTransformedTouchEvent 方法返回值含义


在 ViewGroup | dispatchTouchEvent 方法中 , 调用 ViewGroup | dispatchTransformedTouchEvent 方法进行事件的转发 , 主要是遍历子组件调用子组件的触摸方法 ;


在 ViewGroup | dispatchTransformedTouchEvent 方法中 , 调用了 View 子组件的 View | dispatchTouchEvent 方法 , 该方法返回 true/false , 那么对应的 ViewGroup | dispatchTransformedTouchEvent 方法也会返回相同的值 ;


由下面的代码可知 , handled 是子组件 child.dispatchTouchEvent 调用返回值 , 返回结果就是 handled , 然后有将该值直接返回 , 说明 View 子组件的 dispatchTouchEvent 调用结果 , 就是 ViewGroup 的 dispatchTransformedTouchEvent 方法的返回结果 ;


ViewGroup | dispatchTransformedTouchEvent 方法的返回值含义 :


① 如果返回 true , 证明 子组件 View 已经将事件消费完毕 , 后续不需要再继续执行遍历了 ;

② 如果返回 false , 证明 子组件并没有将事件消费 , 后续还可以进行触发 ;



public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
  ...
                if (child == null) {
                  // 被遍历的 child 子组件为空 
                  // 调用父类的分发方法 
                    handled = super.dispatchTouchEvent(event);
                } else {
                  // 被遍历的 child 子组件不为空 
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);
      // 子组件分发触摸事件 
      // 此处调用的是 View 组件的 dispatchTouchEvent 方法 ; 
                    handled = child.dispatchTouchEvent(event);
                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
  ...
  }
}






二、记录被消费的触摸事件


如果 ViewGroup | dispatchTransformedTouchEvent 方法的返回值为 true , 则会触发记录已经被消费的事件的分支 ; 如果返回 false , 则不会触发 ;


// 正式开始分发触摸事件
// 处理以下两种情况 : 
// ① 情况一 : 子控件触摸事件返回 true 
// ② 情况二 : 子控件触摸事件返回 false 
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {


记录时 , 同时会创建 TouchTarget , 并赋值给 ViewGroup | dispatchTouchEvent | newTouchTarget 局部变量 ;


// 记录消费事件 
// 添加触摸目标 
newTouchTarget = addTouchTarget(child, idBitsToAssign);


调用 addTouchTarget 方法 , 创建被消费的事件 , 并将创建的值赋值给了 ViewGroup | mFirstTouchTarget 成员 ; 第一次消费事件被赋值 ;


 

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }


完整代码如下 :


@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
  ...
                            // 正式开始分发触摸事件
                            // 处理以下两种情况 : 
                            // ① 情况一 : 子控件触摸事件返回 true 
                            // ② 情况二 : 子控件触摸事件返回 false 
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                // 如果返回值为 true , 说明该事件已经被消费了 
                                // 此时记录这个已经被消费的事件 
                                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();
  ...
  }
    /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
}





三、触摸事件没有被消费的调用链分析


如果上述事件分发方法 dispatchTransformedTouchEvent 返回 true , 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空 ;


如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false , 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空 ;


如果 ViewGroup | dispatchTransformedTouchEvent 方法的返回值为 false , 则不会记录事件消费 ; 也就不会调用 ViewGroup | addTouchTarget 方法 , 此时


ViewGroup | dispatchTouchEvent | newTouchTarget 局部变量

ViewGroup | mFirstTouchTarget 成员变量


为空 ;


 

/**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }


当执行到 if (mFirstTouchTarget == null) 分支时 , 会命中该分支 , 进而调用 dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS) 方法 , 在 dispatchTransformedTouchEvent 方法中 , 如果 child 为空 , 则会调用父类的 super.dispatchTouchEvent 方法 ;


@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
  ...
                            // 正式开始分发触摸事件
                            // 处理以下两种情况 : 
                            // ① 情况一 : 子控件触摸事件返回 true 
                            // ② 情况二 : 子控件触摸事件返回 false 
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                // 如果返回值为 true , 说明该事件已经被消费了 
                                // 此时记录这个已经被消费的事件 
                                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();
            // 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);
            }
  ...
  }
    /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    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;
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ...
  }
}


ViewGroup 继承自 View , 在 ViewGroup 中调用 super.dispatchTouchEvent 方法 , 就是调用 View 的 dispatchTouchEvent 方法 ;


super.dispatchTouchEvent(event) 方法消费的是父容器自己的事件 ;


child.dispatchTouchEvent(event) 方法消费的是子组件的事件 ;



目录
相关文章
|
4月前
|
XML 搜索推荐 Android开发
Android改变进度条控件progressbar的样式(根据源码修改)
本文介绍了如何基于Android源码自定义ProgressBar样式。首先分析了系统源码中ProgressBar样式的定义,发现其依赖一张旋转图片实现动画效果。接着分两步指导开发者实现自定义:1) 模仿源码创建一个旋转动画XML文件(放置在drawable文件夹),修改图片为自定义样式;2) 在UI控件中通过`indeterminateDrawable`属性应用该动画。最终实现简单且个性化的ProgressBar效果,附带效果图展示。
253 2
|
5月前
|
NoSQL 应用服务中间件 PHP
布谷一对一直播源码android版环境配置流程及功能明细
部署需基于 CentOS 7.9 系统,硬盘不低于 40G,使用宝塔面板安装环境,包括 PHP 7.3(含 Redis、Fileinfo 扩展)、Nginx、MySQL 5.6、Redis 和最新 Composer。Swoole 扩展需按步骤配置。2021.08.05 后部署需将站点目录设为 public 并用 ThinkPHP 伪静态。开发环境建议 Windows 操作系统与最新 Android Studio,基础配置涉及 APP 名称修改、接口域名更换、包名调整及第三方登录分享(如 QQ、微信)的配置,同时需完成阿里云与腾讯云相关设置。
|
Ubuntu 开发工具 Android开发
Repo下载AOSP源码:基于ubuntu22.04 环境配置,android-12.0.0_r32
本文介绍了在基于Ubuntu 22.04的环境下配置Python 3.9、安装repo工具、下载和同步AOSP源码包以及处理repo同步错误的详细步骤。
949 0
Repo下载AOSP源码:基于ubuntu22.04 环境配置,android-12.0.0_r32
|
Android开发 容器
Android经典实战之如何获取View和ViewGroup的中心点
本文介绍了在Android中如何获取`View`和`ViewGroup`的中心点坐标,包括计算相对坐标和屏幕上的绝对坐标,并提供了示例代码。特别注意在视图未完成测量时可能出现的宽高为0的问题及解决方案。
167 7
|
开发工具 git 索引
repo sync 更新源码 android-12.0.0_r34, fatal: 不能重置索引文件至版本 ‘v2.27^0‘。
本文描述了在更新AOSP 12源码时遇到的repo同步错误,并提供了通过手动git pull更新repo工具来解决这一问题的方法。
474 1
|
Android开发 Docker 容器
docker中编译android aosp源码,出现Build sandboxing disabled due to nsjail error
在使用Docker编译Android AOSP源码时,如果遇到"Build sandboxing disabled due to nsjail error"的错误,可以通过在docker run命令中添加`--privileged`参数来解决权限不足的问题。
2169 1
|
API 开发工具 Android开发
Android源码下载
Android源码下载
1475 0
|
开发工具 uml git
AOSP源码下载方法,解决repo sync错误:android-13.0.0_r82
本文分享了下载AOSP源码的方法,包括如何使用repo工具和处理常见的repo sync错误,以及配置Python环境以确保顺利同步特定版本的AOSP代码。
1879 0
AOSP源码下载方法,解决repo sync错误:android-13.0.0_r82
|
Java Android开发 芯片
使用Android Studio导入Android源码:基于全志H713 AOSP,方便解决编译、编码问题
本文介绍了如何将基于全志H713芯片的AOSP Android源码导入Android Studio以解决编译和编码问题,通过操作步骤的详细说明,展示了在Android Studio中利用代码提示和补全功能快速定位并修复编译错误的方法。
905 0
使用Android Studio导入Android源码:基于全志H713 AOSP,方便解决编译、编码问题
|
Android开发
我的Android 进阶修炼(1): AOSP源码根目录结构
本文介绍了AOSP源码的根目录结构,提供了基于MTK9269 Android 9.0源码的目录说明,帮助读者了解AOSP源码的组织方式和各目录的功能。
791 0
我的Android 进阶修炼(1): AOSP源码根目录结构

热门文章

最新文章