Detecting rotate gestures

A common gesture for manipulating objects on the screen is to use the twist or rotate gesture. This is often more natural than entering a rotation value or dragging a cursor.

How to do it...

Another multifinger gesture that is very common is the rotate gesture. For a two-finger rotate, we can do something very similar to what we would do when implementing the ScaleGestureDetector instance. However, we do have to create a RotateGestureDetector instance ourselves as this is not currently provided by the framework. We will create RotateGestureDetector by preforming the following steps:

  1. First, we need an interface that represents our gesture detector's events:
    public interface IOnRotateGestureListener {
      bool OnRotateBegin(RotateGestureDetector detector);
      void OnRotate(RotateGestureDetector detector);
      void OnRotateEnd(RotateGestureDetector detector);
    }
  2. Then, we create the gesture detector type:
    public class RotateGestureDetector {
      private float oldX2, oldY2, oldX1, oldY1;
      private int pointerId1, pointerId2;
      private IOnRotateGestureListener listener;
    
      public RotateGestureDetector(
      IOnRotateGestureListener listener) {
        this.listener = listener;
        pointerId1 = -1;
        pointerId2 = -1;
      }
    
      public bool IsInProgress { get; private set; }
      public float Angle { get; private set; }
      public float FocusX { get; private set; }
      public float FocusY { get; private set; }
    
      public bool OnTouchEvent(MotionEvent e) {
        return true;
      }
    }

Once we have the structure in place, we can now implement the logic that will process the touch events from the view. It is important to ensure that we return the correct bool value when we handle the event to let any other gesture detectors know that it has been handled:

  • After the interface has been defined, we implement the OnTouchEvent() method so that we can process the rotate gesture:
    public bool OnTouchEvent(MotionEvent e) {
      var pointerIndex = e.ActionIndex;
      var pointerId = e.GetPointerId(pointerIndex);
    
      switch (e.ActionMasked) {
      case MotionEventActions.Down:
        pointerId1 = pointerId;
        pointerId2 = -1;
        IsInProgress = false;
        break;
      case MotionEventActions.PointerDown:
        if (pointerId1 != -1 && pointerId2 == -1) {
          pointerId2 = pointerId;
          var index1 = e.FindPointerIndex(pointerId1);
          var index2 = e.FindPointerIndex(pointerId2);
          oldX1 = e.GetX(index1);
          oldY1 = e.GetY(index1);
          oldX2 = e.GetX(index2);
          oldY2 = e.GetY(index2);
    
          FocusX = (oldX1 + oldX2) / 2f;
          FocusY = (oldY1 + oldY2) / 2f;
          Angle = 0;
    
          if (listener != null) {
            IsInProgress = listener.OnRotateBegin(this);
          }
        }
        break;
      case MotionEventActions.Move:
        if (IsInProgress) {
          float newX2, newY2, newX1, newY1;
          var index1 = e.FindPointerIndex(pointerId1);
          var index2 = e.FindPointerIndex(pointerId2);
          newX1 = e.GetX(index1);
          newY1 = e.GetY(index1);
          newX2 = e.GetX(index2);
          newY2 = e.GetY(index2);
    
          FocusX = (newX1 + newX2) / 2f;
          FocusY = (newY1 + newY2) / 2f;
    
          var oldA = Math.Atan2(oldY1 - oldY2, oldX1 - oldX2);
          var newA = Math.Atan2(newY1 - newY2, newX1 - newX2);
          var angle = (float)(oldA - newA);
          Angle = angle * 180.0f / (float)Math.PI;
    
          oldX1 = newX1;
          oldY1 = newY1;
          oldX2 = newX2;
          oldY2 = newY2;
    
          if (listener != null) {
            listener.OnRotate(this);
          }
        }
        break;
      case MotionEventActions.Up:
      case MotionEventActions.PointerUp:
        if (IsInProgress) {
          if (pointerId == pointerId1) {
            pointerId1 = pointerId2;
            pointerId2 = -1;
          }
          else if (pointerId == pointerId2) {
            pointerId2 = -1;
          }
          if (pointerId1 == -1 || pointerId2 == -1) {
            IsInProgress = false;
    
            if (listener != null) {
              listener.OnRotateEnd(this);
            }
          }
        }
        break;
      }
      return true;
    }

Now that we have completed our rotate gesture detector, we can use it to perform a rotation:

  1. Just like we did when implementing the scale gesture detector, we must implement the IOnRotateGestureListener interface:
    public class MyView : View, IOnRotateGestureListener {
      public bool OnRotateBegin(RotateGestureDetector detector) {
        return true;
      }
      public void OnRotate(RotateGestureDetector detector) {
      }
      public void OnRotateEnd(RotateGestureDetector detector) {
      }
    }
  2. Then, we create an instance of RotateGestureDetector:
    rotateDetector = new RotateGestureDetector(this);
  3. Next, we need to override the OnTouchEvent() method to pass the event to the rotate gesture detector:
    public override bool OnTouchEvent(MotionEvent e) {
      var handled = rotateDetector.OnTouchEvent(e);
      return handled || base.OnTouchEvent(e);
    }
  4. Lastly, in the OnRotate() method, we can read the rotation angle since the last update:
    public void OnRotate(RotateGestureDetector detector) {
      rotation = (rotation + detector.Angle) % 360f;
    }

How it works...

Using touch as input is one of the easiest forms of input. Whether it be a stretch, twist, or fling, the user will always find it easier to actually interact with the objects on the screen.

Android provides many of the common forms of gesture detection, but not everything that may be needed has been added to the framework. However, there is no reason why we cannot create custom gesture detectors to make it easier for the user to work with our app.

Tip

Custom gesture detectors can be created to add functionality to an app when a native gesture detector is not available.

We can implement touch event processing in many ways, but following the patterns of the Android framework makes it easier to reuse the code in other apps as well as to make it easier to maintain in the future.

Custom gesture detectors are simple to create, requiring an interface that defines the events of the gesture detector, and the gesture detector itself. The gesture detector receives all the touch events from the view, which are processed. When the gesture actually occurs, the appropriate method on the interface is invoked.

Note

Gesture detectors consist of an interface defining the events and the gesture detector type.

In the case of our rotate gesture detector, as soon as the user places two fingers on the screen, we initiate the rotate gesture. As the user moves their fingers across the screen, we calculate the size of the angle that the fingers have moved in. The angle is calculated from the lines formed from the location of the two fingers on the screen. When the user lifts a finger off the screen, we complete the rotate gesture.

Like with the scale gesture detector, we implement the interface on our view and create an instance of the RotateGetureDetector instance. Then, we override the OnTouchEvent() method, passing the touch event data to the gesture detector. This will result in the interface methods being invoked when a rotate gesture is detected.

We can prevent the rotate gesture from being performed for some reason by returning false from the OnRotateBegin() method. This is useful when we want to allow objects to be rotated on the screen, but only when both fingers are actually touching the object.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.175.180