Android触摸反馈①概念简析

Android触摸反馈①概念简析

Android小彩虹2021-08-17 21:36:15220A+A-

自定义onTouchEvent触摸反馈

MotionEvent.ACTION_UP包含了按下+抬起的事件组合,可以判断这个动作是一个点击事件,响应点击事件performClick()

class TouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
  override fun onTouchEvent(event: MotionEvent): Boolean {
    //抬起事件(背后是按下+抬起的事件序列)
    if(event.actionMasked == MotionEvent.ACTION_UP){
      performClick()
    }
    //true表示事件被消费了, 并且其上层布局这块区域的点击事件不会被消费(调用)
    return true;
  }
}

return true的含义

可以理解为这是一个事件所有权的标志,只跟DOWN事件有关 如图: 49

  • 黄色区域与绿色区域有重叠,黄色、绿色、灰色区域均注册了点击事件
    • 此时当黄色区域的onTouchEvent返回true的时候,黄色区域的点击事件被消费,而绿色区域灰色区域的事件不会被消费,也就是不会响应
    • 如果黄色区域返回false,那么事件会逐级被消费,黄色->绿色->灰色

event.actionMasked和event.action

event.actionMasked也可以写为event.action,建议使用event.action,大多数view源码用的都是event.action

  • getActionMasked() 和 getAction() 怎么选?
    • 选 getActionMasked()。因为到了多点触控时代,getAction() 已经 不够准确
    • 那为什么有些地方(包括 Android 源码里)依然在用 getAction()?

因为它们的场景不考虑多点触控

  • POINTER_DOWN / POINTER_UP:多点触控时的事件
    • getActionIndex():多点触控时用到的方法
    • 关于 POINTER_DOWN、POINTER_UP 和 getActionIndex(),请往下看

View.onTouchEvent() 的源码逻辑

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

自定义 ViewGroup 的触摸反馈

  • 除了重写 onTouchEvent() ,还需要重写 onInterceptTouchEvent() ,如果重写onInterceptTouchEvent,那么也需要重写onTouchEvent
  • onInterceptTouchEvent() 不用在第一时间返回 true,而是在任意一个事件里, 需要拦截的时候返回 true 就行(默认false,需要拦截的时候才去拦截)
  • 在 onInterceptTouchEvent() 中除了判断拦截,还要做好拦截之后的工作的准备 工作(主要和 onTouchEvent() 的代码逻辑一致)
  • 只有有子view的view才有onInterceptTouchEvent,如ViewGroup,没有子view的view没有onInterceptTouchEvent,如EditText
class TouchLayout(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs) {

  override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
      TODO("Not yet implemented")
    }

  override fun shouldDelayChildPressedState(): Boolean {
    return super.shouldDelayChildPressedState()
  }

  override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
//    return super.onInterceptTouchEvent(ev)
    //这里处理事件拦截逻辑,如大于某个阈值才拦截事件
    val delta = ev.y -100
    if (delta>100){
      return true
    }else{
      return false
    }
  }

  override fun onTouchEvent(event: MotionEvent?): Boolean {
    //重写事件处理逻辑
    return super.onTouchEvent(event)
  }

}

触摸反馈的流程

  • Activity.dispatchTouchEvent()

    • 递归: ViewGroup(View).dispatchTouchEvent()
      • ViewGroup.onInterceptTouchEvent()
      • child.dispatchTouchEvent()
      • super.dispatchTouchEvent()
      • View.onTouchEvent()
    • Activity.onTouchEvent()
    //1. View 
    View.dispatchTouchEvent();
      
    public boolean dispatchTouchEvent(MotionEvent event){
        retrun onTouchEvent();
    }
      
    //2. ViewGroup
    ViewGroup.dispatchTouchEvent();
      
    public boolean dispatchtouchEvent(MotionEvent event){
        boolean result;
        if(interceptTouchEvent()){
            result = onTouchEvent();
        }else{
            result = 子View的dispatchTouchEvent
        }
        return result;
    }
    

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

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

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们