引言

在 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 手势检测,我跪搓衣板!的更多相关文章

  1. 看完这篇还不会自定义 View ,我跪搓衣板

    自定义 View 在实际使用的过程中,我们经常会接到这样一些需求,比如环形计步器,柱状图表,圆形头像等等,这时我们通常的思路是去Google 一下,看看 github 上是否有我们需要的这些控件,但是 ...

  2. 看完这篇还不懂Redis的RDB持久化,你们来打我!

    一.为什么需要持久化 redis里有10gb数据,突然停电或者意外宕机了,再启动的时候10gb都没了?!所以需要持久化,宕机后再通过持久化文件将数据恢复. 二.优缺点 1.rdb文件 rdb文件都是二 ...

  3. 看完这篇还不清楚Netty的内存管理,那我就哭了!

    说明 在学习Netty的时候,ByteBuf随处可见,但是如何高效分配ByteBuf还是很复杂的,Netty的池化内存分配这块还是比较难的,很多人学习过,看过但是还是云里雾里的,本篇文章就是主要来讲解 ...

  4. 看完这篇还不会 Elasticsearch 搜索,那我就哭了!

    本文主要介绍 ElasticSearch 搜索相关的知识,首先会介绍下 URI Search 和 Request Body Search,同时也会学习什么是搜索的相关性,如何衡量相关性. Search ...

  5. 看完这篇还不会用Git,那我就哭了!

    你使用过 Git 吗?也许你已经使用了一段时间,但它的许多奥秘仍然令人困惑. Git 是一个版本控制系统,是任何软件开发项目中的主要内容.通常有两个主要用途:代码备份和代码版本控制.你可以逐步处理代码 ...

  6. 看完这篇还不懂 MySQL 主从复制,可以回家躺平了~

    大家好,我是小羽. 我们在平时工作中,使用最多的数据库就是 MySQL 了,随着业务的增加,如果单单靠一台服务器的话,负载过重,就容易造成宕机. 这样我们保存在 MySQL 数据库的数据就会丢失,那么 ...

  7. 看完这篇再不会 View 的动画框架,我跪搓衣板

    引言 众所周知,一款没有动画的 app,就像没有灵魂的肉体,给用户的体验性很差.现在的 android 在动画效果方面早已空前的发展,1.View 动画框架 2.属性动画框架 3.Drawable 动 ...

  8. 【最短路径Floyd算法详解推导过程】看完这篇,你还能不懂Floyd算法?还不会?

    简介 Floyd-Warshall算法(Floyd-Warshall algorithm),是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似.该算法名称以 ...

  9. [转帖]看完这篇文章你还敢说你懂JVM吗?

    看完这篇文章你还敢说你懂JVM吗? 在一些物理内存为8g的服务器上,主要运行一个Java服务,系统内存分配如下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约 600m,Linux自身使用 ...

随机推荐

  1. csu1811(树上启发式合并)

    csu1811 题意 给定一棵树,每个节点有颜色,每次仅删掉第 \(i\) 条边 \((a_i, b_i)\) ,得到两颗树,问两颗树节点的颜色集合的交集. 分析 转化一下,即所求答案为每次删掉 \( ...

  2. 洛谷——P1107 最大整数

    P1107 最大整数 题目描述 设有n个正整数 (n<=20), 将它们连接成一排, 组成一个最大的多位整数. 例如: n=3时, 3个整数13, 312, 343连接成的最大整数为: 3433 ...

  3. SQL SERVER 内存学习系列

    http://www.cnblogs.com/double-K/p/5049417.html http://blog.sina.com.cn/s/blog_5deb2f5301014wti.html ...

  4. Java实现中文算数验证码(算数运算+-*/)

    原文:http://blog.csdn.net/typa01_kk/article/details/45050091 /** * creat verification code * */ @Actio ...

  5. NSOperationQueue 和 NSOperation

    The NSOperationQueue class regulates the execution of a set of NSOperation objects. After being adde ...

  6. 在Android中解决内存溢出 – OutOfMemoryError

    原文链接:http://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/ 注:本文在原文基础上在如何判断内存是否泄露方面进 ...

  7. 查看sqlserver的端口号

    背景 这几天想写一个使用java连接sqlserver的数据库连接测试程序.但是在查看数据库连接字符格式以后发现需要sqlserver数据库服务的端口号.在安装sqlserver的时候也没有提到端口号 ...

  8. Android Server Push - MQTT推送实现tokudu

    转自:http://www.juapk.com/thread-2834-1-1.html 项目说明:采用MQTT协议实现Android推送消息传输协议:IBM的MQTT协议 JAR包地址:下载服务器安 ...

  9. Android应用程序窗体设计框架介绍

    在Android系统中,一个Activity相应一个应用程序窗体.不论什么一个Activity的启动都是由AMS服务和应用程序进程相互配合来完毕的.AMS服务统一调度系统中全部进程的Activity启 ...

  10. hdu 1283 最简单的计算机

    水题. .. import java.util.Scanner; public class Main { static int m1, m2; static int r1, r2, r3; publi ...