Android事件高级手势处理

news2024/11/26 10:05:51

原文链接 Android事件高级手势处理

GestureDetector只能帮我们处理并识别一些常用的简单的手势,如点击,双击,长按,滑动(Scroll)和快速滑动(Fling)等,一般情况下,这些足够我们使用了,但有些时候需要一些更为复杂的手势操作,如Translate,Zoom,Scale和Rotate,以及像处理一些多点触控(MultiTouch),这就需要开发人猿自己处理了,本文将讨论一下这些内容。

高级手势识别

移动(Translate/Drag)

这里的移动的意思是让物体随着手指在屏幕上移动,或者叫作拖拽。而且这个只需要一个手指就可以办到,不涉及多点触控。

其实,这个实现起来并不复杂,从onTouchEvent处获得事件后,不断的用MotionEvent的坐标来刷新目标View即可,甚至都不用管具体的事件类型,因为无论是ACTION_DOWN,ACTION_UP或者ACTION_MOVE,都可以提供新的坐标,只管从事件处取坐标然后刷新就可以了。

   draw at (x0, y0);
   
   onTouchEvent(event) {
      x = event.getRawX();
      y = event.getRawY();
      invalidate with (x, y); // will draw at (x, y);
   }

旋转(Rotate)

同样,对于旋转用单个手指也可以办到,以目标View当前的位置为圆心,以手指划过的曲线作为圆弧,由此便可让目标View旋转起来,而且这个手势由单个手指也可以实现,不用管多点触控。

其实可以进一步的做简化,认定屏幕中央为圆心,来计算手势划过的角度,并且为了连惯性,要以事件ACTION_MOVE过程中的增量角度来对View进行旋转,这样会让旋转看起来更顺滑一些,额外的工作是要把事件的坐标进行一下转化,转化为以屏幕中心为原点的坐标。

