/utils/src/com/google/marvin/widget/GestureOverlay.java

http://eyes-free.googlecode.com/ · Java · 220 lines · 146 code · 46 blank · 28 comment · 41 complexity · 69eb6876a07a2e8ef34a52ec92b52dcc MD5 · raw file

  1. /*
  2. * Copyright (C) 2010 Google Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.google.marvin.widget;
  17. import android.content.Context;
  18. import android.view.MotionEvent;
  19. import android.view.View;
  20. import com.googlecode.eyesfree.utils.compat.MotionEventCompatUtils;
  21. /**
  22. * A transparent overlay which catches all touch events and uses a call back to
  23. * return the gesture that the user performed. Includes edge events.
  24. *
  25. * @author clchen@google.com (Charles L. Chen), credo@google.com (Tim Credo)
  26. */
  27. public class GestureOverlay extends View {
  28. public static class Gesture {
  29. public static final int UPLEFT = 1;
  30. public static final int UP = 2;
  31. public static final int UPRIGHT = 3;
  32. public static final int LEFT = 4;
  33. public static final int CENTER = 5;
  34. public static final int RIGHT = 6;
  35. public static final int DOWNLEFT = 7;
  36. public static final int DOWN = 8;
  37. public static final int DOWNRIGHT = 9;
  38. public static final int EDGELEFT = 10;
  39. public static final int EDGERIGHT = 11;
  40. }
  41. /**
  42. * The callback interface to be used when a gesture is detected.
  43. */
  44. public interface GestureListener {
  45. public void onGestureStart(int g);
  46. public void onGestureChange(int g);
  47. public void onGestureFinish(int g);
  48. }
  49. private final double left = 0;
  50. private final double upleft = Math.PI * .25;
  51. private final double up = Math.PI * .5;
  52. private final double upright = Math.PI * .75;
  53. private final double downright = -Math.PI * .75;
  54. private final double down = -Math.PI * .5;
  55. private final double downleft = -Math.PI * .25;
  56. private final double right = Math.PI;
  57. private final double rightWrap = -Math.PI;
  58. private GestureListener cb = null;
  59. private double downX;
  60. private double downY;
  61. private int currentGesture;
  62. private int radiusThreshold = 30;
  63. private boolean edgeGesture = false;
  64. int leftEdge, rightEdge;
  65. public GestureOverlay(Context context, GestureListener callback) {
  66. super(context);
  67. cb = callback;
  68. }
  69. public GestureOverlay(Context context) {
  70. super(context);
  71. }
  72. public void setGestureListener(GestureListener callback) {
  73. cb = callback;
  74. }
  75. public void setMinimumRadius(int minRadius) {
  76. radiusThreshold = minRadius;
  77. }
  78. // @Override (only in API 13+)
  79. public boolean onHoverEvent(MotionEvent event) {
  80. return onTouchEvent(event);
  81. }
  82. @Override
  83. public boolean onTouchEvent(MotionEvent event) {
  84. int action = event.getAction();
  85. float x = event.getX();
  86. float y = event.getY();
  87. int prevGesture = -1;
  88. switch (action) {
  89. case MotionEventCompatUtils.ACTION_HOVER_ENTER:
  90. case MotionEvent.ACTION_DOWN:
  91. downX = x;
  92. downY = y;
  93. leftEdge = getWidth() / 7;
  94. rightEdge = getWidth() - getWidth() / 7;
  95. if (x < leftEdge) {
  96. currentGesture = Gesture.EDGELEFT;
  97. edgeGesture = true;
  98. } else if (x > rightEdge) {
  99. currentGesture = Gesture.EDGERIGHT;
  100. edgeGesture = true;
  101. } else {
  102. currentGesture = Gesture.CENTER;
  103. edgeGesture = false;
  104. }
  105. if (cb != null)
  106. cb.onGestureStart(currentGesture);
  107. break;
  108. case MotionEventCompatUtils.ACTION_HOVER_EXIT:
  109. case MotionEvent.ACTION_UP:
  110. prevGesture = currentGesture;
  111. currentGesture = evalMotion(x, y);
  112. // Do some correction if the user lifts up on deadspace
  113. if (currentGesture == -1) {
  114. currentGesture = prevGesture;
  115. }
  116. if (cb != null) {
  117. cb.onGestureFinish(currentGesture);
  118. }
  119. break;
  120. default:
  121. prevGesture = currentGesture;
  122. currentGesture = evalMotion(x, y);
  123. // Do some correction if the user lifts up on deadspace
  124. if (currentGesture == -1) {
  125. currentGesture = prevGesture;
  126. break;
  127. }
  128. if (prevGesture != currentGesture) {
  129. if (cb != null) {
  130. cb.onGestureChange(currentGesture);
  131. }
  132. }
  133. break;
  134. }
  135. return true;
  136. }
  137. public int evalMotion(double x, double y) {
  138. float rTolerance = radiusThreshold;
  139. double thetaTolerance = (Math.PI / 12);
  140. if (edgeGesture) {
  141. if (x < leftEdge && downX < leftEdge) {
  142. return Gesture.EDGELEFT;
  143. } else if (x > rightEdge && downX > rightEdge) {
  144. return Gesture.EDGERIGHT;
  145. } else {
  146. edgeGesture = false;
  147. }
  148. }
  149. double r = Math.sqrt(((downX - x) * (downX - x)) + ((downY - y) * (downY - y)));
  150. if (r < rTolerance) {
  151. return Gesture.CENTER;
  152. }
  153. double theta = Math.atan2(downY - y, downX - x);
  154. if (Math.abs(theta - left) < thetaTolerance) {
  155. return Gesture.LEFT;
  156. } else if (Math.abs(theta - upleft) < thetaTolerance) {
  157. return Gesture.UPLEFT;
  158. } else if (Math.abs(theta - up) < thetaTolerance) {
  159. return Gesture.UP;
  160. } else if (Math.abs(theta - upright) < thetaTolerance) {
  161. return Gesture.UPRIGHT;
  162. } else if (Math.abs(theta - downright) < thetaTolerance) {
  163. return Gesture.DOWNRIGHT;
  164. } else if (Math.abs(theta - down) < thetaTolerance) {
  165. return Gesture.DOWN;
  166. } else if (Math.abs(theta - downleft) < thetaTolerance) {
  167. return Gesture.DOWNLEFT;
  168. } else if ((theta > right - thetaTolerance) || (theta < rightWrap + thetaTolerance)) {
  169. return Gesture.RIGHT;
  170. }
  171. // Off by more than the threshold, so it doesn't count
  172. return -1;
  173. }
  174. }