二十三、 View 的事件体系(1)--- View 基础知识

什么是View:View是Android中所有控件的基类。它是一种界面层的控件的一种抽象,代表一个控件。我们平常使用的TextView和ImageView等都是继承自View的,源码如下:publicclassTextViewextendsViewimplementsViewTreeObserver.OnPreDrawListener...}publicclassImageViewextendsV...

二十三、 View 的事件体系(1)--- View 基础知识

什么是 View:

� � View 是 Android 中所有控件的基类。它是一种界面层的控件的一种抽象,代表一个控件。我们平常使用的 TextView 和 ImageView 等都是继承自 View 的,源码如下:

public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { ... }public class ImageView extends View { ... }

� � 接着我们看看平常使用的布局控件 LinearLayout,它继承自 ViewGroup。ViewGroup 又是什么呢?ViewGroup 可以理解为 View 的组合,它可以包含很多 View 以及 ViewGroup,而且包含的 ViewGroup 又可以包含 View 和 ViewGroup,以此类推,形成一个 View 树。如下图所示:

� � 需要注意的是 ViewGroup 也继承自 View,并且还是个抽象类,所以我们在开发的过程中一般都是使用 Android 提供的实现好的 ViewGroup 的实现类。

public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... }

�� �ViewGroup 作为 View 或者 ViewGroup 这些组件的容器,派生了多种布局控件子类,比如 LinearLayout、RelativeLayout 等。

� � 下面这张图是 Android 中 View 的部分继承关系,也就是我们常常使用的控件类:

public class LinearLayout extends ViewGroup { ... }public class RelativeLayout extends ViewGroup { ... }public class FrameLayout extends ViewGroup { ... }...

View 的位置参数� ��

� � Android 系统中有两种坐标系,分别为 Android 坐标系和 View 坐标系。

� � Android 坐标系:

� � 在 Android 中,将屏幕左上角的顶点作为 Android 坐标系的原点,这个原点向右是 X 轴正方向,向下是 Y 轴正方向。另外在触控事件中,使用 getRawX() 和 getRawY() 方法获得的坐标也是 Android 坐标系的坐标(绝对坐标)。

� � View 坐标系:

� � 除了 Android 坐标系,还有一个坐标系:View 坐标系,它与 Android 坐标系并不冲突,两者是共同存在的,通过这两个坐标系我们可以更好地控制 View。

� � 在 Android 系统中,View 的位置主要由它的四个顶点来决定,分别对应于 View 的四个属性:top、left、right、bottom,其中 top 是左上角纵坐标,left 是左上角横坐标,right 是右下角横坐标,bottom 是右下角纵坐标。需要注意的是,这些坐标都是相对于 View 的父容器来说的,因此它们都是相对坐标。

� � 我们可以通过如下方法,获取 View 的四个顶点坐标(也就是 View 到其父控件(ViewGroup)的距离):

� � getTop():获取 View 自身顶边到父控件顶边的距离。

� � getLeft():获取 View 自身左边到父控件左边的距离。

� � getRight():获取 View 自身右边到父控件左边的距离。

� � getBottom():获取 View 自身底边到父控件顶边的距离。

� � getWidth() 和 getHeight() 是获取 View 的最终宽高。

public final int getWidth() {����return mRight - mLeft;}public final int getHeight() {����return mBottom - mTop;}

� � 上面 View 图中的那个圆点,假设就是我们触摸的点。我们知道无论是 View 还是 ViewGroup,最终的点击事件都会由 onTouchEvent(MotionEvent event) 方法来处理。关于 MotionEvent 待会介绍,这里先看一下它提供的获取焦点坐标的方法:

� � getX():获取触点距离 View 控件左边的距离,即视图坐标。(也是相对坐标)

� � getY():获取触点距离 View 控件上边的距离,即视图坐标。(也是相对坐标)

� � getRawX():获取触点距离屏幕左边的距离,即绝对坐标。

� � getRawY():获取触点距离屏幕顶边的距离,即绝对坐标。

� � 从 Android 3.0 开始,View 增加了额外的几个参数:x、y、translationX、translationY,其中 x 和 y 是 View 左上角的坐标,而 translationX 和 translationY 是 View 左上角相对于父容器的偏移量。这几个参数也是相对于父容器的坐标,并且 translationX 和 translationY 的默认值是 0,和 View 的四个基本位置参数一样,View 也为它们提供了 get/set 方法:

public float getX() {����return mLeftgetTranslationX();}public float getY() {����return mTopgetTranslationY();}public float getTranslationX() {����...}public float getTranslationY() {����...}

