android手势监听

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。
未经允许不得转载

目录

  • 一、导读
  • 二、概览
  • 三、使用
  • 四、 如何实现触摸滚动
  • 五、 如何拖动对象 、缩放对象
    • 基本尺寸示例
  • 六、 推荐阅读

在这里插入图片描述

一、导读

我们继续总结学习基础知识,温故知新。

本文主要描述了手势监听相关的知识。

二、概览

Android事件处理机制是基于Listener实现的,比如触摸屏相关的事件,就是通过onTouchListener实现,
通过MotionEvent的getAction()方法来获取Touch事件的类型,包括 ACTION_DOWN(按下触摸屏),
ACTION_MOVE(按下触摸屏后移动受力点), ACTION_UP(松开触摸屏)和ACTION_CANCEL(不会由用户直接触发)。
借助对于用户不同操作的判断,结合getRawX()、getRawY()、getX()和getY()等方法来获取坐标后,我们可以实现诸如拖动某一个按钮,拖动滚动条等功能。

但是如果是更复杂的一些动画,比如用户在屏幕上的手势,这就需要另外一个接口了,GestureDetector.OnGestureListener接口。
Android 提供了GestureDetector(手势识别)类,通过这个类我们可以识别很多的手势,主要是通过他的onTouchEvent(event)方法完成了不同手势的识别。

三、使用

我们先来看下基本的使用,方法的注释我们都写代码里面

private class MyGesturelistener implements GestureDetector.OnGestureListener{/*** 用户按下屏幕触发* @param e* @return*/public boolean onDown(MotionEvent e) {  return false;  }  /*** 如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行 * 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发 * 注意和onDown()的区别,强调的是没有松开或者拖动的状态* @param e*/public void onShowPress(MotionEvent e) {  }  /*** 长按触摸屏,超过一定时长,就会触发这个事件    触发顺序:    onDown->onShowPress->onLongPress * @param e*/public void onLongPress(MotionEvent e) {  }  /*** 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发  * 一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以也就不会触发这个事件    * 触发顺序:    * 点击一下非常快的(不滑动)Touchup:   * onDown->onSingleTapUp->onSingleTapConfirmed    * 点击一下稍微慢点的(不滑动)Touchup:    * onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed * @param e*/public boolean onSingleTapUp(MotionEvent e) {  return false;  }  /*** 在屏幕上拖动事件 ,用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发* @param e1* onDown------》onScroll----》onScroll------》onFiling*/public boolean onScroll(MotionEvent e1, MotionEvent e2,  float distanceX, float distanceY) {  return false;  }  /*** 滑屏,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发* @param e1 第1个ACTION_DOWN MotionEvent* @param e2 最后一个ACTION_MOVE MotionEvent* @param velocityX X轴上的移动速度,像素/秒 * @param velocityY Y轴上的移动速度,像素/秒*                  onDown-----》onScroll----》onScroll----》onScroll----》………----->onFling  */public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,  float velocityY) {  return false;  }
}

这里有两种不同类型的滚动,也是我们平常用的比较多的方法,onScroll()、onFling()

  • 拖动 是用户在触摸屏上拖动手指时发生的滚动类型。简单的拖动通常通过重写来 onScroll()
    实现GestureDetector.OnGestureListener。有关拖动的更多信息,请参阅拖动和缩放。

  • 拖放 是用户快速拖动并抬起手指时发生的滚动类型。用户抬起手指后,他们通常希望继续滚动(移动视口),
    但要放慢速度,直到视口停止移动。拖放可以通过覆盖 onFling() 并GestureDetector.OnGestureListener使用滚动对象来实现。

