37、Android手势识别

摘要:
手势识别已经解释了与Android事件传输和自定义控制相关的知识。接下来,我们将介绍Android中手势的控制。如下图所示,事件类型Android使用32位整数值表示TouchEvent事件,低8位表示Touch事件的特定动作。MotionEvent.ACTION_OUTSIDE:表示用户的触摸超过了正常的UI边界。MotionEvent.ACTION _ SCROLL:在android3.1中引入,非触摸滚动主要由鼠标、滚轮和轨迹球触发。MotionEvent对象可以存储多个指针的相关信息,每个指针都有自己的id和索引。指针的ID在整个事件流中不会改变,但索引会改变。
手势识别

前面已经讲解了Android事件传递和自定义控件相关的知识,接下来介绍Android中手势的控制。说到触摸事件,不得不提的就是MotionEvent。

MotionEvent

事件坐标

每个触摸事件都代表用户在屏幕上的一个动作,而每个动作必定有其发生的位置。在MotionEvent中就有一系列与标触摸事件发生位置相关的函数:

  • getX()和getY():这两个函数获得的x、y值是相对的坐标值,相对于消费这个事件的视图的左上点的坐标。
  • GetRawX()和getRawY():这两个函数获得的x、y值是绝对坐标,是相对于屏幕的。

具体如下图所示:

37、Android手势识别第1张

事件类型

Android用一个32位的整型值表示一次TouchEvent事件,低8位表示Touch事件的具体动作。

Android动作都封装在TouchEvent中,其中Action有两种,它们分别如下所示:

getAction():触摸动作的原始32位信息,包括事件的动作,触控点信息。

getActionMask():触摸的动作、按下、抬起、滑动、多点按下、多点抬起。

分别对应相应的常量,系统内置了很多种事件常量,它们分别如下所示:

MotionEvent.ACTION_DOWN:当屏幕检测到第一个触点按下之后就会触发到这个事件。
MotionEvent.ACTION_MOVE:当触点在屏幕上移动时触发,触点在屏幕上停留也是会触发的,主要是由于它的灵敏度很高。
MotionEvent.ACTION_POINTER_DOWN:当屏幕上已经有触点处于按下的状态的时候,再有新的触点被按下时触发。
MotionEvent.ACTION_POINTER_UP:当屏幕上有多个点被按住,松开其中一个点时触发(即非最后一个点被放开时)触发。
MotionEvent.ACTION_UP:当触点松开时被触发。
MotionEvent.ACTION_OUTSIDE: 表示用户触碰超出了正常的UI边界.
MotionEvent.ACTION_SCROLL:android3.1引入,非触摸滚动,主要是由鼠标、滚轮、轨迹球触发。
MotionEvent.ACTION_CANCEL:当用户保持按下操作,并从你的控件转移到外层控件时触发。

最主要的包括:按下、移动、抬起。

MotionEvent.ACTION_DOWN:当屏幕检测到第一个触点按下之后就会触发到这个事件。

MotionEvent.ACTION_MOVE:当触点在屏幕上移动时触发,触点在屏幕上停留也是会触发的,主要是由于它的灵敏度很高。

MotionEvent.ACTION_UP:当触点松开时被触发。

代码如下所示:

int action = MotionEvent.getAction(event);
switch(action) {
    case MotionEvent.ACTION_DOWN:	// 当屏幕检测到第一个手指按下之后就会触发到这个事件
        break;
    case MotionEvent.ACTION_MOVE:	// 手指在屏幕移动或抖动时执行的事件,会多次执行
        break;
    case MotionEvent.ACTION_UP:		// 手指抬起时执行的事件
        break;        
}

触点个数

为了可以表示多个触摸点的动作,MotionEvent中引入了Pointer的概念,一个pointer就代表一个触摸点,每个pointer都有自己的事件类型,也有自己的横轴坐标值。

getActionIndex():多点触控获取经过掩码和平移后的索引。
getPointerId(id):对于每个触控的点的细节,我们可以通过一个循环执行getPointerId方法获取索引。

一个MotionEvent对象中可能会存储多个pointer的相关信息,每个pointer都会有一个自己的id和index。pointer的id在整个事件流中是不会发生变化的,但是index会发生变化。

由于pointer的index值在不同的MotionEvent对象中会发生变化,但是id值却不会变化。所以,当我们要记录一个触摸点的事件流时,就只需要保存其id,然后使用findPointerIndex(int)来获得其index值,然后再获得其他信息。

Index变化

假设我们现在按下一只手指,然后再按下另一只手指,再把其中一只手指抬起,最后抬起另外一只手指,代码如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int actionMasked = event.getActionMasked();
    int index = event.getActionIndex();
    switch (actionMasked){
        case MotionEvent.ACTION_DOWN:
            Log.e(TAG, "ACTION_DOWN:" + index);
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            Log.e(TAG, "ACTION_POINTER_DOWN:" + index);
            break;
        case MotionEvent.ACTION_POINTER_UP:
            Log.e(TAG, "ACTION_POINTER_UP:" + index);
            break;
        case MotionEvent.ACTION_MOVE:
            //Log.e(TAG, "ACTION_MOVE" + index);
            break;
        case MotionEvent.ACTION_UP:
            Log.e(TAG, "ACTION_UP:" + index);
            break;
    }
    return super.onTouchEvent(event);
}