� � 从上面源码我们可以看到它们之间的关系,同时我们要注意,View 在平移的过程中,top 和 left 表示的是原始左上角的位置参数,其值并不会发生改变,此时发生改变的是 x、y、translationX、translationY 这四个参数。

MotionEvent 和 TouchSlop:

� � MotionEvent:

�� �用于报告移动(鼠标,笔,手指,轨迹球)事件的对象。运动事件可以保持绝对或相对运动以及其他数据,具体取决于设备的类型。这里我们关注一下关于手指接触屏幕后所产生的几种典型事件:

� � ACTION_DOWN --- 手指刚接触屏幕;

� � ACTION_MOVE --- 手指在屏幕上移动;

� � ACTION_UP --- 手指从屏幕上松开的一瞬间。

� � 正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,如下所示:

� � * 点击屏幕后离开松开,事件序列为 ACTION_DOWN -> ACTION_UP;

� � * 点击屏幕滑动一会再松开,事件序列为 ACTION_DOWN -> ACTION_MOVE -> ... -> ACTION_MOVE -> ACTION_UP。

� � 上面就是典型的事件序列,同时通过 MotionEvent 对象我们可以得到点击事件发生的 x 和 y 坐标。上面也列出来了(getX、getY(相对坐标); getRawX、getRawY(绝对坐标)),其中 getX() 和 getY() 返回的是相对于当前 View 左上角的 x 和 y 坐标,而 getRawX() 和 getRawY() 返回的是相对于手机屏幕左上角的 x 和 y 坐标。

� ��

�� �TouchSlop:

� � TouchSlop 是系统所能识别出的被认为是滑动的最小距离。这是一个常量,和设备有关,在不同设备上这个值可能是不同的。可通过如下方式获取这个常量值:

ViewConfiguration.get(getContext()).getScaledTouchSlop();

� � 获取这个常量的意义:当我们处理滑动时,可以利用这个常量来做一些过滤,比如当两次滑动事件的滑动距离小于这个值,我们就可以认为未达到滑动距离的临界值,因此就可以认为它们不是滑动,这样做可以有更好的用户体验。

VelocityTracker(速度追踪对象):

� � 速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向上的速度,常用于实现投掷和其他此类手势。当你要跟踪一个 touch 事件的时候,使用 obtain() 方法得到这个类的实例,然后用 addMovement(MotionEvent) 方法将你接受到的 Motionevent 加入到 VelocityTracker 类实例中。当你使用到速率时,使用 computeCurrentVelocity(int) 初始化速率的单位,并计算当前的事件的速率,然后使用 getXVelocity() 或 getXVelocity() 获得横向和竖向的速率。

� � 使用方法:

� � 1. eg:在 View 的 onTouchEvent() 方法中追踪当前点击事件的速度。

VelocityTracker velocityTracker = VelocityTracker.obtain();velocityTracker.addMovement(event);

� � 2. 获取当前的滑动速度:

// 参数为时间,单位 ms,计算速率velocityTracker.computeCurrentVelocity(1000);int xVelocity = (int) velocityTracker.getXVelocity();int yVelocity = (int) velocityTracker.getYVelocity();

� � 3. 最后,当不需要它的时候,需要调用 clear() 方法来重置并回收内存。

velocityTracker.clear();velocityTracker.recycle();

� � 这里我们需要注意:

� � 1. 获取速度之前必须先计算速度,即 getXVelocity() 和 getYVelocity() 这两个方法的前面必须要调用 computeCurrentVelocity() 方法。

� � 2. 这里的速度是指一段时间内手指所滑过的像素数,比如将时间间隔设为 1000ms 时,在 1s 内,手指从左向右滑过 100 像素,那么水平速度就是 100。注意速度可以为负数,当手指从右向左滑动时,水平方向速度即为负值。

� ���� ��� ��� ��� ��� ��� �� � 速度的计算公式: 速度 = (终点位置 - 起点位置)/ 时间间隔

代码示例:

MainActivity.java:

