热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android点击事件之多点触摸与手势识别的实现

这篇文章主要介绍了Android点击事件之多点触摸与手势识别的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

最近遇到想要实现三指滑动监听的需求,实现代码不方便贴出来,但是思路还是可以记录一下。

Muilti-touch 双指缩放探索

首先要实现OnTouchListener接口,然后重写方法:

public boolean onTouch(View v, MotionEvent event); 

从这个方法中我们就可以获取实现两指缩放功能的全部信息。

View v是触发事件的源,MotionEvent event即一个触摸事件。对屏幕的几乎所有操作都会触发事件,如点击、放开、滑动等。

不同的事件在MotionEvent中有不同的id,我们可以根据event.getAction() & MotionEvent.ACTION_MASK的结果来判断是何种事件。

有如下事件使我们要用到的:

  • MotionEvent.ACTION_DOWN:在第一个点被按下时触发
  • MotionEvent.ACTION_UP:当屏幕上唯一的点被放开时触发
  • MotionEvent.ACTION_POINTER_DOWN:当屏幕上已经有一个点被按住,此时再按下其他点时触发。
  • MotionEvent.ACTION_POINTER_UP:当屏幕上有多个点被按住,松开其中一个点时触发(即非最后一个点被放开时)。
  • MotionEvent.ACTION_MOVE:当有点在屏幕上移动时触发。值得注意的是,由于它的灵敏度很高,而我们的手指又不可能完全静止(即使我们感觉不到移动,但其实我们的手指也在不停地抖动),所以实际的情况是,基本上只要有点在屏幕上,此事件就会一直不停地被触发。

举例子来说:当我们放一个食指到屏幕上时,触发ACTION_DOWN事件;再放一个中指到屏幕上,触发ACTION_POINTER_DOWN事件;此时再把食指或中指放开,都会触发ACTION_POINTER_UP事件;再放开最后一个手指,触发ACTION_UP事件;而同时在整个过程中,ACTION_MOVE事件会一直不停地被触发。

event.getX(index)和event.getY(index)可以获取到指定index点的坐标,所以当屏幕上有两个点的时候,我们用如下方法来获取两点间的距离:

private float spacing(MotionEvent event) {  
    float x = event.getX(0) - event.getX(1);  
    float y = event.getY(0) - event.getY(1);  
    return FloatMath.sqrt(x * x + y * y);  
} 

由以上事件触发的原理,就可以根据被触发的不同事件来判断当前屏幕上的点的个数:

switch (event.getAction() & MotionEvent.ACTION_MASK) {  
        case MotionEvent.ACTION_DOWN:  
            mode = 1;  
            break;  
        case MotionEvent.ACTION_UP:  
            mode = 0;  
            break;  
        case MotionEvent.ACTION_POINTER_UP:  
            mode -= 1;  
            break;  
        case MotionEvent.ACTION_POINTER_DOWN:  
            mode += 1;  
            break;  
}

然后在MotionEvent.ACTION_MOVE事件中,判断点的个数,如果大于等于2,就计算两点间的距离,如果距离增大就把图片放大,距离减少就把图片缩小。

于是代码就成了:

switch (event.getAction() & MotionEvent.ACTION_MASK) {  
case MotionEvent.ACTION_DOWN:  
    mode = 1;  
    break;  
case MotionEvent.ACTION_UP:  
    mode = 0;  
    break;  
case MotionEvent.ACTION_POINTER_UP:  
    mode -= 1;  
    break;  
case MotionEvent.ACTION_POINTER_DOWN:  
    oldDist = spacing(event);//两点按下时的距离  
    mode += 1;  
    break;        
case MotionEvent.ACTION_MOVE:  
    if (mode >= 2) {  
        float newDist = spacing(event);  
        if (newDist > oldDist) {  
            zoomOut();  
        }  
        if (newDist 

经过检验,这种方法是能够实现缩放效果的。

但是有了另外一个问题:就是由于ACTION_MOVE会因颤抖一直被触发,而每次触发的时候两点间的距离也总会有细小的变化,所以运行之后只要有两点在屏幕上,就总会在放大或缩小字体。 

经过一番思考,我想出了一个控制其灵敏度的方法,即在case MotionEvent.ACTION_MOVE时判断只有当距离变化大于一定程度时才会更改字体大小:

if (newDist > oldDist + 1) {//原为:if (newDist > oldDist)  
    zoomOut();//放大  
}  

另外缩放的方法也改成了按比例缩放,完整的ZoomListenter代码:

import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.TextView;
 
public class ZoomListenter implements OnTouchListener {
 
    private int mode = 0;
    float oldDist;
    float textSize = 0;
 
    TextView textView = null;
 
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        textView = (TextView) v;
        if (textSize == 0) {
            textSize = textView.getTextSize();
        }
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            mode = 1;
            break;
        case MotionEvent.ACTION_UP:
            mode = 0;
            break;
        case MotionEvent.ACTION_POINTER_UP:
            mode -= 1;
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            oldDist = spacing(event);
            mode += 1;
            break;
 
        case MotionEvent.ACTION_MOVE:
            if (mode >= 2) {
                float newDist = spacing(event);
                if (newDist > oldDist + 1) {
                    zoom(newDist / oldDist);
                    oldDist = newDist;
                }
                if (newDist 

这样,基本算是能达到预期的效果了。

Android原生带的手势监听

GestureDetector 使用

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

使用方法

首先,创建一个 SimpleOnGestureListener 回调方法对象,并对其中各个方法进行重写
根据这个 listener 对象,实例化出 GestureDetector 对象
对目标控件重写 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);
    }

GestureDetecotr 还有哪些厉害的回调方法呢?

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

OnDoubleTapListener

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

单击回调 SingleTapConfirmed

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

首先,这两个方法是冲突的,这里就涉及到了事件分发机制,具体的后面有空再总结下,这里就不详解了。

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

        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

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

大家可以尝试着在 onDoubleTapEvent和 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

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

  • 按下(Down)
  • 一扔(Fling)
  • 长按(LongPress)
  • 滚动(Scroll)
  • 触摸反馈(ShowPress)
  • 单击抬起(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 就好了。

其它

Android 除了提供了一个 GestureDetector 来帮助我们识别一些基本的触摸手势外,还有 ScaleGestureDetector 可以识别缩放手势,让我们很方便地实现手势控制功能。

//-----------------------implement OnScaleGestureListener's method----------------------//
 
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        Toast.makeText(MainActivity.this, "onScale", Toast.LENGTH_SHORT).show();
        return true;
    }
 
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        Toast.makeText(MainActivity.this, "onScaleBegin", Toast.LENGTH_SHORT).show();
        return true;
    }
 
    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        Toast.makeText(MainActivity.this, "onScaleEnd", Toast.LENGTH_SHORT).show();
    }

到此这篇关于Android点击事件之多点触摸与手势识别的实现的文章就介绍到这了,更多相关Android 多点触摸与手势识别内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 查找最小值的操作是很简单的,只需要从根节点递归的遍历到左子树节点即可。当遍历到节点的左孩子为NULL时,则这个节点就是树的最小值。上面的树中,从根节点20开始,递归遍历左子 ... [详细]
  • 在使用STM32Cube进行定时器配置时,有时会遇到延时不准的问题。本文探讨了可能导致延时不准确的原因,并提供了解决方法和预防措施。 ... [详细]
  • 本文详细介绍了如何在 Android 开发中高效地管理和使用资源,包括本地资源和系统资源的访问方法。通过实例和代码片段,帮助开发者更好地理解和应用资源管理的最佳实践。 ... [详细]
  • Python 内存管理机制详解
    本文深入探讨了Python的内存管理机制,涵盖了垃圾回收、引用计数和内存池机制。通过具体示例和专业解释,帮助读者理解Python如何高效地管理和释放内存资源。 ... [详细]
  • C#设计模式学习笔记:观察者模式解析
    本文将探讨观察者模式的基本概念、应用场景及其在C#中的实现方法。通过借鉴《Head First Design Patterns》和维基百科等资源,详细介绍该模式的工作原理,并提供具体代码示例。 ... [详细]
  • 本文详细介绍了划分树这一数据结构,重点探讨了其在子树和中值计算中的应用及优化方法。 ... [详细]
  • Android Studio 安装与配置指南
    本教程详细介绍了如何下载并安装 Android Studio,包括设置 SDK 路径和优化启动性能的方法。通过这些步骤,您可以顺利地开始开发 Android 应用。 ... [详细]
  • Appium + Java 自动化测试中处理页面空白区域点击问题
    在进行移动应用自动化测试时,有时会遇到某些页面没有返回按钮,只能通过点击空白区域返回的情况。本文将探讨如何在Appium + Java环境中有效解决此类问题,并提供详细的解决方案。 ... [详细]
  • 如何清除Chrome浏览器地址栏的特定历史记录
    在使用Chrome浏览器时,你可能会发现地址栏保存了大量浏览记录。有时你可能希望删除某些特定的历史记录而不影响其他数据。本文将详细介绍如何单独删除地址栏中的特定记录以及批量清除所有历史记录的方法。 ... [详细]
  • 利用Selenium与ChromeDriver实现豆瓣网页全屏截图
    本文介绍了一种使用Selenium和ChromeDriver结合Python代码,轻松实现对豆瓣网站进行完整页面截图的方法。该方法不仅简单易行,而且解决了新版Selenium不再支持PhantomJS的问题。 ... [详细]
  • 嵌入式开发环境搭建与文件传输指南
    本文详细介绍了如何为嵌入式应用开发搭建必要的软硬件环境,并提供了通过串口和网线两种方式将文件传输到开发板的具体步骤。适合Linux开发初学者参考。 ... [详细]
  • 解决TensorFlow CPU版本安装中的依赖问题
    本文记录了在安装CPU版本的TensorFlow过程中遇到的依赖问题及解决方案,特别是numpy版本不匹配和动态链接库(DLL)错误。通过详细的步骤说明和专业建议,帮助读者顺利安装并使用TensorFlow。 ... [详细]
  • 探索新一代API文档工具,告别Swagger的繁琐
    对于后端开发者而言,编写和维护API文档既繁琐又不可或缺。本文将介绍一款全新的API文档工具,帮助团队更高效地协作,简化API文档生成流程。 ... [详细]
  • 本文详细探讨了Android Activity中View的绘制流程和动画机制,包括Activity的生命周期、View的测量、布局和绘制过程以及动画对View的影响。通过实验验证,澄清了一些常见的误解,并提供了代码示例和执行结果。 ... [详细]
  • 本文探讨了在构建应用程序时,如何对不同类型的数据进行结构化设计。主要分为三类:全局配置、用户个人设置和用户关系链。每种类型的数据都有其独特的用途和应用场景,合理规划这些数据结构有助于提升用户体验和系统的可维护性。 ... [详细]
author-avatar
星魂陨石
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有