具体的流程是:

   lastTheta = -1;
   
   onTouchEvent(event) {
   switch (action) {
   case ACTION_DOWN:
      lastX = normalize(event.getX());
      lastY = normalize(event.getY());
      lastTheta = angle(lastX, lastY);
      break;
   case ACTION_MOVE:
     newX = normalize(event.getX());
     newY = normalize(event.getY());
     theta = angle(newX, newY);
     deltaTheta = alpha - beta;
     invalidate to rotate with deltaTheta;
     lastTheta = theta;
     break;
   case ACTION_CANCEL:
   case ACTION_UP:
      we are done.
   }
   
   normalizeX(x) {
      return 2 * x / screenWidth;
   }
   
   normalizeY(y) {
     return 2 * y / screenHeight;
   }
   
   angle(x, y) {
      return atan(y / x);
   }

至于缩放,单个手指无法完成,必须要用两个手指才可以,就涉及到多点触控,所以需要先介绍一下多点触控。

多点触控(MultiTouch)

这个并不复杂,虽然听起来像个神秘高科技,但其实,处理流程并不复杂,主体流程仍然是在onTouchEvent方法中,并且主要的对象仍是MotionEvent,文档里面基本上都说清楚了,要点就是:

  1. MotionEvent对象,会用pointerId和pointerIndex来区分不同的触控点(术语是Pointer)
  2. 事件流是:ACTION_DOWN 称为主触控点(Primary Pointer),然后是ACTION_POINTER_DOWN 另外一个触控点来了(非Primary Pointer),然后是ACTION_MOVE 这里没有显示 区分不同的pointer,需要开发人猿自己去区分,然后是ACTION_POINTER_UP 非主触控点 离开了,最后是ACTION_UP 主触控点离开了。需要注意的是,这是处理事件的逻辑上的顺序 ,真实的事件流,不一定是这样的(ACTION_DOWN肯定是第一个,ACTION_UP肯定 肯定最后一个,但中间的几个有顺序 不定)。
  3. 注意的要点,每次事件来了后,不同的触控点(Pointer)的index并不是固定的,比如上一次MOVE时它在index 0,但下次可能就在index 1,而其Pointer Id是固定的。所以在处理的整个流程中要记录不同Pointer的id,然后获得其index,再用index去取坐标啊之类的数据。
  4. 多点触控,天生就支持,所以即使你不识别多点触控手势(如scale),只关心单个手指手势,在处理的时候,仍要考虑到多点的逻辑。比如说translate时,如果不考虑多点,那么当另外一个手指触摸了屏幕,产生了ACTION_MOVE事件,但它的坐标跟最初产生事件的Pointer差距很远,那么如果不做排除,就可能产生瞬间漂移。

加强版的单触控点手势

对于前面提到的单触控点手势(单手指就能识别的手势)如Translate和Rotate,其实都需要加强一下逻辑,以防止多触控点产生的干扰。

加强版本的单触控点手势处理:

   primaryPointerId = INVALIDE_POINTER_ID;
   
   onTouchEvent(event) {
      switch (event.getActionMasked()) {
         case ACTION_DOWN:
              primaryPointer = event.getPointerId(event.getActionIndex());
              break;
         case ACTION_MOVE:
              pointerIndex = event.findPointerIndex(primaryPointerId);
              x = event.getX(pointerIndex);
              y = event.getY(pointerIndex);
              be happy with x and y;
              break;
          case ACTION_UP:
          case ACTION_CANCEL:
            primaryIndex = INVALIDE_POINTER_ID;
            break;
      }
   }

当然,这里也取决于具体的使用场景,假如允许切换触控点,比如先一个手指拖动,然后另外一个手指点进来,这时第一个手指离开了,如果想继续 拖动的话,就需要更换已保存的primaryPointer。这时会收到ACTION_POINTER_UP,需要在此做切换处理,继续 上面的代码片段,

      secondPointer = INVALIDE_POINTER_ID;
      case ACTION_POINTER_DOWN:
         secondPointer = event.getPointerId(event.getActionIndex());
         break;
      case ACTION_POINTER_UP:
         thisPointer = event.getPointerId(event.getActionIndex());
         if (thisPointer == primaryPointer) {
              primaryPointer = secondPointer;
         }
         secondPointer = INVALIDE_POINTER_ID;
         break;

还有一点需要注意的是,不能简单的只用getPointerCount来作判断,就比如pointer 1先来,然后pointer 2来了,pointer 1又离开了,这时pointerCount仍是1,但是pointer已变化 了,事件的位置就变了,如果不按上述方法处理,将会发生跳变。

缩放(Zoom/Scale)

缩放手势是多点触控的一个非常典型的应用,因为单手无法做出比较合理的手势判断。SDK当中提供了一个用于识别缩放的手势识别器ScaleGestureDetector,它的使用方法与GestureDetector一样,创建对象,塞MotionEvent进去,然后注册listener即可。

但如果,用单独的detector不是很方便,比如已经自己实现了一套手势识别逻辑,现在只想加上Scale,或者其他原因不方便引入ScaleGestureDetector,那么就得自己去做了,也并不是很复杂。

主要思路就是,收集齐两个触控点,记录它们初始的位置,计算它们之间初始的距离,在ACTION_MOVE时,再计算新的距离,新旧距离之比既可当作缩放的比例:

   primaryPointer = INVALIDE_POINTER_ID;
   secondPointer = INVALIDE_POINTER_ID;
   initialSpan = -1;
   startPoint = null;
   onTouchEvent(event) {
         case ACTION_DOWN:
              index = event.getActionIndex();
              primaryPointer = event.getPointerId(index);
              startPoint = Point(event.getX(index), event.getY(index));
              break;
         case ACTION_POINTER_DOWN:
              index = event.getActionIndex();
              secondPointer = event.getPointerId(index);
              sp = Point(event.getX(index), event.getY(index));
              initialSpan = distance(startPoint, sp);
             break;
         case ACTION_MOVE:
              if (event.getPointerCount() > 1) {
                  primaryIndex = event.findPointerIndex(primaryPointer);
                  pp = Point(event.getX(primaryIndex), event.getY(primaryIndex));
                  secondIndex = event.findPointerIndex(secondPointer);
                  sp = Point(event.getX(secondIndex), event.getY(secondIndex));
                  thisDistance = distance(pp, sp);
                  if (thisDistance > ScaledSpan) {
                  	scale = thisDistance / initialSpan;
                  	be happy with scale;
                  }
              }
              break;
         case ACTION_UP:
         case ACTION_CANCEL:
         case ACTION_POINTER_UP:
             thisPointer = event.getPointerId(event.getActionIndex());
             if (thisPointer == primaryPointer) {
                primaryPointer = INVALIDE_POINTER_ID;
             } else if (thisPointer == seocndPointer) {
                secondPointer = INVALIDE_POINTER_ID;
             }
            break;
   }

当然 ,还可以加一些阈值判断,比如当distance大于getScaledTouchSlop,才触发使用scale的逻辑。

参考资料

  • Detecting gestures on Android via GestureDetector
  • Handle multi-touch gestures
  • Drag and scale
  • Drag and drop
  • MotionEvent
  • Gestures and Touch Events
  • android-gesture-detectors
  • SwipeBackLayout
  • GestureViews
  • Sensey

原创不易,打赏点赞在看收藏分享 总要有一个吧

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

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

相关文章

js 实现导入导出功能(FileSave.js)

点击导出按钮弹出文件框导出文件&#xff0c;点击导入选择文件获取文件数据&#xff0c;以下我保存的是json。 html&#xff1a; <div class"layui-inline"> <div class"layui-btn-group"><button type"button" class"la…

文心一言 VS 讯飞星火 VS chatgpt (61)-- 算法导论6.4 5题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;61&#xff09;-- 算法导论6.4 5题 五、证明:在所有元素都不同的情况下&#xff0c;HEAPSORT 的时间复杂度是 Ω (nlgn)。 文心一言&#xff1a; 要证明在所有元素都不同的情况下&#xff0c;HEAPSORT 的时间复杂度是 Ω(n log…

音视频H265编码; Socket通信实现N对N连接代码示例

H.265编码和Socket通信是两个不同的概念&#xff0c;它们分别涉及视频编码和网络通信。在实现N对N连接时&#xff0c;您可以将它们结合起来&#xff0c;但要注意每个方面的具体实现。 H.265编码&#xff08;视频编码&#xff09;&#xff1a; H.265编码涉及将视频数据进行压缩…

速腾robosense-sdk配置和使用方法

官方的安装和配置教程https://github.com/RoboSense-LiDAR/rslidar_sdk/ 1.手动下载sdk&#xff0c;下载tar.gz&#xff0c;然后解压缩:Releases RoboSense-LiDAR/rslidar_sdk GitHub 2.个人电脑连接速腾激光雷达后&#xff0c;确保能够ping通。当连接网线后&#xff0c;电…

使用 appium 进行微信小程序的自动化测试

目录 前言&#xff1a; 微信小程序结构 自动化用例的调整 示例代码 后记 前言&#xff1a; 微信小程序是一种流行的移动应用程序&#xff0c;它在移动设备上提供了丰富的功能和用户体验。为了确保微信小程序的质量和稳定性&#xff0c;自动化测试是必不可少的一环。Appiu…

视频融合平台EasyCVR级联后上级平台播放失败的问题排查与优化

EasyCVR视频融合平台基于云边端智能协同架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制等视频能力与服务&#xff0c;可支持多协议、多类型的海量设备接入与分发。 …

MATLAB数据类型及代码实现

本推文是MATLAB基础与统计实战课程中的S02-1数据类型及代码实现 矩阵(Matrix) MATLAB最基础的数据单位是矩阵。什么是矩阵&#xff1f; 如下图可以看作M行乘以N列的数的组。这就是矩阵最基础的显示 ■区别于其他数据分 析软件或者编程语言的最大一 个特点(如&#xff0c;转…

DOM4j及源码分析

文章目录 DOM4jXML 解析技术原理XML 解析技术介绍 DOM4J 介绍DOM4j 中&#xff0c;获得 Document 对象的方式有三种源码增删改查代码 DOM4j 文档: https://dom4j.github.io/javadoc/1.6.1/ 本地文档: dom4j-1.6.1\docs\index.html XML 解析技术原理 不管是 html 文件还是 x…

OAuth2.0详细介绍与实践(通俗易懂)

一、OAuth2.0介绍 1.1 概述 OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息&#xff08;如用户名与密码&#xff09;&#xff0c;即第三方无需使用用户的用户名与密码就可以申请获…

[Juc进阶]Callable、Future和FutureTask

一、Callable 与 Runnable 先说一下java.lang.Runnable吧&#xff0c;它是一个接口&#xff0c;在它里面只声明了一个run()方法&#xff1a; public interface Runnable {public abstract void run(); }由于run()方法返回值为void类型&#xff0c;所以在执行完任务之后无法返…

多个文件保存位置不同:如何一键批量重命名为相同名称

在日常工作中&#xff0c;我们会经常遇到需要修改文件名称&#xff0c;文件改名方法也是很多种呢&#xff0c;可以手动修改或使用工具批量重命名&#xff0c;一般大家修改文件或文件夹重命名&#xff0c;都是在同一个保存位置&#xff0c;有没有遇到多个文件保存位置不同&#…

抖音账号矩阵系统源码.搭建技术开发分享

技术自研框架开发背景&#xff1a; 抖音账号矩阵系统是一种基于数据分析和管理的全新平台&#xff0c;能够帮助用户更好地管理、扩展和营销抖音账号。 部分源码分享&#xff1a; //计算分页$active_list_all $Video_model->getCount($where);$page_libs new Libs_Pagin…

Android平台如何高效率实现GB28181对接?

技术背景 GB28181协议是一种用于设备状态信息报送的协议&#xff0c;可以在不同设备之间进行通信和数据传输。 在安卓系统上实现GB/T 28181非常必要&#xff0c;GB28181协议实现分两部分&#xff0c;一部分是信令&#xff0c;另外一部分就是媒体数据的编码。 信令主要包括S…

CenterNet Objects as Points 论文学习

论文链接&#xff1a;Objects as Points 1. 解决了什么问题&#xff1f; 目标检测的任务是从图像中检出目标的矩形框。现有的检测方法大多会穷举所有潜在的目标位置&#xff0c;然后做分类。这非常浪费资源、低效率&#xff0c;并且依赖后处理。单阶段方法会在图像上放置大量…

049、事务设计之分布式基本原理

隔离级别 iso定义的隔离级别 可串行化 可重复读 读已提交 读未提交 隔离级别区分的现象 脏读&#xff1a; 一个事务读取另一个未提交的事务所做更改 不可重复度 &#xff1a;同一事务中&#xff0c;前后执行相同的语句&#xff0c;出来的记录不一样 幻读&#xff1a; 同一事务…

ImVoxelNet 论文学习

论文链接&#xff1a;ImVoxelNet: Image to Voxels Projection for Monocular and Multi-View General-Purpose 3D Object Detection 1. 解决了什么问题&#xff1f; RGB 图像成本低、数据源丰富&#xff0c;可以提供场景和物体的视觉信息&#xff0c;但不包括场景几何结构的…

guitar pro2023最新专业的吉他制谱、扒谱软件

guitar pro8是一款打谱编曲软件。我们是会在guitar pro8里面去得到更多自由的打谱设置操作。软件中的改变都很强大好用&#xff0c;是会等等更轻松的编曲制作和设置等等。这里面的所有使用都是很精彩的&#xff0c;是能够去随意的使用更多不同的音符节拍的设置。想要自己进行打…

全志F1C200S嵌入式驱动开发(spi-nand驱动)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 和v3s一样,f1c200s也支持tf卡、spi-nor、spi-nand启动。前面也说过,tf卡由于机械结构的原因,更适合拿来学习,spi-nor和spi-nand比较适合用来进行工业部署和消费娱乐领域。只是s…

使用3DS Max 创建未来派螺栓枪模型

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 步骤 1 创建一个框并将其转换为可编辑多边形&#xff08;右键单击>转换为&#xff1a;>转换为可编辑多边形&#xff09;&#xff0c;然后使用连接添加一系列边循环&#xff0c;如下图所示。 步骤 2 …

vue往window里存数据

1、存 window._getAction () > {return actions}2、取 const parentAction window._getAction()