事件分发

首先要声明这里用来分析的对象是 MotionEvent,即点击事件。

所谓点击事件的事件分发其实就是对 MotionEvent 事件的分发过程,即当一个 MotionEvent 产生了以后,系统需要把这个事件传递给一个具体的 View,而这个传递过程就是分发机制。

了解了分发机制后就来了解另一个概念,同一个事件序列:从手指接触屏幕的那一刻起到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件就叫同一个事件序列。这个事件序列以 down 事件开始,中间含有 n move 事件,最终以 up 事件结束。

知道什么是同一个事件序列对后面的分析有很大的帮助,因为后续很多都是针对同一个事件序列来进行分析的。接下来看看点击事件的分发过程中三个很重要的方法:

public boolean dispatchTouchEvent(MotionEvent event)

public boolean onInterceptTouchEvent(MotionEvent event)

public boolean onTouchEvent(MotionEvent event)

这三个方法的关系就如下面的伪代码:

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean consume = false;
    if (onInterceptTouchEvent(event)) {
        consume = onTouchEvent(event);
    } else {
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}



通过这个伪代码可以很清晰的知道这三个方法之间的关系。它们的传递规则:对于一个根 ViewGroup 来说,点击事件产生后,首先会传递给 ViewGroup 本身,此时 ViewGroup dispatchTouchEvent() 方法就会被调用,接着会调用 onInterceptTouchEvent() 方法,如果 onInterceptTouchEvent() 方法返回 true 就表示 ViewGroup 要拦截当前事件;接着再调用 onTouchEvent() 方法来处理该事件;但是如果 onInterceptTouchEvent() 方法返回 false 就表示 ViewGroup 不拦截当前事件,此时 ViewGroup onTouchEvent() 方法就不会被调用,而是调用子 View dispatchTouchEvent() 方法,如此反复直到事件最终被处理。

通常来说那三个方法的执行流程就如上所说的,但是还会有一些比较特殊的情况,比如设置 OnTouchListenerOnClickListener。

当一个 View 需要处理事件时,如果 View 设置了 OnTouchListener,那么 OnTouchListener 中的 onTouch() 方法就会被调用。如果 onTouch() 方法返回 false,则当前 View onTouchEvent() 方法就会被调用;如果返回 true,当前 View onTouchEvent() 方法将不会被调用。

在 onTouchEvent() 方法中,如果有设置 OnClickListener,那么 onClick() 方法是一定会被调用的。

曾经见过一道面试题,详细描述记不清了,大概意思是这样的:onTouch() onTouchEvent() 谁先执行 ?有评论者说如果对 View 进行过比较深入的了解是接触不到这些的。

那么通过前面几段叙述,对事件分发机制应该有个比较清晰的理解了,还有以下几点需要注意:

当一个点击事件产生后,该点击事件的传递遵循如下顺序:Activity -> Window -> View,即事件总是先传递给 ActivityActivity 再传递给 Window,最后 Window 再传递给顶级 View。顶级 View 接收到事件后就会按照事件分发机制去分发事件。
ViewGroup 默认不拦截任何事件,即 ViewGroup onInterceptTouchEvent() 方法默认返回falseView onTouchEvent() 默认都会处理事件(返回 true)。
子 View 可以通过 requestDisallowInterceptTouchEvent() 方法干预父 View 的事件分发过程,ACTION_DOWN 事件除外。

滑动冲突

在界面中只要内外两层同时可以滑动,这个时候就会产生滑动冲突。常见的滑动冲突场景可以简单的分为以下三种:

外部滑动方向和内部滑动方向不一致;
外部滑动方向和内部滑动方向一致;
上面两种情况的嵌套。

场景一: 主要是 ViewPager Fragment 配合使用所组成的页面滑动效果。在这种效果中可以通过 ViewPager 提供的左右滑动来切换页面,而每个页面内部往往又是一个 RecyclerView。按说这种效果是会出现滑动冲突的,但却不用我们去处理,原因是 ViewPager 内部已经处理了滑动冲突,所以我们使用的时候无须关注这个问题。

场景二: 如果在一个 ScrollView 中嵌套一个 RecyclerView,那么内外两层都在同一个方向可以滑动,当手指开始滑动的时候就会出现问题,因为系统不知道我们想要滑动的是哪一层。同理,如果内外两层都可以在左右方向滑动也会出现这种情况。

场景三: 场景三是场景一和场景二两种情况的嵌套,这种情况更为复杂。

比较常见的滑动冲突就是上面那三种,那么该怎么来解决呢?其实可以根据滑动类型是水平滑动还是竖直滑动来判断由谁来拦截事件。至于如何判断滑动类型就有比较多的方式了:可以依据滑动路径和水平方向所形成的夹角;也可以依据水平方向和竖直方向上的距离差来判断 。

滑动类型已经确定了,接下来就是确定滑动的接收者,究竟是谁来响应这个滑动类型?下面介绍两种具体的解决方法:
外部拦截

所谓外部拦截就是指点击事件都先经过父容器的拦截处理,如果父容器需要该事件就拦截,否则就不拦截而是交给子容器。外部拦截需要重写父容器的 onInterceptTouchEvent() 方法,在内部做响应的拦截即可。这种方法的伪代码如下:

