在 android 开发过程当中,咱们常常须要对一些手势,如:单击、双击、长按、滑动、缩放等,进行监测。这时也就引出了手势监测的概念,所谓的手势监测,说白了就是对于 GestureDetector 的用法的使用和注意要点的学习。注:因为缩放手势独有的复杂性,我打算后期将其单独拿出来概括总结。java
像网上其余将手势监听的博客同样,本文将以双击事件为引子,逐步展开探讨 Android 手势监听,你须要知道的点点滴滴,仍是那句话:看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!android
对于一个 Android 新手而言,若是须要你实现一个双击功能,咱们通常会怎么想呢?ide
May Beoop
But
这实在是太复杂了,你又要控制时间,又要判断控件等等等等。因此,咱们因该如何解决呢?手势监听的使用post
个人理解是 GestureDetector 是 Android 中,专门用来进行手势监听的一个对象,在他的监听器中,咱们经过传入 MotionEvents 对象,就能够在各类事件的回调方法中各类手势进行监测。举个例子: GestureDetector 的 OnGestureListener 就是一种回调方法,就是说在得到了传入的这个 MotionEvents 对象以后,进行了处理,咱们经过重写了其中的各类方法(单击事件、双击事件等等),就能够监听到单击,双击,滑动等事件,而后直接在这些方法内部进行处理。学习
简单易懂,一分钟搞定this
@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); }
运行结果线程
若是哪位好奇的老铁,尝试着在线程中建立这个 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== 致使建立失败。code
既然问题出现了,那要怎么解决呢。既然是缺乏活动中的 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 还有哪些厉害的回调方法呢?
咱们先来说讲 OnDoubleTapListener,你们可能要问:刚刚不是已经讲过双击事件监听了吗,这里又来不是浪费时间?废话不说,让我详细介绍下这类的方法:
有人就会很好奇,对于单击事件的回调,直接去用 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); } ... };
我打算把这两个方法放在一块儿将,一则他两都属于双击的范畴,二则他两有着极高类似和细微却重要的区别。
你们能够尝试着在 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 的学习
这能够说是整个手势监测中,最核心的部分了,前面都是引入,如今才是正题,这里我主要向你们介绍一下手势:
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 我我的感受这是个最经常使用的方法,就像它的名字,翻译过来是拖、拽、扔的意思。举个例子 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 很简单,就是长按事件的回调,好比说长按复制,长按弹窗等等,它不但应用普遍,同时使用也很是简单,这里就不唠叨了
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public void onLongPress(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "onLongPress"); // 后续工做 super.onLongPress(e); } };
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); } };
这个方法我其实以为做用不是很大,由于它是在 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 | 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 中包含了以上全部方法的空实现,之因此在文末再一次说起他,主要是想讲下它的方便之处。
咱们以监听 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); } };
本篇博客,是我对我学习过程的总结,因此其中不免有疏漏,但愿你们能在评论区中指出,万分感谢。
同时,若是你们有任何疑问,也能够在评论区中留言、讨论,这个搓衣板跪不跪,大家说了算!🙏