原文链接 Android事情高档手势处理

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

Android事件高级手势处理

高档手势辨认

移动(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)

Android事件高级手势处理

这个并不杂乱,虽然听起来像个神秘高科技,但其实,处理流程并不杂乱,主体流程仍然是在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

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