package com.cfm.viewtest;public class MainActivity extends AppCompatActivity {����@Override����protected void onCreate(Bundle savedInstanceState) {��������super.onCreate(savedInstanceState);��������setContentView(R.layout.activity_main);��������LinearLayout layout = findViewById(R.id.linear_layout);��������layout.setOnTouchListener(new View.OnTouchListener() {������������@Override������������public boolean onTouch(View v, MotionEvent event) {� � � � � � � � // 步骤1�� ��� ��� ��� �VelocityTracker velocityTracker = VelocityTracker.obtain();����������������velocityTracker.addMovement(event);����������������switch (event.getAction()){��������������������case MotionEvent.ACTION_DOWN:������������������������break;��������������������case MotionEvent.ACTION_MOVE:�� ��� ��� ��� ��� �� � // 步骤2������������������������velocityTracker.computeCurrentVelocity(2000);������������������������int xVelocity = (int) velocityTracker.getXVelocity();������������������������int yVelocity = (int) velocityTracker.getYVelocity();������������������������Log.d("cfmtest", "xVelocity: "xVelocity" ,yVelocity: "yVelocity);������������������������break;��������������������case MotionEvent.ACTION_UP:�� ��� ��� ��� ��� �� � // 步骤3������������������������velocityTracker.clear();������������������������velocityTracker.recycle();������������������������break;����������������}����������������return true;������������}��������});����}}

Log 打印信息:

2019-06-03 22:26:15.724 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 0 ,yVelocity: 02019-06-03 22:26:15.761 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 1170 ,yVelocity: 02019-06-03 22:26:15.779 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 2438 ,yVelocity: 02019-06-03 22:26:15.797 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 3150 ,yVelocity: 02019-06-03 22:26:15.816 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 3504 ,yVelocity: 02019-06-03 22:26:15.834 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 3703 ,yVelocity: 02019-06-03 22:26:15.852 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 4243 ,yVelocity: 02019-06-03 22:26:15.870 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 2687 ,yVelocity: 02019-06-03 22:26:15.888 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 1521 ,yVelocity: 02019-06-03 22:26:15.906 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 1045 ,yVelocity: 02019-06-03 22:26:15.924 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 584 ,yVelocity: 02019-06-03 22:26:15.943 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 0 ,yVelocity: 02019-06-03 22:26:15.961 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 464 ,yVelocity: 02019-06-03 22:26:15.979 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 0 ,yVelocity: 02019-06-03 22:26:15.998 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 0 ,yVelocity: 02019-06-03 22:26:16.017 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: 0 ,yVelocity: 02019-06-03 22:26:16.035 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: -2105 ,yVelocity: 02019-06-03 22:26:16.054 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: -3488 ,yVelocity: 02019-06-03 22:26:16.071 30852-30852/com.cfm.viewtest D/cfmtest: xVelocity: -5386 ,yVelocity: 0

GestureDetector(手势检测对象):

� � 手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。

� � 方法解释:

package android.view;public class GestureDetector {������public interface OnGestureListener {��������/**���������* 手指轻轻触摸屏幕的一瞬间,由一个 ACTION_DOWN 触发。���������*/��������boolean onDown(MotionEvent e);��������/**���������* 手指轻轻触摸屏幕,尚未松开或拖动,由一个 ACTION_DOWN 触发。���������* (注意,这里和 onDown() 的区别,它强调的是没有松开或者拖动的状态)���������*/��������void onShowPress(MotionEvent e);��������/**���������* 手指(轻轻触摸屏幕后)松开,伴随着一个 ACTION_UP 而触发,这是单击行为。���������*/��������boolean onSingleTapUp(MotionEvent e);��������/**���������* 手指按下并在屏幕上拖动,由一个 ACTION_DOWN 和 多个 ACTION_MOVE 触发,这是拖动行为。���������*/��������boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);��������/**���������* 长按事件���������*/��������void onLongPress(MotionEvent e);��������/**���������* 用户按下触摸屏,快速滑动后松开,由一个 ACTION_DOWN 和 多个��ACTION_MOVE 和一个 ACTION_UP 触发,这是快速滑动行为。���������*���������* @param 第一个 ACTION_DOWN 的 MotionEvent���������* @param 最后一个 ACTION_MOVE 的 MotionEvent���������* @param X 轴上的移动速度,像素/秒���������* @param Y 轴上的移动速度,像素/秒���������* @return 事件被消费,返回 ture,否则返回 false���������*/��������boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);����}����/**�����* 当双击或确认时调用接口�����*/����public interface OnDoubleTapListener {��������/**���������* 严格的单击行为���������* 注意,这里它和 onSingleTapUp() 的区别是,如果触发了 onSingleTapConfirmed(),那么后面���������* 不可能再紧跟着另一种单击行为,即这只可能是单击,而不可能是双击中的一次单击。���������*/��������boolean onSingleTapConfirmed(MotionEvent e);��������/**���������* 双击���������* 由两次连续的单击组成,它不可能和 onSingleTapConfirmed 共存。���������*/��������boolean onDoubleTap(MotionEvent e);��������/**���������* 双击行为���������* 在双击的期间,ACTION_DOWN、ACTION_MOVE、ACTION_UP 都会触发此回调。���������*/��������boolean onDoubleTapEvent(MotionEvent e);����}����...}

� � 使用方法:

� � 1. 创建一个 GestureDetector 对象并实现 OnGestureListener 接口 或者 onDoubleTapListener 接口(不同的接口,实现不同的监听事件,根据需求选择),这里我们要注意,在 GestureDetector�构造函数中,除了 SimpleOnGestureListener 以外的其它两个构造函数都必须是 OnGestureListener 的实例。所以要想使用OnDoubleTapListener 的几个函数,就必须先实现 OnGestureListener。

mGestureDetector = new GestureDetector(this, new GestureListener());mGestureDetector.setOnDoubleTapListener(new DoubleTapListener());customView.setFocusable(true);customView.setClickable(true);customView.setLongClickable(true);

� � 2.�在目标 View 的 onTouch() 方法中,我们调用 GestureDetector 的 onTouchEvent() 方法,将捕捉到的 MotionEvent 交给 GestureDetector 来分析是否有合适的 callback 函数来处理用户的手势:

customView.setOnTouchListener(new View.OnTouchListener() {����@Override����public boolean onTouch(View v, MotionEvent event) {��������return mGestureDetector.onTouchEvent(event);����}});

eg:

MainActivity.java:

package com.cfm.viewtest;public class MainActivity extends AppCompatActivity {����private GestureDetector mGestureDetector;����@Override����protected void onCreate(Bundle savedInstanceState) {��������super.onCreate(savedInstanceState);��������setContentView(R.layout.activity_main);��������CustomView customView = findViewById(R.id.custom_view);��������customView.setFocusable(true);��������customView.setClickable(true);��������customView.setLongClickable(true);��������mGestureDetector = new GestureDetector(this, new GestureListener());��������mGestureDetector.setOnDoubleTapListener(new DoubleTapListener());��������customView.setOnTouchListener(new View.OnTouchListener() {������������@Override������������public boolean onTouch(View v, MotionEvent event) {����������������return mGestureDetector.onTouchEvent(event);������������}��������});����}����private class GestureListener implements GestureDetector.OnGestureListener{��������/**���������* 手指轻轻触摸屏幕的一瞬间,由一个 ACTION_DOWN 触发。���������*/��������@Override��������public boolean onDown(MotionEvent e) {������������Log.d("cfmtest", "--- onDown() ---");������������return false;��������}��������/**���������* 用户轻触触摸屏,尚未松开或拖动,由一个 MotionEvent ACTION_DOWN 触发���������* 注意和 onDown() 的区别,强调的是没有松开或者拖动的状态���������*���������* 而 onDown() 也是由一个MotionEventACTION_DOWN触发的,但是他没有任何限制,���������* 也就是说当用户点击的时候,首先 MotionEventACTION_DOWN,onDown() 就会执行,���������* 如果在按下的瞬间没有松开或者是拖动的时候 onShowPress() 就会执行,如果是按下的时间超过瞬间���������* (这块我也不太清楚瞬间的时间差是多少,一般情况下都会执行onShowPress),拖动了,就不执行onShowPress。���������*/��������@Override��������public void onShowPress(MotionEvent e) {������������Log.d("cfmtest", "--- onShowPress() ---");��������}��������/**���������* 用户(轻触触摸屏后)松开,由一个 MotionEvent.ACTION_UP 触发���������*��轻击一下屏幕,立刻抬起来,才会有这个触发���������*�����从名子也可以看出,一次单独的轻击抬起操作,当然,如果除了 Down() 以外还有其它操作,���������*�����那就不再算是 Single 操作了,所以这个事件 就不再响应���������*/��������@Override��������public boolean onSingleTapUp(MotionEvent e) {������������Log.d("cfmtest", "--- onSingleTapUp() ---");������������return true;��������}��������/**���������* 手指按下并在屏幕上拖动,由一个 ACTION_DOWN 和 多个 ACTION_MOVE 触发,这是拖动行为。���������*/��������@Override��������public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {������������Log.d("cfmtest", "--- onScroll() ---");������������return true;��������}��������@Override��������public void onLongPress(MotionEvent e) {������������Log.d("cfmtest", "--- onLongPress() ---");��������}��������@Override��������public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {������������Log.d("cfmtest", "--- onFling() ---");������������return true;��������}����}����private class DoubleTapListener implements GestureDetector.OnDoubleTapListener{��������@Override��������public boolean onSingleTapConfirmed(MotionEvent e) {������������Log.d("cfmtest", "--- onSingleTapConfirmed() ---");������������return true;��������}��������@Override��������public boolean onDoubleTap(MotionEvent e) {������������Log.d("cfmtest", "--- onDoubleTap() ---");������������return true;��������}��������@Override��������public boolean onDoubleTapEvent(MotionEvent e) {������������Log.d("cfmtest", "--- onDoubleTapEvent() ---");������������return true;��������}����}}

源文地址:https://www.guoxiongfei.cn/csdn/4851.html