首页 > 其他 > 详细

看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!

时间:2019-06-25 13:18:29      阅读:106      评论:0      收藏:0      [点我收藏+]

引言

在 android 开发过程中,我们经常需要对一些手势,如:单击、双击、长按、滑动、缩放等,进行监测。这时也就引出了手势监测的概念,所谓的手势监测,说白了就是对于 GestureDetector 的用法的使用和注意要点的学习。注:由于缩放手势独有的复杂性,我打算后期将其单独拿出来归纳总结。

像网上其他将手势监听的博客一样,本文将以双击事件为引子,逐步展开探讨 Android 手势监听,你需要知道的点点滴滴,还是那句话:看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!


双击 666

对于一个 Android 新手而言,如果需要你实现一个双击功能,我们一般会怎么想呢?

May Be

  1. 首先我们重写 onTouchEvent 方法
  2. 当第一次点击后,咱们先判断是否为需要监听的控件
  3. 如果是则 new 一个线程,开始倒计时(如 1s)
  4. 如果在这个倒计时的期间,再次调用了点击事件
  5. 判断成功、发生双击事件??

But
这实在是太复杂了,你又要控制时间,又要判断控件等等等等。所以,我们因该如何解决呢?手势监听的使用


GestureDetector 使用

我的理解是 GestureDetector 是 Android 中,专门用来进行手势监听的一个对象,在他的监听器中,我们通过传入 MotionEvents 对象,就可以在各种事件的回调方法中各种手势进行监测。举个例子: GestureDetector 的 OnGestureListener 就是一种回调方法,就是说在获得了传入的这个 MotionEvents 对象之后,进行了处理,我们通过重写了其中的各种方法(单击事件、双击事件等等),就可以监听到单击,双击,滑动等事件,然后直接在这些方法内部进行处理。

技术分享图片

使用方法

  1. 首先,创建一个 SimpleOnGestureListener 回调方法对象,并对其中各个方法进行重写
  2. 根据这个 listener 对象,实例化出 GestureDetector 对象
  3. 对目标控件重写 setOnTouchListener 方法,并在其中调用 detector 对象的 onTouchEvent 方法即可

简单易懂,一分钟搞定

    @Override
    protected void onResume() {
        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return detector.onTouchEvent(event);
            }
        });
        super.onResume();
    }

    private void iniGestureListener(){
        GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                MyToast.makeToast(GestureDetectorActivity.this, "double  click up!");
                return super.onDoubleTap(e);
            }

        detector = new GestureDetector(GestureDetectorActivity.this, listener);
    }

运行结果


Looper 注册

如果哪位好奇的老铁,尝试着在线程中创建这个 detector 对象(比如下面这种)。那么运行时就可能出现程序崩溃的情况,这是为什么呢?

        new Thread(){
            @Override
            public void run() {
                super.run();
                detector = new GestureDetector(GestureDetectorActivity.this, listener);
            }
        }.start();

其实在 GestureDetector 被实例化时,内部会自动创建一个 Handler 用于处理数据,所以如果你在主线程中创建 GestureDetector,那么这个 GestureDetector 内部创建的 Handler 会自动获得主线程的 Looper。也是因此:如果你在一个没有创建 Looper 的子线程中创建 GestureDetector 则需要传递一个带有 Looper 的 Handler 给它,否则就会因为无法获取到 ==Looper== 导致创建失败。

解决

既然问题出现了,那要怎么解决呢。既然是缺少活动中的 Looper ,那么将活动中的 ==Looper== 传入就是。观察 ==detector== 的构造方法,发现其一共有种方法,其中我们常用的方法有两种,首先是我们在主线程中用的那种,另外一种就是我们现在要用的,在子线程中,能传入 Looper 的 构造方法:
| public GestureDetector(Context context, OnGestureListener listener) |
|--|
| GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler) |

方案一
传入一个保有 Looper 对象的 Hander

        new Thread(){
            @Override
            public void run() {
                super.run();
                detector = new GestureDetector(GestureDetectorActivity.this, listener, new Handler(Looper.getMainLooper()));
            }
        }.start();

方案二
跟方法已一样,只是把 Hander 拿出来单独创建罢了

        new Thread(){
            @Override
            public void run() {
                super.run();
                Handler handler = new Handler(Looper.getMainLooper());
                detector = new GestureDetector(GestureDetectorActivity.this, listener, handler);
            }
        }.start();

方案三
在主线程中创建 Hander ,这样就不用在创建 Hander 时,传入主线程的 Looper

        final Handler handler = new Handler();
        new Thread(){
            @Override
            public void run() {
                super.run();
                detector = new GestureDetector(GestureDetectorActivity.this, listener, handler);
            }
        }.start();

方案四
和上面几个方法一样,只不过在子线称里提前准备好 Lopper ,这样子线称就和主线程一样了

        new Thread(){
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                detector = new GestureDetector(GestureDetectorActivity.this, listener);
            }
        }.start();

重点:手势监听

