一、问题
- View内部的工作原理是什么?
- 如何解决滑动冲突?
- View的事件分发机制是什么?
二、名词解释
- View:视图
- ViewGroup:视图组
- MotionEvent:移动事件
- TouchSlop
- VelocityTracker:速度追踪
三、什么是View
3.1 什么是View与ViewGroup?
View类代表用户界面组件的基本构建块。视图占据屏幕上的矩形区域,负责绘图和事件处理。 View是窗口小部件的基类,用于创建交互式UI组件(按钮,文本字段等)。 ViewGroup子类是布局的基类,它是不可见容器。ViewGroup包含其他视图(或其他ViewGroups)并定义他们的布局属性的。
(This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class for widgets, which are used to create interactive UI components (buttons, text fields, etc.). The ViewGroup
subclass is the base class for layouts, which are invisible containers that hold other Views (or other ViewGroups) and define their layout properties.)
Android中的View与我们以前理解的“视图”不同。在Android中,View比视图具有更广的含义,它包含了用户交互和显示,更像Windows操作系统中的window。
ViewGroup是View的子类,所以它也具有View的特性,但它主要用来充当View的容器,将其中的View视作自己的孩子,对它的子View进行管理,当然它的孩子也可以是ViewGroup类型。
ViewGroup(树根)和它的孩子们(View和ViewGroup)以树形结构形成了一个层次结构,View类有接受和处理消息的功能,android系统所产生的消息会在这些ViewGroup和 View之间传递。
3.2 View的位置参数
视图的几何形状是矩形。视图具有一个位置,表示为一对左坐标和顶坐标,以及两个尺寸,表示为宽度和高度。位置和尺寸的单位是像素。
它可以通过调用方法getLeft()
和getTop()
来检索视图的位置 。前者返回表示视图矩形的左坐标或X坐标。后者返回表示视图矩形的顶部或Y坐标。这些方法都返回视图相对于其父级的位置。例如,当getLeft()返回20时,这意味着视图位于其直接父级左边缘右侧20像素处。
此外,提供了几种便利方法来避免不必要的计算,即getRight()
和getBottom()
。这些方法返回表示视图矩形的右边和底边的坐标。例如,调用getRight()
类似于以下计算:getLeft() + getWidth()
(The geometry of a view is that of a rectangle. A view has a location, expressed as a pair of left and top coordinates, and two dimensions, expressed as a width and a height. The unit for location and dimensions is the pixel.
It is possible to retrieve the location of a view by invoking the methods getLeft()
and getTop()
. The former returns the left, or X, coordinate of the rectangle representing the view. The latter returns the top, or Y, coordinate of the rectangle representing the view. These methods both return the location of the view relative to its parent. For instance, when getLeft() returns 20, that means the view is located 20 pixels to the right of the left edge of its direct parent.
In addition, several convenience methods are offered to avoid unnecessary computations,namely getRight()
and getBottom()
. These methods return the coordinates of the right and bottom edges of the rectangle representing the view. For instance, calling getRight()
is similar to the following computation: getLeft() + getWidth()
)
- Left = getLeft()
- Right = getRight()
- Top = getTop()
- Bottom = getBottom()
width =right - left
height = bottom -top
从Android 3.0开始,View增加了额外的几个参数:x、y、translationX和translationY
x: View左上角的坐标
y: View右上角的坐标
translationX和translationY是View左上角相对于父容器的偏移量
x= left + translationX
y= top + translationY
3.3 MotionEvent和TouchSlop
3.3.1 MotionEvent
手指触摸屏幕后产生的典型事件类型
ACTION_Down 手指刚接触屏幕
ACTION_MOVE 手指在屏幕上移动
ACTION_UP 手指从屏幕上松开的一瞬间
一般情况下的顺序:
* 点击屏幕后松开,事件顺序为 DOWN -> UP
* 点击屏幕滑动一会再松开,事件顺序为 DOWN->MOVE->…MOVE->UP
获取点击的坐标:
getX/getY 返回的是相当于当前View左上角的x和y坐标
getRawX/getRawY 返回的相对于手机屏幕左上角的坐标
3.3.2 TouchSlop
Touch Slop是指用户的触摸操作在被识别为滚动之前的像素距离。Touch Slop一般用于在用户执行某些其他触摸操作(例如触摸屏幕元素)时防止意外滚动。
使用方法: private int mSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
("Touch slop" refers to the distance in pixels a user's touch can wander before the gesture is interpreted as scrolling. Touch slop is typically used to prevent accidental scrolling when the user is performing some other touch operation, such as touching on-screen elements.)
可以使用ViewConfiguration
该类访问Android系统使用的常用距离,速度和时间。
参考代码:
ViewConfiguration vc = ViewConfiguration.get(view.getContext());
private int mSlop = vc.getScaledTouchSlop();
private int mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
private int mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
...
case MotionEvent.ACTION_MOVE: {
...
float deltaX = motionEvent.getRawX() - mDownX;
if (Math.abs(deltaX) > mSlop) {
// 滚动发生,执行一些事情
}
...
case MotionEvent.ACTION_UP: {
...
} if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity
&& velocityY < velocityX) {
// 标准满足,执行一些事情
}
}
两种常用的ViewConfiguration
方法是 getScaledMinimumFlingVelocity()
和getScaledMaximumFlingVelocity()
。这些方法返回最小和最大速度(分别)以启动fling,以每秒像素数为单位。
3.4 VelocityTracker、GestureDetector和Scroller
3.4.1 VelocityTracker
速度追踪,用于追踪滑动过程的速度,包括水平的和竖直方向的速度。
//View的onTouchEvent方法中,追踪当前单击事件的速度
VelocityTracker mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(event);
//计算当前滑动速度
mVelocityTracker.computeCurrentVelocity(1000);
//在x和y方向的速度
int xVelocity = mVelocityTracker.getXVelocity();
int yVelocity = mVelocityTracker.getYVelocity();
//不用的时候需要调用clear方法来重置并回收
mVelocityTracker.clear();
mVelocityTracker.recycle();
3.4.2 GestureDetector
手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为。 GestureDetector类对外提供了两个接口:OnGestureListener,OnDoubleTapListener。
OnGestureListener有下面的几个动作:
按下(onDown): 刚刚手指接触到触摸屏的那一刹那,就是触的那一下。
抛掷(onFling): 手指在触摸屏上迅速移动,并松开的动作。
长按(onLongPress): 手指按在持续一段时间,并且没有松开。
滚动(onScroll): 手指在触摸屏上滑动。
按住(onShowPress): 手指按在触摸屏上,它的时间范围在按下起效,在长按之前。
抬起(onSingleTapUp):手指离开触摸屏的那一刹那。
具体地说,典型的触屏事件及其listener执行的流程见下:
1.单击事件的执行流程:
有两种情况,一种是时间很短,一种时间稍长。
时间很短:onDown ----> onSingleTapUp ----> onSingleTapConfirmed
时间稍长:onDown ----> onShowPress ----> onSingleTapUp ----> onSingleTapConfirmed
2. 长按事件
onDown ----> onShowPress ----> onLongPress
3.抛(fling):手指触动屏幕后,稍微滑动后立即松开:
onDown ----> onScroll ----> onScroll ----> onScroll ----> ……… ----> onFling
4.拖动(drag)
onDown ----> onScroll ----> onScroll ----> onFiling
注意:有的时候会触发onFiling,但是有的时候不会触发,这是因为人的动作不确定所致
3.4.3 Scroller
弹性滑动对象,用于实现View的弹性滑动。
四、View的滑动
三种方式:
1.通过View本身提供的scrollTo/scrollBy方法
2.通过动画给View施加平移效果
3.通过View的LayoutParams使得view重新布局从而实现滑动
对比:
-
scrollTo/scrollBy:操作简单,适合对View内容的滑动
-
使用动画:操作简单,适合用于没有交互的View和实现复杂的动画效果
- 改变布局参数:操作稍微复杂,适合用于有交互的View
五、View的事件分发机制
Android-进阶-事件分发机制原理(简洁版)
图解 Android 事件分发机制(详细版)
ViewGroup和View 的dispatchTouchEvent 是做事件分发,那么这个事件可能分发出去的四个目标
注:------> 后面代表事件目标需要怎么做。
1、 自己消费,终结传递。------->return true ;
2、 给自己的onTouchEvent处理-------> 调用super.dispatchTouchEvent()系统默认会去调用 onInterceptTouchEvent,在onInterceptTouchEvent return true就会去把事件分给自己的onTouchEvent处理。
3、 传给子View------>调用super.dispatchTouchEvent()默认实现会去调用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就会把事件传给子类。
4、 不传给子View,事件终止往下传递,事件开始回溯,从父View的onTouchEvent开始事件从下到上回归执行每个控件的onTouchEvent------->return false;
注: 由于View没有子View所以不需要onInterceptTouchEvent 来控件是否把事件传递给子View还是拦截,所以View的事件分发调用super.dispatchTouchEvent()的时候默认把事件传给自己的onTouchEvent处理(相当于拦截),对比ViewGroup的dispatchTouchEvent 事件分发,View的事件分发没有上面提到的4个目标的第3点。
ViewGroup和View的onTouchEvent方法是做事件处理的,那么这个事件只能有两个处理方式:
1、自己消费掉,事件终结,不再传给谁----->return true;
2、继续从下往上传,不消费事件,让父View也能收到到这个事件----->return false;View的默认实现是不消费的。所以super==false。
ViewGroup的onInterceptTouchEvent方法对于事件有两种情况:
1、拦截下来,给自己的onTouchEvent处理--->return true;
2、不拦截,把事件往下传给子View---->return false,ViewGroup默认是不拦截的,所以super==false;
未完待续