打印的日志信息如下所示:

2021-07-06 02:29:33.331 31441-31441/com.legend.touch E/MainActivity: ACTION_DOWN:0
2021-07-06 02:29:34.687 31441-31441/com.legend.touch E/MainActivity: ACTION_POINTER_DOWN:1
2021-07-06 02:29:36.975 31441-31441/com.legend.touch E/MainActivity: ACTION_POINTER_UP:0
2021-07-06 02:29:39.149 31441-31441/com.legend.touch E/MainActivity: ACTION_UP:0

从结果可以看出,当第二只手指按下的时候,index是1,但是因为相继抬起,index就变为0了。(所以,Index的值会根据在屏幕上的手指个数而变化)

ID不变

假设我们现在按下一只手指,然后再按下另一只手指,再把其中一只手指抬起,最后抬起另外一只手指,代码如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int actionMasked = event.getActionMasked();
    int pointerId = event.getPointerId(event.getActionIndex());
    switch (actionMasked){
        case MotionEvent.ACTION_DOWN:
            Log.e(TAG, "ACTION_DOWN:" + pointerId);
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            Log.e(TAG, "ACTION_POINTER_DOWN:" + pointerId);
            break;
        case MotionEvent.ACTION_POINTER_UP:
            Log.e(TAG, "ACTION_POINTER_UP:" + pointerId);
            break;
        case MotionEvent.ACTION_MOVE:
            //Log.e(TAG, "ACTION_MOVE" + index);
            break;
        case MotionEvent.ACTION_UP:
            Log.e(TAG, "ACTION_UP:" + pointerId);
            break;
    }
    return super.onTouchEvent(event);
}

打印的日志信息如下所示:

2021-07-06 02:36:38.855 2006-2006/com.legend.touch E/MainActivity: ACTION_DOWN:0
2021-07-06 02:36:40.423 2006-2006/com.legend.touch E/MainActivity: ACTION_POINTER_DOWN:1
2021-07-06 02:36:42.159 2006-2006/com.legend.touch E/MainActivity: ACTION_POINTER_UP:1
2021-07-06 02:36:43.662 2006-2006/com.legend.touch E/MainActivity: ACTION_UP:0

从结果可以看出,id的值并没有发生变化,所以在开发中要使用id来作为依据。

禁用多点触控

在开发中一般情况下不会使用到多点触控的情况,如果想屏蔽多点触控只需要在需要屏蔽的控件的的直接父容器使用如下属性:

android:splitMotionEvents="false"

如果想针对整个Activity中禁用多点触控,只需要在theme.xml中修改主题即可:

<style name="xxxxxx" parent="AppTheme.NoActionBar">
	...
    <item name="android:windowEnableSplitTouch">false</item>
    <item name="android:splitMotionEvents">false</item>
</style>

GestureDetector手势

当用户触摸屏幕的时候,会产生许多手势,例如down,up,scroll,filing等等。

一般情况下,我们知道View类有个View.OnTouchListener内部接口,通过重写他的onTouch(View v, MotionEvent event)方法,我们可以处理一些touch事件。

如果需要处理一些复杂的手势,用这个接口就会很麻烦,Android给我们提供了GestureDetector类,通过这个类我们可以识别很多的手势,主要是通过他的onTouchEvent(event)方法完成了不同手势的识别。

GestureDetector这个类对外提供了两个接口和一个内部类:

OnGestureListener、OnDoubleTapListener、SimpleOnGestureListener

OnGestureListener

OnGestureListener提供了六个函数,它们分别如下:

private class MyGestureListener implements GestureDetector.OnGestureListener{
    @Override
    public boolean onDown(MotionEvent e) {
        // 用户按下屏幕就会触发
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
        // 如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行
        // 执行顺序:onDown->onShowPress->onLongPress
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        // 一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发
        // 点击一下非常快的(不滑动)Touchup:onDown->onSingleTapUp->onSingleTapConfirmed 
        // 点击一下稍微慢点的(不滑动)Touchup:onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        // 在屏幕上拖动事件。无论是用手拖动view,或者是以抛的动作滚动,都会多次触发,这个方法
        // 滑屏:手指触动屏幕后,稍微滑动后立即松开: onDown->onScroll->onScroll->………->onFling
        // 拖动:onDown->onScroll->onScroll->onFiling
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
        // 长按触摸屏,超过一定时长,就会触发这个事件
    }

    /**
      * @param e1    第1个ACTION_DOWN MotionEvent
      * @param e2    最后一个ACTION_MOVE MotionEvent
      * @param velocityX    X轴上的移动速度,像素/秒
      * @param velocityY    Y轴上的移动速度,像素/秒 
    */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        // 滑屏,用户按下触摸屏、快速移动后松开  
        return false;
    }
}