刚刚我们已经通过双击效果,讲过 onDoubleTapEvent 了,那么 GestureDetecotr 还有哪些厉害的回调方法呢?

  1. OnDoubleTapListener :也就是双击事件,双击事件除了 onDoubleTapEvent 这个回调方法之外,还有 SingleTapConfirmed 和 DoubleTap 这两个回调方法
  2. OnGestureListener :这里集合了众多手势的监听器:主要有:按下(Down)、 扔(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress) 和 单击抬起(SingleTapUp)
  3. SimpleOnGestureListener :上述接口的空实现,用的频率比较多

OnDoubleTapListener

我们先来讲讲 OnDoubleTapListener,大家可能要问:刚刚不是已经讲过双击事件监听了吗,这里又来不是浪费时间?废话不说,让我详细介绍下这类的方法:

单击回调 SingleTapConfirmed

有人就会很好奇,对于单击事件的回调,直接去用 onClickListener 不就好了么,干嘛要用 SingleTapConfirmed 呢?

首先,这两个方法是冲突的,这里就涉及到了事件分发机制,这点我后期会专门给大家总结下,这里就不详解了。

其二,更具 onClickListener 的机制,我们不难发现,如果是用 onClickListener 的话,当我们双击时,我们也会调用单击事件,也就是单击了两次,这明显是不符合我们意图的。那么该如何调用呢?very easy !

        final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                MyToast.makeToast(GestureDetectorActivity.this, "single  click!");
                return super.onSingleTapConfirmed(e);
            }

            ...
        };

DoubleTap 与 onDoubleTapEvent

我打算把这两个方法放在一起将,一则他两都属于双击的范畴,二则他两有着极高相似和细微却重要的区别。

大家可以尝试着在 onTouchEvent 和 DoubleTap 中,对点击的 Down move 和 up 进行打印,你就会发现,对于 DoubleTap 而言,它是在第二次点击按下是,发生的回调,而对于 onDoubleTapEvent 而言,则是在第二次点击后,手指抬起离开了屏幕时,发生的回调。这就是他两最重要的区别。

    final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this, "double  click down!");
            return super.onDoubleTap(e);
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
            switch (e.getActionMasked()){
                case MotionEvent.ACTION_UP:
                    MyToast.makeToast(GestureDetectorActivity.this, "double  click up!");
                    break;
            }
            return super.onDoubleTapEvent(e);
        }
    };

所以,有了这两个方法,我们就可以更具目的性的满足两种需求。
讲到这里,单击双击事件就告一段落了,下面我们进入 OnGestureListener 的学习


OnGestureListener

这可以说是整个手势监测中,最核心的部分了,前面都是引入,现在才是正题,这里我主要向大家介绍一下手势:

  1. 按下(Down)
  2. 一扔(Fling)
  3. 长按(LongPress)
  4. 滚动(Scroll)
  5. 触摸反馈(ShowPress)
  6. 单击抬起(SingleTapUp)

onDown

onDown 事件很好理解,他在一个 View 被按下时执行。也正是如此,要想能执行 onDown ,首先要保证这个 View 是可以点击的,也就是 onClickable 的值为 true 。

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

        @Override
        public boolean onDown(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this, "onDown");
            // 后续事件
            return super.onDown(e);
        }
    };

onFling

对于 onFling 我个人感觉这是个最常用的方法,就像它的名字,翻译过来是拖、拽、扔的意思。举个例子 RecyclerView 或者 ListView 我们都有用过,当我们快速上拉后会滚动一定距离停止,我们可爱的 onFling 就是用于检测这种手势的。

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            mSpeedX = velocityX;
            mSpeedY = velocityY;
            handler.postDelayed(runnable, 30);
            return super.onFling(e1, e2, velocityX, velocityY);
        }
    };

从代码中,我们不难发现:该方法有四个参数

参数 意义
e1 手指按下时的 Event。
e2 手指抬起时的 Event。
velocityX 在 X 轴上的运动速度(像素/秒)。
velocityY 在 Y 轴上的运动速度(像素/秒)。

通过前两个 MotionEvent 参数,我们可以获得点击发生的位置等,通过后两个 float 参数,我们可以获得手指滑动的速度。

具体使用其实还是蛮多的,比如我们可以想象下台球游戏,球杆击球后,就有这样一个初速度递减的效果。

onLongPress

onLongPress 很简单,就是长按事件的回调,比如说长按复制,长按弹窗等等,它不但应用广泛,同时使用也非常简单,这里就不唠叨了

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

        @Override
        public void onLongPress(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this, "onLongPress");
            // 后续工作
            super.onLongPress(e);
        }
    };

onScroll

onScroll 方法和 onFling 很像,唯一的区别在于,onFling 的参数是滑动的速度,而 onScroll 的后两个参数则是滑动的距离:

参数 意义
e1 手指按下时的 MotionEvent
e2 手指抬起时的 MotionEvent
distanceX 在 X 轴上划过的距离
distanceY 在 Y 轴上划过的距离
    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            MyToast.makeToast(GestureDetectorActivity.this, "onScroll X = " + 
                    distanceX + " Y = " + distanceY);
            return super.onScroll(e1, e2, distanceX, distanceY);
        }
    };

