在介绍 View 的基础知识之前,我们需要知道它到底是什么? View 在 Android 中是所有控件的基类(结构参考上图),不管是简单的 TextView , 还是复杂的 ViewGroup 、 CustomView 亦或者 RecyclerView 它们的共同顶级父类都是 View, 所以说, View 是一种界面层控制的一种抽象,它代表的是一个控件。从上图可知 ViewGroup 是 View 的子类,ViewGroup 在视图层它可以有任意子 View 。
明白 View 的层级关系有助于理解 View 的工作机制。从上图我们也可以知道实现自定义 View 控件可以继承自 View 也可以继承自 ViewGroup 。
View 位置参数
View 的位置主要由它的四个顶点来决定,分别对应于 View 的四个属性: top、left、right、bottom , 其中 top 是左上角纵坐标,left 是左上角横坐标,right 是右下角横坐标,bottom 是右下角纵坐标。需要注意的是,这些坐标都是相对于 View 的父容器,因为它是一种相对坐标,View 的坐标和父容器的关系可以参考下图,在 Android 中 ,x 轴 y 轴 的正方向分别为右和下,这点不难理解,不仅仅是 Android ,其实大部分显示系统都是按照这个标准来定义坐标系的。
根据上图,我们很容易得出 View 的宽高和坐标的关系:
val width = right - left
val height = bottom - top
复制代码
那么如何得到 View 的这四个参数呢?也很简单,在 View 的源码中它们对应于 mLeft 、mRight 、mTop 、和 mBottom 这四个成员变量,通过代码获取方式如下:
val left = left;
val right = right
val top = top
val bottom = bottom
复制代码
从 Android 3.0 开始,View 增加了 额外的几个参数,x 、y 、translationX 、translationY , 其中 x 和 y 是 View 左上角的坐标,而 translationX 和 translationY 是 View 左上角相对于父容器的偏移量。这几个参数也是相对于父容器的坐标,并且 translationX 和 translationY 的默认值是 0 ,和 View 的四个基本的位置参数一样,View 也为他们提供了 set/get 方法,这几个参数的换算关系如下所示:
val x = left + translationX
val y = top + translationY
复制代码
需要注意的是,View 在平移过程中,top 和 left 表示的是原始左上角的位置信息,其值并不会发生改变,此时发生改变的是 x 、y、translationX 、translationY 这四个参数。
MotionEvent 和 TouchSlop
MotionEvent
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
println(“手指按下”)
}
MotionEvent.ACTION_UP -> {
println(“手指抬起”)
}
MotionEvent.ACTION_MOVE -> {
println(“手指移动”)
}
}
return true
}
复制代码
在手指接触屏幕后所产生的一系列事件中,常用的并且非常典型的事件类型有如下几种:
- MotionEvent.ACTION_DOWN: 手指刚接触屏幕
- MotionEvent.ACTION_MOVE: 手指在屏幕上滑动
- MotionEvent.ACTION_UP: ** 手指在屏幕上抬起的一瞬间触发该事件
正常情况下,一次手指触摸屏幕的行为会触发一些列点击事件,考虑有如下几种情况:
- DOWN —> UP: 点击屏幕后立刻抬起手指松开屏幕触发的事件
- DOWN —> MOVE —> MOVE —> MOVE —> UP: 点击屏幕然后随着在屏幕上滑动之后在松开产生的事件
上述三种情况是典型的事件序列,同时通过 MotionEvent 对象我们可以得到点击事件发生的 x 和 y 坐标。因此,系统提供了两组方法 getX / getY 和 getRawX / getRawY 它们的区别其实很简单, 如下:
- getX / getY : 返回相对于当前 View 左上角的 x 和 y 的坐标
- getRawX / getRawY : 返回的是相对于手机屏幕左上角的 x 和 y 坐标。
TouchSlop
TouchSlop 官方解释就是系统所能识别的被认为是滑动的最小距离,通俗点说就是当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就认为你没有在滑动,可以通过下面的 API 获取该常量值,
/**
- 系统所能识别出来的被认为滑动的最小距离
*/
val scaledDoubleTapSlop = ViewConfiguration.get(context).scaledDoubleTapSlop;
复制代码
这个常量可以帮助我们在处理滑动时,利用该数值来做一些过滤,比如当两次滑动事件的滑动距离小于这个值,我们就可以未达到滑动距离的临界点,因此就可以认为他们不是滑动,这样做可以有更好的用户体验。
VelocityTracker 、GestureDetector 和 Scroller
VelocityTracker
VelocityTracker 的作用是用于追踪滑动过程中的速度,包括水平和竖直方向的速度。它的使用过程很简单,首先,在 View 的 onTouchEvent 方法中追踪当前单击事件的速度;
/**
- 速度追踪
*/
val velocityTracker = VelocityTracker.obtain()
velocityTracker.addMovement(event)
复制代码
接着,当我们先知道当前的滑动速度时,这个时候可以采用如下方式来获得当前的速度:
velocityTracker.computeCurrentVelocity(1000)
val xVelocity = velocityTracker.getXVelocity()
val yVelocity = velocityTracker.getYVelocity()
复制代码
这一步有 2 点需要注意,其一 获取速度之前必须先计算速度,既 getXVelocity 和 getYVelocity 这两个方法的前面必须要调用 computeCurrentVelocity 方法,第二点,这里的速度是指一段时间内手指所滑过的像素值,比如将时间间隔设为 1000 ms 时,那么就是在 1s 内手指在水平方向从左向右滑动 500 px 那么水平速度就是 500,注意速度可以为负数,当手指从右往左滑动时,水平方向速度即为负值,这个需要理解一下。速度的计算可以用如下公式:
速度 = ( 终点位置 - 起点位置) / 时间段
根据上面的公式再加上 Android 系统的坐标体系,可以知道,手指逆着坐标系的正方向滑动,所产生的速度就为负值,另外,computeCurrentVelocity 这个方法的参数表示的是一个时间单元或者说时间间隔,它的单位是毫秒 (ms), 计算速度时得到的速度就是在这个时间间隔内手指在水平或竖直方向上所滑