【艺术探索笔记】 第 3 章 View 的事件体系

View 的事件体系


3.1 View 基础知识

View 的位置参数、MotionEvent 和 TouchSlop 对象、VelocityTracker、GestureDetector 和 Scroller 对象

3.1.1 什么是 View

  1. 所有控件的基类
  2. ViewGroup 继承自 View,所以 View 既可以是单个控件也可以是一组控件

通过这种关系形成了 View 树的结构

3.1.2 View 的位置参数

View 位置(相对于父容器的位置)由四个顶点决定:left、top、right、bottom。如下图:
image
Android 3.0 之后,新加了几个参数:

x、y:View 左上角的坐标(相对于父容器)  
translationX、translationY:View 左上角相对于父容器的偏移量,默认值是0  

这几个参数的转换关系:
* x = left + translationX
* y = top + translationY

注意:View 平移的时候,left 和 top 的值不会变,变得只是 x、y、translationX、translationY

3.1.3 MotionEvent 和 TouchSlop

  1. MotionEvent(手指接触屏幕后产生的事件)

    事件类型

    • ACTION_DOWN、ACTION_MOVE、ACTION_UP

    点击后触发的流程:

    • 点击后直接松手:DOWN -> UP
    • 点击后滑动再松手:DOWN -> MOVE -> … -> MOVE -> UP

    MotionEvent里边的坐标:

    • getX/getY:相对于当前 View 左上角的触摸坐标
    • getRawX/getRawY:相对于手机屏幕左上角的触摸坐标
  2. TouchSlop(系统能识别的滑动最小距离)

    • 常量,不同设备上此值可能不同

    获取:ViewConfiguration.get(context).getScaledTouchSlop()
    作用:处理滑动事件时,可以对滑动进行过滤,小于此值就认为不是滑动

3.1.4 VelocityTracker、GestureDetector 和 Scroller

  1. VelocityTracker(速度追踪)

    使用:

    //在 onTouchEvent 方法中  
    VelocityTracker velocityTracker = VelocityTracker.obtain();
    velocityTracker.addMovement(event);
    
    //获取当前滑动速度  
    //先计算
    velocityTracker.computeCurrentVelocity(1000);
    //获取 x 轴方向
    velocityTracker.getXVelocity()
    //获取 y 轴方向
    velocityTracker.getXVelocity()
    
    //不需要使用的时候要即使释放
    velocityTracker.clear();
    velocityTracker.recycle();

    速度是单位时间内的像素数

  2. GestureDetector(手势检测)

    作用:辅助开发者更轻松的识别点击、滑动、长按、双击行为

    用法:

    1. 创建 GestureDetector 对象并实现 OnGestureListener 接口
    2. 在 View 的 onTouchEvent 方法中接管触摸事件mGestureDetector.onTouchEvent(event);

    接口方法介绍:
    image

  3. Scroller(弹性滑动对象)

    它本身不能完成弹性滑动,需要和 View 的 computeScroll 方法配合使用

    代码实现:
    image


3.2 View 的滑动

实现 View 滑动的三种方式

  • scrollTo/scrollBy 方法
  • 通过动画给 View 施加平移效果
  • 改变 View 的 LayoutParams 使它重新布局实现滑动

3.2.1 使用 scrollTo/scrollBy

看源码可知:

  • scrollBy 实际调用了 scrollTo,实现了基于当前位置的相对滑动
  • scrollTo 实现了基于所传递参数的绝对滑动

mScrollX:View 左边缘和 View 内容左边缘在水平方向的距离

mScrollY:View 上边缘 和 View 内容上边缘在垂直方向的距离

scrollTo/scrollBy 只能改变 View 内容的位置而不能改变 View 在布局中的位置

image

3.2.2 使用动画

  • View 动画的平移动画实现滑动
  • 属性动画操作 View 的 translationX/translationY

对于需要响应点击事件等 View 来说不太好:

  • View 动画执行完毕后 View 还在原位置(fillAfter 属性了解一下,控制动画执行完后动画效果要不要消失)
  • 属性动画无法兼容 android 3.0 以下的版本(使用兼容库在 3.0 以下版本具体实现也是 View 动画)

对于 View 动画平移后,View 还在原位置的解决方案:

写两个 View,在动画结果位置先隐藏那个 View,当动画执行完毕后,隐藏原始 View,显示动画结束位置的 View

3.2.3 改变布局参数

重新设置 LayouParams 中的 margin 值去改变 View 的位置,从而实现 View 滑动

3.2.4 各种滑动方式对比

  • scrollTo/scrollBy:操作简单,适合对 View 内部的滑动
  • 动画:操作简单,主要用于没有交互的 View 和实现复杂的动画效果
  • 改变布局参数:操作稍微复杂,适用于有交互的 View