onShowPress

这个方法我其实觉得作用不是很大,因为它是在 View 被点击(按下)是调用,其作用是给用户一个视觉反馈,让用户知道我这个控件被点击了,这样的效果我们完全可以用 Material design 的 ripple 实现,或者直接 drawable 写个背景也行。

如果说它有什么特别指出的话,它是一种延时回调,延迟时间是 180 ms。也就是说用户手指按下后,如果立即抬起或者事件立即被拦截,时间没有超过 180 ms的话,这条消息会被 remove 掉,也就不会触发这个回调。

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public void onShowPress(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this, "onShowPress");// >150ms 时调用
            super.onShowPress(e);
        }
    };

onSingleTapUp

对于 onSingleTapUp 网上有很多分析,但我觉得过于复杂了,其实这东西很简单。举个例子你就懂了:

之前我们讲过双击事件,那好 onSingleTapUp 就是在 双击事件的第一次点击时回调。也就是说但你点击了一个控件时(双击第一下),这个回调马上会被调用,然后迅速点第二下(双击事件的第二下),则其不会被调用。

类型 触发次数 摘要
onSingleTapUp 1 在双击的第一次抬起时触发
onSingleTapConfirmed 0 双击发生时不会触发。
onClick 2 在双击事件时触发两次。

它和 onSingleTapConfirmed 的区别也就很明显了,onSingleTapConfirmed 在发生双击时,会回调两次,而 onSingleTapUp 只会在双击的的第一次回调。

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public boolean onSingleTapUp(MotionEvent e) {// 双击第一次抬起触发,第二次不触发
            Log.d("onSingleTapUp", "onSingleTapUp");// >150ms 时调用
            return super.onSingleTapUp(e);
        }
    };

SimpleOnGestureListener

SimpleOnGestureListener 中包含了以上所有方法的空实现,之所以在文末再一次提及他,主要是想讲下它的方便之处。

我们以监听 OnDoubleTapListener 为例,如果想要使用 OnDoubleTapListener 接口则需要这样进行设置:

GestureDetector detector = new GestureDetector(this, new GestureDetector
        .SimpleOnGestureListener());
detector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
    @Override public boolean onSingleTapConfirmed(MotionEvent e) {
        Toast.makeText(MainActivity.this, "onSingleTapConfirmed", Toast.LENGTH_SHORT).show();
        return false;
    }

    @Override public boolean onDoubleTap(MotionEvent e) {
        Toast.makeText(MainActivity.this, "onDoubleTap", Toast.LENGTH_SHORT).show();
        return false;
    }

    @Override public boolean onDoubleTapEvent(MotionEvent e) {
        Toast.makeText(MainActivity.this,"onDoubleTapEvent",Toast.LENGTH_SHORT).show();
        return false;
    }
});

我们不难发现一个问题,既然在 GestureDetector 实例化时,已经实例化了一个 SimpleOnGestureListener 了,那么在舍近求远的去使用 OnGestureListener 的话,会多出几个无用的空实现,显然很浪费,所以在一般情况下,乖乖的使用 SimpleOnGestureListener 就好了。

最后

由于手势监听的方法有点多,大家一时难以记住,所以我打算把所有方法,在 SimpleOnGestureListener 中重写一遍,方便大家进行查阅、记忆:

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this, "single click!");
            return super.onSingleTapConfirmed(e);
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this, "double click down!");
            return super.onDoubleTap(e);
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
            switch (e.getActionMasked()){
                case MotionEvent.ACTION_UP:
                    MyToast.makeToast(GestureDetectorActivity.this, "double click up!");
                    break;
            }
            return super.onDoubleTapEvent(e);
        }

        @Override
        public boolean onDown(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this, "onDown");
            return super.onDown(e);
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            mSpeedX = velocityX;
            mSpeedY = velocityY;
            handler.postDelayed(runnable, 30);
            return super.onFling(e1, e2, velocityX, velocityY);
        }

        @Override
        public void onShowPress(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this, "onShowPress");// >150ms 时调用
            super.onShowPress(e);
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {// 双击第一次抬起触发,第二次不触发
            Log.d("onSingleTapUp", "onSingleTapUp");// >150ms 时调用
            return super.onSingleTapUp(e);
        }

        @Override
        public void onLongPress(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this, "onLongPress");
            // 后续工作
            super.onLongPress(e);
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            MyToast.makeToast(GestureDetectorActivity.this, "onScroll X = " +
                    distanceX + " Y = " + distanceY);
            return super.onScroll(e1, e2, distanceX, distanceY);
        }
        
    };

本篇博客,是我对我学习过程的总结,所以其中难免有疏漏,希望大家能在评论区中指出,万分感谢。

同时,如果大家有任何疑问,也可以在评论区中留言、讨论,这个搓衣板跪不跪,你们说了算!??

看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!

原文:https://www.cnblogs.com/yuanhao-1999/p/11081980.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!