触摸反馈基础

触摸反馈基础

自定义单 View 的触摸反馈

  • 重写 onTouchEvent(),在⽅法内部定制触摸反馈算法
    • 是否消费事件取决于 ACTION_DOWN 事件是否返回 true
    • MotionEvent
      • getActionMasked()getAction()
      • POINTER_DOWN / POINTER_UPgetActionIndex()

View.onTouchEvent()

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

自定义 ViewGroup 的触摸反馈

  • 除了重写 onTouchEvent() ,还需要重写 onInterceptTouchEvent()

  • onInterceptTouchEvent() 不⽤在第⼀时间返回 true,⽽是在任意事件,需要拦截的时候返回 true 就⾏

触摸反馈的流程

  • 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),清空 TouchTargetsDISALLOW_INTERCEPT 标记

  • 拦截处理

  • 如果不拦截并且不是 CANCEL 事件,并且是 DOWN 或者 POINTER_DOWN,尝试把 pointer(手指)通过 TouchTarget 分配给子 View;并且如果分配给了新的子 View,调⽤ child.dispatchTouchEvent() 把事件传给子 View

  • 看有没有 TouchTarget

    • 如果没有,调⽤自⼰的 super.dispatchTouchEvent()
    • 如果有,调⽤ child.dispatchTouchEvent() 把事件传给对应的子 View(如果有的话)
  • 如果是 POINTER_UP,从 TouchTargets 中清除 POINTER 信息;如果是 UPCANCEL,重置状态

TouchTarget

  • 作用:记录每个子 View 是被哪些 pointer(手指)按下的
  • 结构:单向链表

拦截处理

  • 如果不是初次按下,并且没有 TouchTarget,直接拦截
  • 如果是初次按下,或者有 TouchTarget
    • 如果设置了 disallow intercept,不拦截
    • 否则,调⽤ onInterceptTouchEvent(),如果返回 true 则拦截,返回 false 则不拦截