Android事件高级手势处理

原文链接 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的逻辑。

参考资料

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
android开发秘籍完整版清晰版 第1 章 android 概述   1 1.1 android 演化史  1 1.2 android 的两面性  2 1.3 运行android 的设备   2 1.3.1 htc 系列机型   4 1.3.2 摩托罗拉系列机型   4 1.3.3 三星系列机型   4 1.3.4 平板电脑   5 1.3.5 其他设备   5 1.4 android 设备的硬件差异   5 1.4.1 屏幕  5 1.4.2 用户输入方式   6 1.4.3 传感器   6 1.5 android 的特点   8 1.5.1 多进程和应用程序微件   8 1.5.2 触摸手势和多点触控   8 1.5.3 硬键盘和软键盘   8 1.6 android 开发   8 1.6.1 如何使用本书中的秘诀   8 .1.6.2 好好设计应用程序   9 1.6.3 保持向前兼容   9 1.6.4 健壮性  10 1.7 软件开发工具包   10 1.7.1 安装与更新   10 1.7.2 软件特性和api 级别   11 1.7.3 利用模拟器或真机调试程序   12 1.7.4 使用android 调试桥   13 1.7.5 签名和发布应用   14 1.8 android market    14 1.8.1 最终用户许可协议   14 1.8.2 提升应用程序的曝光率   15 1.8.3 脱颖而出   15 1.8.4 为应用程序收费   15 1.8.5 管理评论和更新   16 1.8.6 android market 的候补之选   17 第2 章 应用程序基础知识:activity 和intent    18 2.1 android 应用程序预览   18 2.1.1 秘诀1:创建工程并新建activity    19 2.1.2 工程目录结构及自动生成内容   20 2.1.3 android 包和manifest 清单文件   22 2.1.4 重命名应用程序中的部分文件   23 2.2 activity 的生命周期   23 2.2.1 秘诀2:使用其他的生命周期方法  24 2.2.2 秘诀3:强制执行单任务模式   26 2.2.3 秘诀4:强制屏幕方向   26 2.2.4 秘诀5:保存和恢复activity信息   27 2.3 多个activity   28 2.3.1 秘诀6:使用按钮和文本框   28 2.3.2 秘诀7:通过事件启动另外一个activity    29 2.3.3 秘诀8:将语音转换成文本并启动activity 显示结果   32 2.3.4 秘诀9:实现选择列表   34 2.3.5 秘诀10:使用隐式intent 创建activity    35 2.3.6 秘诀11:在activity 间传递基本数据类型   37 第3 章 线程、服务、receiver 以及alert 对话框   40 3.1 线程   40 3.1.1 秘诀12:启动一个辅助线程  40 3.1.2 秘诀13:创建实现runnable接口的activity    44 3.1.3 秘诀14:设置线程优先级   45 3.1.4 秘诀15:取消线程   45 3.1.5 秘诀16:在两个应用程序之间共享线程   46 3.2 线程之间的消息机制:handler   46 3.2.1 秘诀17:从主线程调度runnable 任务   46 3.2.2 秘诀18:使用倒数计时器   49 3.2.3 秘诀19:处理耗时的初始化工作  50 3.3 服务  51 3.4 添加broadcast receiver    56 3.5 应用微件   58 3.6 alert 对话框   60 3.6.1 秘诀23:使用toast 在屏幕上显示简短消息   61 3.6.2 秘诀24:使用alert 对话框   61 3.6.3 秘诀25:在状态栏中显示通知   62 第4 章 用户界面布局   65 4.1 资源目录及其基本属性   65 4.2 view 和viewgroup   67 4.2.1 秘诀27:利用eclipse 编辑器生成布局   68 4.2.2 秘诀28:控制ui 元素的宽度和高度   71 4.2.3 秘诀29:设置相对布局和布局id   73 4.2.4 秘诀30:通过编程声明布局   74 4.2.5 秘诀31:使用独立线程更新布局  75 4.3 文本操作   78 4.3.1 秘诀32:设置和更改文本属性   79 4.3.2 秘诀33:提供文本输入   81 4.3.3 秘诀34:创建表单  82 4.4 其他控件:从按钮到拖动条   83 4.4.1 秘诀35:在表格布局中使用图像按钮   83 4.4.2 秘诀36:使用复选框和开关按钮   86 4.4.3 秘诀37:使用单选按钮   90 4.4.4 秘诀38:创建下拉菜单   90 4.4.5 秘诀39:使用进度条   92 4.4.6 秘诀40:使用拖动条   94 第5 章 用户界面事件   97 5.1 事件处理器和事件监听器   97 5.1.1 秘诀41:截取物理按键事件  97 5.1.2 秘诀42:创建菜单   100 5.1.3 秘诀43:在xml 文件中定义菜单   104 5.1.4 秘诀44:使用搜索键   105 5.1.5 秘诀45:响应触摸事件   107 5.1.6 秘诀46:监听滑动手势   109 5.1.7 秘诀47:使用多点触控   110 5.2 高级用户界面库  113 5.2.1 秘诀48:使用手势   114 5.2.2 秘诀49:绘制3d 图像   117 第6 章 多媒体技术   122 6.1 图像   123 6.2 音频   128 6.2.1 秘诀51:选取和播放音频文件   128 6.2.2 秘诀52:录制音频文件   131 6.2.3 秘诀53:处理原始音频   132 6.2.4 秘诀54:有效使用音频资源   136 6.2.5 秘诀55:添加媒体资源并更新路径   137 6.3 视频  138 第7 章 硬件接口   140 7.1 照相机   140 7.2 其他传感器   145 7.2.1 秘诀57:获取设备旋转姿态  146 7.2.2 秘诀58:使用温度传感器和光传感器   149 7.3 电话  150 7.3.1 秘诀59:使用电话管理器  150 7.3.2 秘诀60:监听电话状态   152 7.3.3 秘诀61:拨打电话号码   154 7.4 蓝牙  154 7.4.1 秘诀62:打开蓝牙   155 7.4.2 秘诀63:搜索蓝牙设备   155 7.4.3 秘诀64:与已绑定的蓝牙设备配对   156 7.4.4 秘诀65:打开蓝牙套接字  156 7.4.5 秘诀66:使用设备振动功能   159 7.4.6 秘诀67:访问无线网络   159 第8 章 网络通信   161 8.1 使用短信息   161 8.2 使用web 内容   169 8.2.1 秘诀69:定制web 浏览器   169 8.2.2 秘诀70:使用http get请求  170 8.2.3 秘诀71:使用http post请求  174 8.3 社交网络  174 第9 章 数据存储方法   184 9.1 shared preferences   184 9.1.1 秘诀73:创建和检索sharedpreferences    184 9.1.2 秘诀74:使用preferences框架   185 9.1.3 秘诀75:基于stored data改变用户界面   187 9.1.4 秘诀76:添加最终用户许可协议   190 9.2 sqlite 数据库   194 9.2.1 秘诀77:创建一个独立的数据库包   194 9.2.2 秘诀78:使用独立的数据库包   197 9.2.3 秘诀79:创建个人日记   200 9.3 内容提供器  204 9.4 保存和载入文件   209 第10 章 基于位置的服务   210 10.1 位置服务入门   210 10.1.1 秘诀81:获取最新位置   212 10.1.2 秘诀82:在位置改变时更新信息   212 10.1.3 秘诀83:列出所有可用的提供器   214 10.1.4 秘诀84:将位置解析为地址(反向地理编码)   216 10.1.5 秘诀85:将地址解析为位置(地理编码)    218 10.2 使用谷歌地图   220 10.2.1 秘诀86:在应用程序中添加谷歌地图   222 10.2.2 秘诀87:在地图上添加标记   224 10.2.3 秘诀88:在地图上添加视图   228 10.2.4 秘诀89:在地图上标记设备的当前位置   230 10.2.5 秘诀90:设置临近警告   231 第11 章 android 高级开发技术   232 11.1 android 的自定义视图   232 11.2 android 的原生组件   238 11.3 android 的安全机制   241 11.4 android 的进程间通信   242 11.5 android 的备份管理器   247 11.5.1 秘诀95:备份运行时数据   247 11.5.2 秘诀96:备份文件到云端   248 11.5.3 秘诀97:触发备份与还原操作   249 11.6 android 的动画功能   250 第12 章 调试   255 12.1 eclipse 内置的调试工具   255 12.1.1 秘诀99:设置运行配置   255 12.1.2 秘诀100:使用ddms   256 12.1.3 秘诀101:断点调试   257 12.2 android sdk 中的调试工具   258 12.2.1 秘诀102:使用android debug bridge 工具   258 12.2.2 秘诀103:使用logcat 工具   259 12.2.3 秘诀104:使用hierarchyviewer 工具   261 12.2.4 秘诀105:使用traceview工具   262 12.3 android 系统调试工具   264

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值