3.3 弹性滑动

思想:将一次大的滑动分成若干次小的滑动并在一个时间段内完成
实现方式:Scroller、Handler#postDelayed、Thread#sleep

3.3.1 使用 Scroller

工作原理:

Scroller 本身并不会实现 View 的滑动,它需要配合 View 的 computeScroll 方法才能完成弹性滑动效果,它不断的让 View 重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔 Scroller 就可以得出 View 当前的滑动位置,知道了滑动位置就可以通过 scrollTo 方法来完成 View 的滑动。View 的每一次重绘都会导致 View 进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动。

3.3.2 通过动画

  • 属性动画改变 translationX、translationY(前边已经有了)
  • 值动画,设置起始、结束值和时间,在 onAnimationUpdate 中拿到变化的值设置给 View 的 scrollTo 方法

3.3.3 使用延时策略

  • Handler、View 的 postDelayed 方法
  • 线程的 sleep 方法(用 while 在规定时间内循环发)

3.4 View 的事件分发机制

3.4.1 点击事件的传递规则

分析的对象是 MotionEvent

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

点击事件的分发由三个很重要的方法共同完成:
dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

  • public boolean dispatchTouchEvent(MotionEvent event)

    事件的分发。如果事件能够传递给当前 View,那么此方法一定会调用,返回结果受当前 View 的 onTouchEvent 和 下级 View 的dispatchTouchEvent 方法的影响,表示是否消耗当前事件。

  • public boolean onInterceptTouchEvent(MotionEvent ev)

    在上述方法的内部调用,用来判断是否拦截某个事件,如果当前 View 拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

  • public boolean onTouchEvent(MotionEvent event)

    在 dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前 View 无法再次接收到事件。

点击事件的传递规则:

对于一个根 ViewGroup 来说,点击事件产生后,首先会传递给它,这时它的 dispatchTouchEvent 就会被调用,如果这个 ViewGroup 的 onInterceptTouchEvent 方法返回 true 就表示它要拦截当前事件,接着事件就会交给这个 ViewGroup 去处理,即它的 onTouchEvent 方法就会被调用;如果这个 ViewGroup 的 onInterceptTouchEvent 方法返回 false 就表示它不拦截当前事件,这时当前事件就会传递给它的子元素,接着子元素的 dispatchTouchEvent 就会被调用,如此反复直到事件被最终处理。

点击事件传递顺序:Activity -> Window -> View

3.4.2 事件分发的源码解析

  1. Activity 对点击事件的分发过程

    activity -> phonewindow -> decorview
    如果所有 view 都没有处理事件,最后会由 activity#onTouchEvent 处理

  2. 顶级 View 对点击事件的分发过程(DecorView,也是 ViewGroup)

  3. View 对点击事件的处理过程


3.5 View 的滑动冲突

3.5.1 常见的滑动冲突场景

  • 场景 1——外部滑动方向和内部滑动方向不一致
  • 场景 2——外部滑动方向和内部滑动方向一致
  • 场景 3——上面两种情况的嵌套

image

3.5.2 滑动冲突的处理规则

场景 1:

  • 左右滑动时让外部 View 处理滑动事件,上下滑动时让 内部 View 处理滑动事件
  • 根据滑动是水平滑动还是竖直滑动来判断由谁来拦截事件
  • 滑动方向的判断:
    • 滑动路径和水平方向形成的夹角
    • 水平方向和竖直方向的距离差
    • 水平方向和竖直方向的速度差(特殊场景)

场景 2:

  • 根据业务上的需求,当处于某种状态时需要外部 View 响应滑动事件,而处于另一种状态时则需要内部 View 来响应滑动事件

场景 3:

  • 也是从业务上找突破点

3.5.3 滑动冲突的解决方式

  1. 外部拦截法

    点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截

    伪代码:
    image

    ACTION_DOWN 必须返回 false ,因为父容器一旦拦截了 ACTION_DOWN 事件,其他的事件就会直接交由父容器处理,事件就不会传递了

    ACTION_MOVE 根据需要决定是否拦截

    ACTION_UP 必须返回 false,如果拦截的话,子元素的 onclick 无法触发。父容器返回 false 最终也会收到 ACTION_UP 事件的

  2. 内部拦截法

    父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理。

    需要配合 requestDisallowInterceptTouchEvent 方法

    伪代码:
    image

    父容器也要默认拦截除了 ACTION_DOWN 以外的其他事件,这样当子元素调用 parent.requestDisallowInterceptTouchEvent(false) 方法时,父容器才能继续拦截所需的事件

    伪代码:
    image


猜你喜欢

转载自blog.csdn.net/captive_rainbow_/article/details/80989898