1. 什么是 View
- View 是 Android 中所有控件的基类,不管是简单的 Button 和 TextView 还是复杂的 RelativeLayout 和 ListView,它们的共同基类都是 View。即,View 是一种界面层的控件的一种抽象,它代表了一个控件
- 除了 View,还有 ViewGroup,即控件组,ViewGroup 内部包含了许多个控件,即一组 View。View 通常绘制用户可查看并进行交互的内容,ViewGroup 是不可见容器用于定义 View 和其他 ViewGroup 对象的布局结构
- 在 Android 的设计中,ViewGroup 也继承了 View,这意味着 View 本身就可以是单个控件也可以是由多个控件组成的一组控件,通过这种关系就形成了 View 树的结构,这和 Web 前端中的 DOM 树的概念是类似的
2. View 的位置参数
View 的位置主要由它的四个顶点来决定,分别对应 View 的四个属性:left、top、right、bottom
- left: 左上角横坐标;top: 左上角纵坐标;right: 右下角横坐标;bottom: 右下角纵坐标
- 需要注意的是,这些坐标都是相对于 View 的直接父容器,因此是一种相对坐标
- 在 Android 中,x 轴和 y 轴的正方向分别为右和下。不仅是 Android,大部分显示系统都是按照这个标准来定义坐标系的
View 的宽高和坐标的关系
width = getWidth() = mRight - mLeft = getRight() - getLeft()
height = getHeight() = mBottom - mTop = getBottom() - getTop()
View 的位置坐标和直接父容器的关系
从 Android 3.0 开始,View 新增加了几个参数:x、y、translationX、translationY
- x 和 y 是 View 左上角的坐标,translationX 和 translationY 是 View 左上角相对于父容器的偏移量
- 这 4 个参数也是相对于直接父容器的相对坐标,并且 translationX 和 translationY 的默认值是 0
- 和 View 的四个基本的位置参数一样,View 也为这几个新增的参数提供了 get/set 方法,这些参数的换算关系为:
x = left + translationX
,y = top + translationY
需要注意的是,View 在平移的过程中
- top 和 left 表示的是原始左上角的位置信息,其值并不会发生改变
- 此时发生改变的是 x、y、translationX、translationY 这四个新增的参数
3. MotionEvent 和 TouchSlop
MotionEvent
典型的屏幕触摸事件类型
ACTION_DOWN
: 手指刚接触屏幕ACTION_MOVE
: 手指在屏幕上移动ACTION_UP
: 手指从屏幕上松开的一瞬间
正常情况下,一次手指触摸屏幕的行为会触发一系列的点击事件
- 点击屏幕后离开松开,事件序列为:
DOWN -> UP
- 点击屏幕滑动一会再松开,事件序列为:
DOWN -> MOVE -> … -> MOVE -> UP
- 点击屏幕后离开松开,事件序列为:
通过 MotionEvent 对象可以得到点击事件发生的 x 和 y 坐标,系统提供了两组方法:
getX()/getY()
和getRawX()/getRawY()
getX()/getY()
: 返回的是相对于当前 View 左上角的 x 和 y 坐标getRawX()/getRawY()
: 返回的是相对于手机屏幕左上角的 x 和 y 坐标
TouchSlop
TouchSlop 是系统所能识别出的被认为是滑动的最小距离
这是一个常量,和设备有关,在不同设备上这个值可能是不同的
获取这个常量的方法:
ViewConfiguration.get(getContext()).getScaledTouchSlop()
源码中这个常量定义在文件 frameworks/base/core/res/res/values/config.xml 中:
1
2<!-- Base "touch slop" value used by ViewConfiguration as a movement threshold where scrolling should begin. -->
<dimen name="config_viewConfigurationTouchSlop">8dp</dimen>
4. VelocityTracker、GestureDetector 和 Scroller
VelocityTracker
VelocityTracker 即为速度追踪,用于追踪手指在滑动过程中的速度,包括水平速度和垂直速度,坐标轴正方向即为正值、反方向即为负值
VelocityTracker 的用法
首先,在 View 的
onTouchEvent()
方法中追踪当前单击事件的速度:1
2VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);然后,获取当前滑动速度:
1
2
3velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();- 获取速度之前必须先计算速度,即
getXVelocity()
和getYVelocity()
这两个方法的前面必须要调用computeCurrentVelocity()
方法 - 这里的速度是指一段时间内手指所滑过的像素数,方法
computeCurrentVelocity()
的参数表示的就是这个“一段时间”,单位是毫秒
- 获取速度之前必须先计算速度,即
最后,当不需要使用它的时候,需要调用
clear()
方法来重置并回收内存:1
2velocityTracker.clear();
velocityTracker.recycle();
GestureDetector
GestureDetector 即为手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为
GestureDetector 的用法
首先,需要创建一个 GestureDetector 对象并实现
OnGestureListener
接口,根据需要还可以实现OnDoubleTapListener
接口(监听双击行为):1
2
3GestureDetector mGestrueDetector = new GestureDetector(this);
// 解决长按屏幕后无法拖动的现象
mGestureDetector.setIsLongpressEnabled(false);然后,接管目标 View 的
onTouchEvent()
方法,在待监听的onTouchEvent()
方法中添加如下实现:1
2boolean consume = mGestureDetector.onTouchEvent(event);
return consume;最后,可以有选择地实现
OnGestureListener
和OnDoubleTapListener
中的方法方法名 描述 所属接口 onDown()
手指轻轻触摸屏幕的一瞬间,由 1 个 ACTION_DOWN 触发 OnGestureListener
onShowPress()
手指轻轻触摸屏幕,尚未松开或拖动,由 1 个 ACTION_DOWN 触发。注意和 onDown()
的区别,它强调的是没有松开或者拖动的状态OnGestureListener
onSingleTapUp()
手指(轻轻触摸屏幕后)松开,伴随着 1 个 ACTION_UP 触发,即单击行为 OnGestureListener
onScroll()
手指按下屏幕并拖动,由 1 个 ACTION_DOWN,多个 ACTION_MOVE 触发,即拖动行为 OnGestureListene
onLongPress()
用户长久地按着屏幕不放,即长按行为 OnGestureListener
onFling()
用户按下屏幕、快速滑动后松开,由 1 个 ACTION_DOWN、多个 ACTION_MOVE 和 1 个 ACTION_UP 触发,即快速滑动行为 OnGestureListener
onDoubleTap()
双击行为,由 2 次连续的单击组成,它不可能和 onSingleTapConfirmed()
共存OnDoubleTapListener
onSingleTapConfirmed()
严格的单击行为,注意它和 onSingleTapUp()
的区别,如果触发了onSingleTapConfirmed()
,那么后面不可能再紧跟着另一个单击行为,即这只可能是单击行为,而不可能是双击中的一次单击(奇怪为什么所属OnDoubleTapListener
这个接口,有点矛盾)OnDoubleTapListener
onDoubleTapEvent()
表示发生了双击行为,在双击的期间,ACTION_DOWN、ACTION_MOVE 和 ACTION_UP 都会触发此回调 OnDoubleTapListener
注意
- 说明:实际开发中可以不使用 GestureDetector,完全可以在 View 的
onTouchEvent()
方法中实现所需的监听 - 建议:如果只是监听滑动相关的,可以在
onTouchEvent()
中实现;如果要监听双击行为,可以使用 GestureDetector
- 说明:实际开发中可以不使用 GestureDetector,完全可以在 View 的
Scroller
Scroller 即为弹性滑动对象,用于实现 View 的弹性滑动
Scroller 的作用
- 当使用 View 的
scrollTo()/scrollBy()
方法来进行滑动时,其过程是瞬间完成的,这个没有过渡效果的滑动体验并不是很好 - 此时可以使用 Scroller 来实现有过渡效果的滑动,其过程不是瞬间完成的,而是在一定的时间间隔内完成的滑动行为
- Scroller 本身无法让 View 弹性滑动,它需要和 View 的
computeScroll()
方法配合使用才能共同完成这个功能
- 当使用 View 的
使用 Scroller 的典型代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Scroller scroller = new Scroller(mContext);
// 缓慢滚动到指定位置
private void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int delta = destX - scroollX;
// 1000 ms 内滑向 destX,效果就是慢慢滑动
mScroller.startScroll(scrollX, 0, delta, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}