public boolean oonInterceptTouchEvent(MotionEvent event) {
    boolean intercepted = false;
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if (父容器需要当前点击事件) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false;
            break;
        default :
            break;
    }
    mLastXIntercept = x;
    mLastYIntercept = y;
    return intercepted;
}




onInterceptTouchEvent() 方法中,首先是 ACTION_DOWN 事件,父容器必须返回 false,即不拦截 ACTION_DOWN 事件,这是因为一旦父容器拦截了 ACTION_DOWN 事件,那么后续的 ACTION_MOVE ACTION_UP 事件都会直接交由父容器处理,此时事件就不能传递给子元素了;其次是 ACTION_MOVE 事件,这个事件可以根据需要来决定是否拦截,如果父容器需要拦截就返回 true,否则就返回 false;最后是 ACTION_UP 事件,这里必须返回 false,因为 ACTION_UP 事件本身没有太多意义。
内部拦截

内部拦截是指父容器默认不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗处理掉,否则就返回给父容器进行处理。这种方法和外部拦截刚好相反,需要配合 requestDisallowInterceptTouchEvent() 方法才能正常工作,此时需要重写子元素的 dispatchTouchEvent() 方法。伪代码如下:

public boolean dispatchTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction) {
        case MotionEvent.ACTION_DOWN:
            parent.requestDisallowInterceptTouchEvent(true);
            break;
        case MotinEvent.ACTION_MOVE:
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            if (父容器需要此类点击事件) {
                parent.requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
        default :
            break;
    }
    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(event);
}




使用内部拦截除了需要对子元素进行修改以外,父元素也要修改为拦截除了 ACTION_DOWN 以外的其他事件。事件父元素所做的修改如下:

public boolean onInterceptTouchEvent(MotionEvent event) {
    int action = event.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
        return false;
    } else {
        return true;
    }
}




为什么父元素不能拦截 ACTION_DOWN 事件呢?那是因为通过 requestDisallowInterceptTouchEvent() 方法会自动设置一个 FLAG_DISALLOW_INTERCEPT 标记,该标记会导致父元素无法拦截除了 ACTION_DOWN 以外的其他点击事件,而 ACTION_DOWN 事件会重置 FLAG_DISALLOW_INTERCEPT 标记,使之无效。

目前所理解的 View 事件分发机制和滑动冲突就这么多了,只是一些理论概述,接下来还需要好好的实践一番才能进一步掌握,同时也才能发现一些细节上的问题。

内容概要:本文从关键概念、核心技巧、应用场景、代码案例分析及未来发展趋势五个维度探讨了Python编程语言的进阶之路。关键概念涵盖装饰器、生成器、上下文管理器、元类和异步编程,这些概念有助于开发者突破基础认知的核心壁垒。核心技巧方面,介绍了内存优化、性能加速、代码复用和异步处理的方法,例如使用生成器处理大数据流、numba库加速计算密集型任务等。应用场景展示了Python在大数据处理、Web开发、人工智能和自动化运维等多个领域的广泛运用,特别是在FastAPI框架中构建异步API服务的实战案例,详细分析了装饰器日志记录、异步数据库查询和性能优化技巧。最后展望了Python的未来发展趋势,包括异步编程的普及、类型提示的强化、AI框架的深度整合以及多语言协同。 适合人群:已经掌握Python基础语法,希望进一步提升编程技能的开发者,特别是有意向从事数据科学、Web开发或AI相关工作的技术人员。 使用场景及目标:①掌握Python进阶概念和技术,如装饰器、生成器、异步编程等,提升代码质量和效率;②学习如何在实际项目中应用这些技术,如通过FastAPI构建高效的异步API服务;③了解Python在未来编程领域的潜在发展方向,为职业规划提供参考。 阅读建议:本文仅提供了理论知识,还包含了丰富的实战案例,建议读者在学习过程中结合实际项目进行练习,特别是尝试构建自己的异步API服务,并通过调试代码加深理解。同时关注Python社区的发展动态,及时掌握最新的技术和工具。
内容概要:本文档《Rust系统编程实战》详细介绍了Rust在系统编程领域的应用,强调了其内存安全、零成本抽象和高性能的特点。文档分为三个主要部分:核心实战方向、典型项目案例和技术关键点。在核心实战方向中,重点讲解了unsafe编程、FFI(外部函数接口)和底层API调用,涉及操作系统组件开发、网络编程、设备驱动开发、系统工具开发和嵌入式开发等多个领域,并列出了每个方向所需的技术栈和前置知识。典型项目案例部分以Linux字符设备驱动为例,详细描述了从环境搭建到核心代码实现的具体步骤,包括使用bindgen生成Linux内核API的Rust绑定,定义设备结构体,以及实现驱动核心函数。 适合人群:对系统编程有兴趣并有一定编程基础的开发者,尤其是那些希望深入了解操作系统底层机制、网络协议栈或嵌入式系统的工程师。 使用场景及目标:①掌握Rust在同系统编程场景下的应用,如操作系统组件开发、网络编程、设备驱动开发等;②通过实际项目(如Linux字符设备驱动)的学习,理解Rust与操作系统内核的交互逻辑;③提高对unsafe编程、FFI和底层API调用的理解和运用能力。 阅读建议:由于文档内容较为深入且涉及多个复杂概念,建议读者在学习过程中结合实际操作进行练习,特别是在尝试实现Linux字符设备驱动时,务必按照文档提供的步骤逐步进行,并多加调试和测试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值