Twenty-three, View event system (1) --- View Basics

Disclaimer: This article is a blogger original article, shall not be reproduced without the bloggers allowed. https://blog.csdn.net/yz_cfm/article/details/91051872

What's View:

    Android View is the base class for all controls. It is an abstraction controls an interface layer, representing a control. TextView and ImageView and so we usually use are inherited from the View, the source code is as follows:

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

    Then we look at the layout of the controls LinearLayout normally use, it inherits from ViewGroup. ViewGroup What is it? ViewGroup be appreciated that a combination of View, View, and it may contain many ViewGroup, and comprising in turn may comprise ViewGroup View ViewGroup and, so, to form a tree View. As shown below:

 

    Note that ViewGroup also inherits from View, and is also an abstract class, so we generally achieve good ViewGroup implementation class provided by Android to use in the process of development.

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

    View as a container or ViewGroup ViewGroup these components, a variety of layout controls derive subclasses, such LinearLayout, RelativeLayout like.

    The picture below is part of the inheritance of View Android, that is, we often use the control class:

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

View positional parameters    

    Android system, there are two coordinate systems, respectively, and coordinates Android View coordinates.

    Android coordinates:

    In Android, the vertex of the upper left corner of the screen as the origin of the coordinate system Android, the origin of the X-axis positive direction is right, the Y-axis positive direction is downward. Also in the touch event, but also the way to get the coordinates of the coordinate system using Android getRawX () and getRawY () (absolute coordinates).

    View coordinates:

    除了 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 mLeft + getTranslationX();
}

public float getY() {
    return mTop + getTranslationY();
}

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. Before must first calculate the speed acquisition rate, i.e. getXVelocity () and getYVelocity () in front of these two methods must call computeCurrentVelocity () method.

    2. The rate here refers to the number of pixels finger glide over a period of time, such as the time interval is set to 1000ms, within 1s, fingers glide 100 pixels from the left to the right, then the horizontal velocity is 100. Note that speed can be negative, when the sliding finger from right to left, the horizontal direction is the negative velocity.

                                Calculated speed: speed = (the end position - the start position) / time interval

Code Example:

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;
            }
        });
    }
}

Print Log Information:

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

 

GestureDetector (gesture detection object):

    Gesture detection for detecting the user's secondary click, slide, long press, double-clicking behavior.

    Method explanation:

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);
    }

    ...
}

    Instructions:

    1. Create a GestureDetector object and implement OnGestureListener onDoubleTapListener interface or interfaces (different interfaces, to achieve different monitor events, choose according to demand), we should pay attention here, in GestureDetector constructor, in addition to SimpleOnGestureListener other two constructors It must be an instance of OnGestureListener. So, we need to use several functions OnDoubleTapListener, we must first realize OnGestureListener.

mGestureDetector = new GestureDetector(this, new GestureListener());
mGestureDetector.setOnDoubleTapListener(new DoubleTapListener());

customView.setFocusable(true);
customView.setClickable(true);
customView.setLongClickable(true);

    2. On the Target View of onTouch () method, we call GestureDetector of onTouchEvent () method, the captured MotionEvent to GestureDetector to analyze whether there is an appropriate callback function to handle user gestures:

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;
        }
    }
}

 

Guess you like

Origin blog.csdn.net/yz_cfm/article/details/91051872