还有一个双击相关的接口,我们一起来看看代码

     private class MySimpleGesture extends SimpleOnGestureListener {   /*** 双击的第二下Touch down时触发* @param e* @return*/ public boolean onDoubleTap(MotionEvent e) {   return super.onDoubleTap(e);   }   /*** 双击的第二下Touch down和up都会触发,可用e.getAction()区分   * @param e* @return*/ public boolean onDoubleTapEvent(MotionEvent e) {   return super.onDoubleTapEvent(e);   }   /*** Touch down时触发 * @param e* @return*/ public boolean onDown(MotionEvent e) {   return super.onDown(e);   }   /*** Touch了滑动一点距离后,up时触发* @param e1* @return*/ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {   return super.onFling(e1, e2, velocityX, velocityY);   }   // Touch了不移动一直Touch down时触发   public void onLongPress(MotionEvent e) {   super.onLongPress(e);   }   // Touch了滑动时触发   public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {   return super.onScroll(e1, e2, distanceX, distanceY);   }   /**  * Touch了还没有滑动时触发  * (1)onDown只要Touch Down一定立刻触发  * (2)Touch Down后过一会没有滑动先触发onShowPress再触发onLongPress  * So: Touch Down后一直不滑动,onDown -> onShowPress -> onLongPress这个顺序触发。  */  public void onShowPress(MotionEvent e) {   super.onShowPress(e);   }   /**  * 两个函数都是在Touch Down后又没有滑动(onScroll),又没有长按(onLongPress),然后Touch Up时触发  * 点击一下非常快的(不滑动)Touch Up: onDown->onSingleTapUp->onSingleTapConfirmed  * 点击一下稍微慢点的(不滑动)Touch Up: onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed   */    public boolean onSingleTapConfirmed(MotionEvent e) {   return super.onSingleTapConfirmed(e);   }   public boolean onSingleTapUp(MotionEvent e) {   return super.onSingleTapUp(e);   }   } 

下面可以看到一个片段,说明了调整尺寸所涉及的基本成分。

四、 如何实现触摸滚动

使用并重写的GestureDetector方法。用于跟踪拖放手势。如果用户在拖放手势后达到内容限制,应用程序会显示“发光”效果

    // The current viewport. This rectangle represents the currently visible// chart domain and range. The viewport is the part of the app that the// user manipulates via touch gestures.private RectF mCurrentViewport =new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);// The current destination rectangle (in pixel coordinates) into which the// chart data should be drawn.private Rect mContentRect;private OverScroller mScroller;private RectF mScrollerStartViewport;...private final GestureDetector.SimpleOnGestureListener mGestureListener= new GestureDetector.SimpleOnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {// Initiates the decay phase of any active edge effects.releaseEdgeEffects();mScrollerStartViewport.set(mCurrentViewport);// Aborts any active scroll animations and invalidates.mScroller.forceFinished(true);ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);return true;}...@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2,float velocityX, float velocityY) {fling((int) -velocityX, (int) -velocityY);return true;}};private void fling(int velocityX, int velocityY) {// Initiates the decay phase of any active edge effects.releaseEdgeEffects();// Flings use math in pixels (as opposed to math based on the viewport).Point surfaceSize = computeScrollSurfaceSize();mScrollerStartViewport.set(mCurrentViewport);int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left -AXIS_X_MIN) / (AXIS_X_MAX - AXIS_X_MIN));int startY = (int) (surfaceSize.y * (AXIS_Y_MAX -mScrollerStartViewport.bottom) / (AXIS_Y_MAX - AXIS_Y_MIN));// Before flinging, aborts the current animation.mScroller.forceFinished(true);// Begins the animationmScroller.fling(// Current scroll positionstartX,startY,velocityX,velocityY,/** Minimum and maximum scroll positions. The minimum scroll* position is generally zero and the maximum scroll position* is generally the content size less the screen size. So if the* content width is 1000 pixels and the screen width is 200* pixels, the maximum scroll offset should be 800 pixels.*/0, surfaceSize.x - mContentRect.width(),0, surfaceSize.y - mContentRect.height(),// The edges of the content. This comes into play when using// the EdgeEffect class to draw "glow" overlays.mContentRect.width() / 2,mContentRect.height() / 2);// Invalidates to trigger computeScroll()ViewCompat.postInvalidateOnAnimation(this);}

当onFling() 调用时postInvalidateOnAnimation(),它会触发computeScroll()更新 x 和 y 值。通常,这是在子视图使用滚动对象对滚动进行动画处理时完成的

我们来看一段缩放代码

    // Custom object that is functionally similar to ScrollerZoomer mZoomer;private PointF mZoomFocalPoint = new PointF();...// If a zoom is in progress (either programmatically or via double// touch), performs the zoom.if (mZoomer.computeZoom()) {float newWidth = (1f - mZoomer.getCurrZoom()) *mScrollerStartViewport.width();float newHeight = (1f - mZoomer.getCurrZoom()) *mScrollerStartViewport.height();float pointWithinViewportX = (mZoomFocalPoint.x -mScrollerStartViewport.left)/ mScrollerStartViewport.width();float pointWithinViewportY = (mZoomFocalPoint.y -mScrollerStartViewport.top)/ mScrollerStartViewport.height();mCurrentViewport.set(mZoomFocalPoint.x - newWidth * pointWithinViewportX,mZoomFocalPoint.y - newHeight * pointWithinViewportY,mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));constrainViewport();needsInvalidate = true;}if (needsInvalidate) {ViewCompat.postInvalidateOnAnimation(this);}

computeScrollSurfaceSize()这是上一个代码片段中调用的方法。计算可滚动表面的当前大小(以像素为单位)。
例如,如果整个图表区域可见,则这是 的当前大小mContentRect。如果图形在两个方向上放大 200%,则显示的尺寸将是水平和垂直尺寸的两倍

有关使用滚动的另一个示例,请参阅该类的源代码ViewPager。它会滚动以响应拖放手势,并使用滚动来实现“适合页面”动画。

    private ScaleGestureDetector mScaleDetector;private float mScaleFactor = 1.f;public MyCustomView(Context mContext){...// View code goes here...mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());}@Overridepublic boolean onTouchEvent(MotionEvent ev) {// Let the ScaleGestureDetector inspect all events.mScaleDetector.onTouchEvent(ev);return true;}@Overridepublic void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.save();canvas.scale(mScaleFactor, mScaleFactor);...// onDraw() code goes here...canvas.restore();}private class ScaleListenerextends ScaleGestureDetector.SimpleOnScaleGestureListener {@Overridepublic boolean onScale(ScaleGestureDetector detector) {mScaleFactor *= detector.getScaleFactor();// Don't let the object get too small or too large.mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));invalidate();return true;}}

五、 如何拖动对象 、缩放对象

以下代码片段允许用户在屏幕上拖动对象。记录活动指针的起始位置,计算指针移动的距离,并将对象移动到新位置。正确处理附加指针的可能性

    // The ‘active pointer’ is the one currently moving our object.private int mActivePointerId = INVALID_POINTER_ID;@Overridepublic boolean onTouchEvent(MotionEvent ev) {// Let the ScaleGestureDetector inspect all events.mScaleDetector.onTouchEvent(ev);final int action = MotionEventCompat.getActionMasked(ev);switch (action) {case MotionEvent.ACTION_DOWN: {final int pointerIndex = MotionEventCompat.getActionIndex(ev);final float x = MotionEventCompat.getX(ev, pointerIndex);final float y = MotionEventCompat.getY(ev, pointerIndex);// Remember where we started (for dragging)mLastTouchX = x;mLastTouchY = y;// Save the ID of this pointer (for dragging)mActivePointerId = MotionEventCompat.getPointerId(ev, 0);break;}case MotionEvent.ACTION_MOVE: {// Find the index of the active pointer and fetch its positionfinal int pointerIndex =MotionEventCompat.findPointerIndex(ev, mActivePointerId);final float x = MotionEventCompat.getX(ev, pointerIndex);final float y = MotionEventCompat.getY(ev, pointerIndex);// Calculate the distance movedfinal float dx = x - mLastTouchX;final float dy = y - mLastTouchY;mPosX += dx;mPosY += dy;invalidate();// Remember this touch position for the next move eventmLastTouchX = x;mLastTouchY = y;break;}case MotionEvent.ACTION_UP: {mActivePointerId = INVALID_POINTER_ID;break;}case MotionEvent.ACTION_CANCEL: {mActivePointerId = INVALID_POINTER_ID;break;}case MotionEvent.ACTION_POINTER_UP: {final int pointerIndex = MotionEventCompat.getActionIndex(ev);final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);if (pointerId == mActivePointerId) {// This was our active pointer going up. Choose a new// active pointer and adjust accordingly.final int newPointerIndex = pointerIndex == 0 ? 1 : 0;mLastTouchX = MotionEventCompat.getX(ev, newPointerIndex);mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex);mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);}break;}}return true;}

基本尺寸示例

下面可以看到一个片段,说明了调整尺寸所涉及的基本成分

    private ScaleGestureDetector mScaleDetector;private float mScaleFactor = 1.f;public MyCustomView(Context mContext){...// View code goes here...mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());}@Overridepublic boolean onTouchEvent(MotionEvent ev) {// Let the ScaleGestureDetector inspect all events.mScaleDetector.onTouchEvent(ev);return true;}@Overridepublic void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.save();canvas.scale(mScaleFactor, mScaleFactor);...// onDraw() code goes here...canvas.restore();}private class ScaleListenerextends ScaleGestureDetector.SimpleOnScaleGestureListener {@Overridepublic boolean onScale(ScaleGestureDetector detector) {mScaleFactor *= detector.getScaleFactor();// Don't let the object get too small or too large.mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));invalidate();return true;}}

还有更多的复杂手势,建议深度学习源码,本文只是一个记录。

六、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

未经允许不得转载

ddd

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/49863.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

昇思25天学习打卡营第17天 | CycleGAN图像风格迁移互换

通过深入学习CycleGAN模型,我对无监督图像到图像的转换技术有了更深的理解。CycleGAN不仅能在没有成对训练样本的情况下实现域之间的转换,而且在保持内容结构的同时成功转换图像风格,这在许多应用中都非常有用,如艺术风格转换、季…

VAE、GAN与Transformer核心公式解析

VAE、GAN与Transformer核心公式解析 VAE、GAN与Transformer:三大深度学习模型的异同解析 【表格】VAE、GAN与Transformer的对比分析 序号对比维度VAE(变分自编码器)GAN(生成对抗网络)Transformer(变换器&…

Python从0到100(四十三):数据库与Django ORM 精讲

前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

SpringMVC 控制层框架-下

五、SpringMVC其他扩展 1. 异常处理机制 1.1 异常处理概念 开发过程中是不可避免地会出现各种异常情况,例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。因此,在开发过程中&a…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 小区小朋友统计(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…

智能编程,一触即发:使用AIGC优化CSS——提升前端开发效率与质量

文章目录 一、AIGC在CSS优化中的应用场景智能代码生成自动布局调整性能优化建议样式和色彩建议 二、使用AIGC优化CSS的具体步骤明确需求选择AIGC工具输入描述或设计稿审查和调整集成和测试 三、AIGC优化CSS的优势与挑战优势:挑战: 《CSS创意项目实践&…

AttributeError: module ‘selenium.webdriver‘ has no attribute ‘PhantomJS‘

AttributeError: module ‘selenium.webdriver‘ has no attribute ‘PhantomJS‘ 目录 AttributeError: module ‘selenium.webdriver‘ has no attribute ‘PhantomJS‘ 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的…

数据结构初阶 · 二叉搜索树

目录 前言: 二叉搜索树的实现 二叉搜索树的基本结构 增 查 中序遍历 删 前言: 在最初学习二叉树的时候,就提及到过单独用树来存储数据是既不如链表也不如顺序表的,二叉树的用处可以用来排序,比如堆排序,也可以用来搜索数据…

java-数据结构与算法-02-数据结构-05-栈

文章目录 1. 栈1. 概述2. 链表实现3. 数组实现4. 应用 2. 习题E01. 有效的括号-Leetcode 20E02. 后缀表达式求值-Leetcode 120E03. 中缀表达式转后缀E04. 双栈模拟队列-Leetcode 232E05. 单队列模拟栈-Leetcode 225 1. 栈 1. 概述 计算机科学中,stack 是一种线性的…

netty入门-3 EventLoop和EventLoopGroup,简单的服务器实现

文章目录 EventLoop和EventLoopGroup服务器与客户端基本使用增加非NIO工人NioEventLoop 处理普通任务与定时任务 结语 EventLoop和EventLoopGroup 二者大概是什么这里不再赘述,前一篇已简述过。 不理解也没关系。 下面会简单使用,看了就能明白是什么 这…

第124天:内网安全-代理 Sockets协议路由不出网后渗透通讯CS-MSF 控制上线

目录 思维导图 环境配置 案例一:网络通讯&控制上线--CS-路由添加&节点建立&协议生成&正反连接 案例二:网络通讯&控制上线--MSF-路由添加&节点建立&协议生成&正反连接 思维导图 环境配置 这里由于系统内存问题我只设…

Python的人脸识别程序

1.录入人脸,输入ID号 haarcascade_frontalface_default.xml # 导入模块 import os import numpy as np import cv2 as cv import cv2face_detector cv2.CascadeClassifier(rD:\Automation_All_Files\OCR\haarcascade_frontalface_default.xml) # 待更改# 为即将…

Windows10+vs 2017中创建WEB API教程

我们如果需要用到web api怎么办?一般来说可以自己开发和去使用别人开发好的api,今天我们来讲一下Windows10vs 2017中创建web Api的教程。目前本教程当中的方法在Win10 VS2017(MVC5)win server2016vs2017,vs2013 vs201…

网安人必须人手一份的《Linux私房教程》,GitHub星标286K!

Linux是一套免费使用和自由传播的操作系统内核,是一个基于POSIX和Unix的多用户、多任务支持多线程和多CPU的操作系统内核。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳…

【iOS】GCD

参考文章:GCD函数和队列原理探索 之前写项目的时候,进行耗时的网络请求使用GCD处理过异步请求,但对一些概念都很模糊,这次就来系统学习一下GCD相关 相关概念 什么是GCD? Grand Center Dispatch简称GCD,是…

ChatTTS真人文本转语音模型,富有韵律与情感,且免费开源

上期图文教程,我们分享了微软TTS真人转语音大模型,但是微软的TTS模型只有针对新用户免费一年,其他用户都是收费的,虽然微软开源了部分TTS的功能,但是针对真人类似的富有情感的TTS模型并没有进行开源,本期介…

软件测试基础1--功能测试

1、什么是软件测试? 软件是控制计算机硬件运行的工具。 软件测试:使用技术手段验证软件是否满足使用需求,为了发现软件功能和需求不相符合的地方,或者寻找实际输出和预期输出之间的差异。 软件测试的目的:减少软件缺陷…

学习笔记之JAVA篇(0724)

p 方法 方法声明格式: [修饰符1 修饰符2 ...] 返回值类型 方法名(形式参数列表){ java语句;......; } 方法调用方式 普通方法对象.方法名(实参列表)静态方法类名.方法名(实参列表) 方法的详…

【YashanDB知识库】YashanDB的JDBC/OCI驱动如何设置字符编码

问题现象 Oracle、Mysql数据库链接串,JDBC驱动连接串可以指定客户端的编码格式: jdbc:mysql://hostname:port/database_name?useUnicodetrue&characterEncodingutf8mb4 jdbc:oracle:thin://hostname:port/service_name?NLS_LANGUAGEAMERICAN&am…

【SQL语句大全(MySQL)】

SQL语法 添加删除修改查询基本查询条件查询分组函数/聚合函数分组查询排序分页查询(限制查询)多表查询连接查询根据年代分类连接查询根据连接方式分类1、内连接2、左外连接3、右外连接 多张表连接的语法格式 嵌套查询 SQL语句书写顺序 添加 INSERT INTO…