然后创建自定义的手势类来使用:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private GestureDetector mGestureDetector;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mGestureDetector = new GestureDetector(this, new MyGestureListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mGestureDetector.onTouchEvent(event);
    }
}    

OnDoubleTapListener

OnDoubleTapListener提供了三个函数:

private class MyDoubleTapListener implements GestureDetector.OnDoubleTapListener{
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        // 单击事件,用来判定该次点击是SingleTap而不是DoubleTap
        // 触发顺序是:OnDown->OnSingleTapUp->OnSingleTapConfirmed
        return false;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        // 双击事件
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        // 双击间隔中发生的动作
        // 触发顺序是:先触发OnDoubleTap,然后再触发OnDown
        return false;
    }
}

然后创建自定义的手势类来使用,通过setOnDoubleTapListener方法设置:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private GestureDetector mGestureDetector;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mGestureDetector = new GestureDetector(this, new MyGestureListener());
        mGestureDetector.setOnDoubleTapListener(new MyDoubleTapListener());
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mGestureDetector.onTouchEvent(event);
    }
}    

SimpleOnGestureListener

OnGestureListener和OnDoubleTapListener接口里的函数都是强制必须重写的,即使用不到也要重写出来一个空函数但在SimpleOnGestureListener类的实例或派生类中不必如此,可以根据情况,用到哪个函数就重写哪个函数,因为SimpleOnGestureListener类本身已经实现了这两个接口的所有函数,只是里面全是空的而已。

private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onDown(MotionEvent e) {
        return super.onDown(e);
    }

    @Override
    public void onShowPress(MotionEvent e) {
        super.onShowPress(e);
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return super.onSingleTapUp(e);
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        return super.onSingleTapConfirmed(e);
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return super.onScroll(e1, e2, distanceX, distanceY);
    }

    @Override
    public void onLongPress(MotionEvent e) {
        super.onLongPress(e);
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return super.onFling(e1, e2, velocityX, velocityY);
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        return super.onDoubleTap(e);
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        return super.onDoubleTapEvent(e);
    }
}

至此,手势相关的讲解到此结束。

免责声明:文章转载自《37、Android手势识别》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇zabbix 监控特定进程流水线 自动化部署jenkins maven 之github下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

时间不同步导致的nova,cinder服务一会up一会down的来回跳跃

               客户反馈无法创建虚拟机(openstack版本为Juno),登录控制节点,发现nova 和cinder服务有为down的,检查down节点的nova和cinder日志,未发现任何日志信息显示error,且日志显示nova和cinder都在正常更新状态,创建虚拟机的请求,nova-schedule未做任何调度,创建的虚拟机状态直...

xshell各个版本下载

官网下载 怎么从官网下载Xshell 5 或者其他版本呢? 下面我们详细步骤说明! 1)首先我们打开netsarang官网, 点击下载Xshell 6 !填写邮箱等信息! http://www.netsarang.com/download/down_form.html?code=622 2)然后查看邮箱邮件,可见一个Xshell 6 下载的邮件!里面有下载...

ACM/ICPC 之 双向链表_构造列表-模拟祖玛 (TSH OJ-Zuma(祖玛))

这一题是TsingHua OJ上的一道题目,学堂在线的一位数据结构老师的题目(原创),所以我直接把题目先贴下来了,这道题对复习双向链表很有帮助,而且也对数据结构中List,也就是对列表的回顾也是很有帮助的。   祖玛(Zuma) 描述 祖玛是一款曾经风靡全球的游戏,其玩法是:在一条轨道上初始排列着若干个彩色珠子,其中任意三个相邻的珠子不会完全同色...

【Android游戏开发之四】基础的Android 游戏框架(一个游戏角色在屏幕行走的demo)

其实上一篇分析surfaceview的文章就是一个简单的游戏框架了,当然这里再强调一下,简单的游戏框架,以不要高手们不要乱喷哦  ~ 这个Demo是给群里一童鞋写的一个对图片操作以及按键处理,游戏简单框架的一个demo,这里放出给大家分享~ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1...

Visual studio之C#实现数字输入模拟键盘

背景 当前做的一个上位机需运行在工控机上,众所周知,工控机不可能配备键盘,只能是触屏,而我当前的上位机需要输入参数,于是之前的解决办法既是调用Windows自带的OSK.exe虚拟键盘,方法在我的另一外一篇博客Visual studio之C# 调用系统软键盘(外部"osk.exe")中已详述,但这种做法有两个致命缺陷,一是由于调用了外部.exe程序,国产杀...

【转】 中兴OLT-C300常用命令

中兴OLT C300show running-config (加载各种板卡)show gpon onu uncfg (查看OLT所有未配置的ONU)show gpon onu uncfg gpon-olt_1/3/2 (查看端口下未配置的ONU)show gpon onu state gpon-olt_1/2/1 (查看端口下ONU状态)show mac...