type
status
date
slug
summary
tags
category
password
icon

⾃定义单 View 的触摸反馈

  • 重写 onTouchEvent(),在⽅法内部定制触摸反馈算法
    • 是否消费事件取决于 ACTION_DOWN 事件是否返回 true
      • 一个事件序列,只有处理 ACTION_DOWN 事件的 onTouchEvent() 返回值会有作用,表示是否接管这个事件序列。true 表示接管,一旦接管,后续事件序列中的事件,都直接交给这个 View;false 表示不接管,一旦不接管,后续事件队列中的事件,都不会交给这个 View;而处理 ACTION_MOVE、ACTION_UP 和 ACTION_CANCEL 事件时,onTouchEvent() 返回值是没有作用的。
    • MotionEvent
      • getActionMasked() 和 getAction() 怎么选?
        • 选 getActionMasked()。因为到了多点触控时代,getAction() 已经不够准确
        • 那为什么有些地⽅(包括 Android 源码⾥)依然在⽤ getAction()? 因为它们的场景不考虑多点触控
      • POINTER_DOWN / POINTER_UP:多点触控时的事件
        • getActionIndex():多点触控时⽤到的⽅法
        • 关于 POINTER_DOWN、POINTER_UP 和 getActionIndex(),后⾯多点触控的课程⾥会详细讲
当自定义一个 ViewGroup 时,最好重写一下它的shouldDelayChildPressedState() 方法,该方法的作用是延迟子 View 的按下状态,主要用于自己需要滑动时,用户按下内部子View,不知道用户是想点击子 View 还是滑动自己,就需要延迟改变按下的状态,这样延迟后,用户已经做出下一步动作了,就可以知道用户是想点击还是滑动。如果自身需要滑动,最好重写这个方法返回 true,否则就返回 false,默认是返回 true,但是大部分情况下是不需要延迟的。所以还是重写一下返回 false 比较好

View.onTouchEvent() 的源码逻辑

  • 当⽤户按下(ACTION_DOWN):
    • 如果不在滑动控件中,切换⾄按下状态,并注册⻓按计时器
    • 如果在滑动控件中,切换⾄预按下状态,并注册按下计时器
  • 当进⼊按下状态并移动(ACTION_MOVE):
    • 重绘 Ripple Effect
    • 如果移动出⾃⼰的范围,⾃我标记本次事件失效,忽略后续事件
  • 当⽤户抬起(ACTION_UP):
    • 如果是按下状态并且未触发⻓按,切换⾄抬起状态并触发点击事件,并清除⼀切状态
    • 如果已经触发⻓按,切换⾄抬起状态并清除⼀切状态
  • 当事件意外结束(ACTION_CANCEL):
    • 切换⾄抬起状态,并清除⼀切状态
💡
ToolTip:新版 Android 加⼊的「⻓按提示」功能。
notion image

⾃定义 ViewGroup 的触摸反馈

  • 除了重写 onTouchEvent() ,还需要重写 onInterceptTouchEvent()
  • onInterceptTouchEvent() 不⽤在第⼀时间返回 true,⽽是在任意⼀个事件⾥,需要拦截的时候返回 true 就⾏
  • 在 onInterceptTouchEvent() 中除了判断拦截,还要做好拦截之后的⼯作的准备⼯作(主要和onTouchEvent() 的代码逻辑⼀致)

触摸反馈的流程

notion image
  • Activity.dispatchTouchEvent()
    • 递归: ViewGroup(View).dispatchTouchEvent()
      • ViewGroup.onInterceptTouchEvent()
      • child.dispatchTouchEvent()
      • super.dispatchTouchEvent()
        • View.onTouchEvent()
      • Activity.onTouchEvent()

View.dispatchTouchEvent()

  • 如果设置了 OnTouchListener,调⽤ OnTouchListener.onTouch()
    • 如果 OnTouchListener 消费了事件,返回 true
    • 如果 OnTouchListener 没有消费事件,继续调⽤⾃⼰的 onTouchEvent(),并返回和onTouchEvent() 相同的结果
  • 如果没有设置 OnTouchListener,同上
 

ViewGroup.dispatchTouchEvent()

  • 如果是⽤户初次按下(ACTION_DOWN),清空 TouchTargets 和 DISALLOW_INTERCEPT 标记
  • 拦截处理
  • 如果不拦截并且不是 CANCEL 事件,并且是 DOWN 或者 POINTER_DOWN,尝试把 pointer(⼿指)通过 TouchTarget 分配给⼦ View;并且如果分配给了新的⼦ View,调⽤child.dispatchTouchEvent() 把事件传给⼦ View
  • 看有没有 TouchTarget
    • 如果没有,调⽤⾃⼰的 super.dispatchTouchEvent()
    • 如果有,调⽤ child.dispatchTouchEvent() 把事件传给对应的⼦ View(如果有的话)
  • 如果是 POINTER_UP,从 TouchTargets 中清除 POINTER 信息;如果是 UP 或 CANCEL,重置状态
 

TouchTarget

💡
场景: 一个 ViewGroup 内部可能有多个子 ViewGroup 或者 View,当用户的多个手指作用在不同的 View 上,在 ViewGroup 看来这是多点触控,但是某个 View 可能只是被其中的一个手指操作,那么这个 View 应该只接收到单指触控的事件,这种情况就需要 ViewGroup 做事件的转换
  • 作⽤:记录每个⼦ View 是被哪些 pointer(⼿指)按下的
  • 结构:单向链表
 
双向滑动的 ScalableImageViewView 绘制流程-源码解析
Loading...
shuouyang
shuouyang
android开发 ReactNative开发 小程序开发
最新发布
AOSP 环境搭建
2025-3-29
View 绘制流程-源码解析
2025-3-12
HTTP
2025-3-4
JVM 虚拟机
2025-2-28
蓝牙-BLE-基础
2025-2-28
从 OkHttp 的原理来看 HTTP
2025-2-19
公告
🎉热点信息🎉
--- 1 ---
Jet Brains 推出新的跨平台支持 Kotlin MultiPlatform
--- 2 ---
新的小巧便捷的依赖注入框架 Koin
--- 3 ---
新一代 API 查询语言 GraphQL