blob: 6e3a00f726322c4878cac87b3d983d35fe03a6fd [file] [log] [blame]
Yang Li35aa84b2009-05-18 18:29:05 -07001/*
Romain Guyd6a463a2009-05-21 23:10:10 -07002 * Copyright (C) 2009 The Android Open Source Project
Yang Li35aa84b2009-05-18 18:29:05 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Romain Guydb567c32009-05-21 16:23:21 -070017package android.gesture;
Yang Li35aa84b2009-05-18 18:29:05 -070018
19import android.content.Context;
Romain Guyd6a463a2009-05-21 23:10:10 -070020import android.content.res.TypedArray;
Yang Li35aa84b2009-05-18 18:29:05 -070021import android.graphics.Canvas;
Yang Li35aa84b2009-05-18 18:29:05 -070022import android.graphics.Paint;
23import android.graphics.Path;
24import android.graphics.Rect;
Romain Guy82f34952009-05-24 18:40:45 -070025import android.graphics.RectF;
Yang Li35aa84b2009-05-18 18:29:05 -070026import android.util.AttributeSet;
27import android.view.MotionEvent;
Romain Guyd6a463a2009-05-21 23:10:10 -070028import android.view.animation.AnimationUtils;
29import android.view.animation.AccelerateDecelerateInterpolator;
Romain Guy82f34952009-05-24 18:40:45 -070030import android.widget.FrameLayout;
31import android.os.SystemClock;
Romain Guy51099c52009-09-11 14:29:15 -070032import android.annotation.Widget;
Romain Guyd6a463a2009-05-21 23:10:10 -070033import com.android.internal.R;
Yang Li35aa84b2009-05-18 18:29:05 -070034
35import java.util.ArrayList;
36
37/**
Romain Guy82f34952009-05-24 18:40:45 -070038 * A transparent overlay for gesture input that can be placed on top of other
39 * widgets or contain other widgets.
Romain Guyd6a463a2009-05-21 23:10:10 -070040 *
Romain Guy82f34952009-05-24 18:40:45 -070041 * @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled
Romain Guyd6a463a2009-05-21 23:10:10 -070042 * @attr ref android.R.styleable#GestureOverlayView_fadeDuration
43 * @attr ref android.R.styleable#GestureOverlayView_fadeOffset
Romain Guyec25df92009-05-25 04:39:37 -070044 * @attr ref android.R.styleable#GestureOverlayView_fadeEnabled
Romain Guy82f34952009-05-24 18:40:45 -070045 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth
46 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeAngleThreshold
47 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeLengthThreshold
48 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeSquarenessThreshold
Romain Guy8d787562009-05-29 15:02:55 -070049 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType
Romain Guy82f34952009-05-24 18:40:45 -070050 * @attr ref android.R.styleable#GestureOverlayView_gestureColor
Romain Guy8d787562009-05-29 15:02:55 -070051 * @attr ref android.R.styleable#GestureOverlayView_orientation
Romain Guy82f34952009-05-24 18:40:45 -070052 * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
Yang Li35aa84b2009-05-18 18:29:05 -070053 */
Romain Guy51099c52009-09-11 14:29:15 -070054@Widget
Romain Guy82f34952009-05-24 18:40:45 -070055public class GestureOverlayView extends FrameLayout {
56 public static final int GESTURE_STROKE_TYPE_SINGLE = 0;
57 public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1;
58
Romain Guye7c36dd2009-05-25 13:51:00 -070059 public static final int ORIENTATION_HORIZONTAL = 0;
60 public static final int ORIENTATION_VERTICAL = 1;
61
Romain Guy82f34952009-05-24 18:40:45 -070062 private static final int FADE_ANIMATION_RATE = 16;
Yang Li35aa84b2009-05-18 18:29:05 -070063 private static final boolean GESTURE_RENDERING_ANTIALIAS = true;
Yang Li35aa84b2009-05-18 18:29:05 -070064 private static final boolean DITHER_FLAG = true;
65
Romain Guy82f34952009-05-24 18:40:45 -070066 private final Paint mGesturePaint = new Paint();
Yang Li35aa84b2009-05-18 18:29:05 -070067
Romain Guy82f34952009-05-24 18:40:45 -070068 private long mFadeDuration = 150;
69 private long mFadeOffset = 420;
Romain Guyd6a463a2009-05-21 23:10:10 -070070 private long mFadingStart;
Romain Guy82f34952009-05-24 18:40:45 -070071 private boolean mFadingHasStarted;
Romain Guyec25df92009-05-25 04:39:37 -070072 private boolean mFadeEnabled = true;
Romain Guyd6a463a2009-05-21 23:10:10 -070073
Romain Guy82f34952009-05-24 18:40:45 -070074 private int mCurrentColor;
Romain Guyd6a463a2009-05-21 23:10:10 -070075 private int mCertainGestureColor = 0xFFFFFF00;
Romain Guy82f34952009-05-24 18:40:45 -070076 private int mUncertainGestureColor = 0x48FFFF00;
77 private float mGestureStrokeWidth = 12.0f;
Romain Guyd6a463a2009-05-21 23:10:10 -070078 private int mInvalidateExtraBorder = 10;
Romain Guydb567c32009-05-21 16:23:21 -070079
Romain Guy82f34952009-05-24 18:40:45 -070080 private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE;
Romain Guyd1c67d42009-05-29 13:53:16 -070081 private float mGestureStrokeLengthThreshold = 50.0f;
Romain Guy82f34952009-05-24 18:40:45 -070082 private float mGestureStrokeSquarenessTreshold = 0.275f;
83 private float mGestureStrokeAngleThreshold = 40.0f;
84
Romain Guye7c36dd2009-05-25 13:51:00 -070085 private int mOrientation = ORIENTATION_VERTICAL;
86
Romain Guyd6a463a2009-05-21 23:10:10 -070087 private final Rect mInvalidRect = new Rect();
88 private final Path mPath = new Path();
Romain Guye705f2c2009-06-16 17:07:21 -070089 private boolean mGestureVisible = true;
Yang Li35aa84b2009-05-18 18:29:05 -070090
91 private float mX;
Yang Li35aa84b2009-05-18 18:29:05 -070092 private float mY;
Romain Guy8d787562009-05-29 15:02:55 -070093
Yang Li35aa84b2009-05-18 18:29:05 -070094 private float mCurveEndX;
Yang Li35aa84b2009-05-18 18:29:05 -070095 private float mCurveEndY;
96
Romain Guy82f34952009-05-24 18:40:45 -070097 private float mTotalLength;
98 private boolean mIsGesturing = false;
Romain Guy834f0392009-06-05 11:24:09 -070099 private boolean mPreviousWasGesturing = false;
Romain Guy82f34952009-05-24 18:40:45 -0700100 private boolean mInterceptEvents = true;
101 private boolean mIsListeningForGestures;
Romain Guy73d25892009-06-09 03:18:10 -0700102 private boolean mResetGesture;
Romain Guy82f34952009-05-24 18:40:45 -0700103
Yang Li35aa84b2009-05-18 18:29:05 -0700104 // current gesture
Romain Guy82f34952009-05-24 18:40:45 -0700105 private Gesture mCurrentGesture;
106 private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
Yang Li35aa84b2009-05-18 18:29:05 -0700107
Romain Guyc5347272009-05-20 10:37:13 -0700108 // TODO: Make this a list of WeakReferences
Romain Guyd6a463a2009-05-21 23:10:10 -0700109 private final ArrayList<OnGestureListener> mOnGestureListeners =
110 new ArrayList<OnGestureListener>();
Romain Guy82f34952009-05-24 18:40:45 -0700111 // TODO: Make this a list of WeakReferences
112 private final ArrayList<OnGesturePerformedListener> mOnGesturePerformedListeners =
113 new ArrayList<OnGesturePerformedListener>();
Romain Guy9af0b4f2009-06-02 21:56:27 -0700114 // TODO: Make this a list of WeakReferences
115 private final ArrayList<OnGesturingListener> mOnGesturingListeners =
116 new ArrayList<OnGesturingListener>();
Romain Guy82f34952009-05-24 18:40:45 -0700117
118 private boolean mHandleGestureActions;
Yang Li35aa84b2009-05-18 18:29:05 -0700119
120 // fading out effect
121 private boolean mIsFadingOut = false;
Romain Guy82f34952009-05-24 18:40:45 -0700122 private float mFadingAlpha = 1.0f;
Romain Guyd6a463a2009-05-21 23:10:10 -0700123 private final AccelerateDecelerateInterpolator mInterpolator =
124 new AccelerateDecelerateInterpolator();
Yang Li35aa84b2009-05-18 18:29:05 -0700125
Romain Guy82f34952009-05-24 18:40:45 -0700126 private final FadeOutRunnable mFadingOut = new FadeOutRunnable();
Yang Li35aa84b2009-05-18 18:29:05 -0700127
Romain Guydb567c32009-05-21 16:23:21 -0700128 public GestureOverlayView(Context context) {
Yang Li35aa84b2009-05-18 18:29:05 -0700129 super(context);
130 init();
131 }
132
Romain Guydb567c32009-05-21 16:23:21 -0700133 public GestureOverlayView(Context context, AttributeSet attrs) {
Romain Guyd6a463a2009-05-21 23:10:10 -0700134 this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle);
135 }
136
Alan Viverette617feb92013-09-09 18:09:13 -0700137 public GestureOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
138 this(context, attrs, defStyleAttr, 0);
139 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700140
Alan Viverette617feb92013-09-09 18:09:13 -0700141 public GestureOverlayView(
142 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
143 super(context, attrs, defStyleAttr, defStyleRes);
144
145 final TypedArray a = context.obtainStyledAttributes(
146 attrs, R.styleable.GestureOverlayView, defStyleAttr, defStyleRes);
Romain Guyd6a463a2009-05-21 23:10:10 -0700147
Romain Guy82f34952009-05-24 18:40:45 -0700148 mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
149 mGestureStrokeWidth);
150 mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
Romain Guyd6a463a2009-05-21 23:10:10 -0700151 mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
152 mCertainGestureColor);
153 mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
154 mUncertainGestureColor);
155 mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
156 mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
Romain Guy82f34952009-05-24 18:40:45 -0700157 mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
158 mGestureStrokeType);
159 mGestureStrokeLengthThreshold = a.getFloat(
160 R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
161 mGestureStrokeLengthThreshold);
162 mGestureStrokeAngleThreshold = a.getFloat(
163 R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
164 mGestureStrokeAngleThreshold);
165 mGestureStrokeSquarenessTreshold = a.getFloat(
166 R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
167 mGestureStrokeSquarenessTreshold);
168 mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
169 mInterceptEvents);
Romain Guyec25df92009-05-25 04:39:37 -0700170 mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
171 mFadeEnabled);
Romain Guye7c36dd2009-05-25 13:51:00 -0700172 mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
Romain Guyd6a463a2009-05-21 23:10:10 -0700173
174 a.recycle();
175
Yang Li35aa84b2009-05-18 18:29:05 -0700176 init();
177 }
178
Romain Guy82f34952009-05-24 18:40:45 -0700179 private void init() {
180 setWillNotDraw(false);
181
182 final Paint gesturePaint = mGesturePaint;
183 gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
184 gesturePaint.setColor(mCertainGestureColor);
185 gesturePaint.setStyle(Paint.Style.STROKE);
186 gesturePaint.setStrokeJoin(Paint.Join.ROUND);
187 gesturePaint.setStrokeCap(Paint.Cap.ROUND);
188 gesturePaint.setStrokeWidth(mGestureStrokeWidth);
189 gesturePaint.setDither(DITHER_FLAG);
190
191 mCurrentColor = mCertainGestureColor;
192 setPaintAlpha(255);
193 }
194
Yang Li35aa84b2009-05-18 18:29:05 -0700195 public ArrayList<GesturePoint> getCurrentStroke() {
Romain Guy82f34952009-05-24 18:40:45 -0700196 return mStrokeBuffer;
Yang Li35aa84b2009-05-18 18:29:05 -0700197 }
198
Romain Guye7c36dd2009-05-25 13:51:00 -0700199 public int getOrientation() {
200 return mOrientation;
201 }
202
203 public void setOrientation(int orientation) {
204 mOrientation = orientation;
205 }
206
Romain Guydb567c32009-05-21 16:23:21 -0700207 public void setGestureColor(int color) {
208 mCertainGestureColor = color;
209 }
210
211 public void setUncertainGestureColor(int color) {
212 mUncertainGestureColor = color;
213 }
214
215 public int getUncertainGestureColor() {
216 return mUncertainGestureColor;
217 }
218
219 public int getGestureColor() {
220 return mCertainGestureColor;
221 }
222
Romain Guy82f34952009-05-24 18:40:45 -0700223 public float getGestureStrokeWidth() {
224 return mGestureStrokeWidth;
Romain Guyd6a463a2009-05-21 23:10:10 -0700225 }
226
Romain Guy82f34952009-05-24 18:40:45 -0700227 public void setGestureStrokeWidth(float gestureStrokeWidth) {
228 mGestureStrokeWidth = gestureStrokeWidth;
229 mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
230 mGesturePaint.setStrokeWidth(gestureStrokeWidth);
Romain Guyd6a463a2009-05-21 23:10:10 -0700231 }
232
Romain Guy82f34952009-05-24 18:40:45 -0700233 public int getGestureStrokeType() {
234 return mGestureStrokeType;
235 }
236
237 public void setGestureStrokeType(int gestureStrokeType) {
238 mGestureStrokeType = gestureStrokeType;
239 }
240
241 public float getGestureStrokeLengthThreshold() {
242 return mGestureStrokeLengthThreshold;
243 }
244
245 public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
246 mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
247 }
248
249 public float getGestureStrokeSquarenessTreshold() {
250 return mGestureStrokeSquarenessTreshold;
251 }
252
253 public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
254 mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
255 }
256
257 public float getGestureStrokeAngleThreshold() {
258 return mGestureStrokeAngleThreshold;
259 }
260
261 public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
262 mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
263 }
264
265 public boolean isEventsInterceptionEnabled() {
266 return mInterceptEvents;
267 }
268
269 public void setEventsInterceptionEnabled(boolean enabled) {
270 mInterceptEvents = enabled;
271 }
272
Romain Guyec25df92009-05-25 04:39:37 -0700273 public boolean isFadeEnabled() {
274 return mFadeEnabled;
275 }
276
277 public void setFadeEnabled(boolean fadeEnabled) {
278 mFadeEnabled = fadeEnabled;
279 }
280
Romain Guy82f34952009-05-24 18:40:45 -0700281 public Gesture getGesture() {
282 return mCurrentGesture;
283 }
284
285 public void setGesture(Gesture gesture) {
Yang Li35aa84b2009-05-18 18:29:05 -0700286 if (mCurrentGesture != null) {
287 clear(false);
288 }
289
Romain Guy82f34952009-05-24 18:40:45 -0700290 setCurrentColor(mCertainGestureColor);
Yang Li35aa84b2009-05-18 18:29:05 -0700291 mCurrentGesture = gesture;
292
Romain Guy82f34952009-05-24 18:40:45 -0700293 final Path path = mCurrentGesture.toPath();
294 final RectF bounds = new RectF();
295 path.computeBounds(bounds, true);
Yang Li35aa84b2009-05-18 18:29:05 -0700296
Romain Guycfbe8cf2009-06-10 01:21:20 -0700297 // TODO: The path should also be scaled to fit inside this view
Romain Guy82f34952009-05-24 18:40:45 -0700298 mPath.rewind();
Romain Guy03f0b212009-06-09 04:15:22 -0700299 mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
300 -bounds.top + (getHeight() - bounds.height()) / 2.0f);
301
302 mResetGesture = true;
Romain Guyc5347272009-05-20 10:37:13 -0700303
Romain Guy82f34952009-05-24 18:40:45 -0700304 invalidate();
Yang Li35aa84b2009-05-18 18:29:05 -0700305 }
306
Romain Guyb973eef2009-06-16 15:53:27 -0700307 public Path getGesturePath() {
308 return mPath;
309 }
310
311 public Path getGesturePath(Path path) {
312 path.set(mPath);
313 return path;
314 }
315
316 public boolean isGestureVisible() {
317 return mGestureVisible;
318 }
319
320 public void setGestureVisible(boolean visible) {
321 mGestureVisible = visible;
322 }
323
324 public long getFadeOffset() {
325 return mFadeOffset;
326 }
327
328 public void setFadeOffset(long fadeOffset) {
329 mFadeOffset = fadeOffset;
330 }
331
Romain Guydb567c32009-05-21 16:23:21 -0700332 public void addOnGestureListener(OnGestureListener listener) {
333 mOnGestureListeners.add(listener);
Yang Li35aa84b2009-05-18 18:29:05 -0700334 }
335
Romain Guydb567c32009-05-21 16:23:21 -0700336 public void removeOnGestureListener(OnGestureListener listener) {
337 mOnGestureListeners.remove(listener);
Yang Li35aa84b2009-05-18 18:29:05 -0700338 }
339
Romain Guyd6a463a2009-05-21 23:10:10 -0700340 public void removeAllOnGestureListeners() {
341 mOnGestureListeners.clear();
342 }
343
Romain Guy82f34952009-05-24 18:40:45 -0700344 public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
345 mOnGesturePerformedListeners.add(listener);
346 if (mOnGesturePerformedListeners.size() > 0) {
347 mHandleGestureActions = true;
Yang Li35aa84b2009-05-18 18:29:05 -0700348 }
Yang Li35aa84b2009-05-18 18:29:05 -0700349 }
350
Romain Guy82f34952009-05-24 18:40:45 -0700351 public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
352 mOnGesturePerformedListeners.remove(listener);
353 if (mOnGesturePerformedListeners.size() <= 0) {
354 mHandleGestureActions = false;
355 }
356 }
357
358 public void removeAllOnGesturePerformedListeners() {
359 mOnGesturePerformedListeners.clear();
360 mHandleGestureActions = false;
361 }
362
Romain Guy9af0b4f2009-06-02 21:56:27 -0700363 public void addOnGesturingListener(OnGesturingListener listener) {
364 mOnGesturingListeners.add(listener);
365 }
366
367 public void removeOnGesturingListener(OnGesturingListener listener) {
368 mOnGesturingListeners.remove(listener);
369 }
370
371 public void removeAllOnGesturingListeners() {
372 mOnGesturingListeners.clear();
373 }
374
Romain Guy82f34952009-05-24 18:40:45 -0700375 public boolean isGesturing() {
376 return mIsGesturing;
377 }
378
379 private void setCurrentColor(int color) {
380 mCurrentColor = color;
381 if (mFadingHasStarted) {
382 setPaintAlpha((int) (255 * mFadingAlpha));
383 } else {
384 setPaintAlpha(255);
385 }
386 invalidate();
387 }
388
Romain Guy27a2b502009-06-09 04:36:14 -0700389 /**
390 * @hide
391 */
392 public Paint getGesturePaint() {
393 return mGesturePaint;
394 }
395
Romain Guy82f34952009-05-24 18:40:45 -0700396 @Override
397 public void draw(Canvas canvas) {
398 super.draw(canvas);
399
Romain Guyb973eef2009-06-16 15:53:27 -0700400 if (mCurrentGesture != null && mGestureVisible) {
Romain Guy82f34952009-05-24 18:40:45 -0700401 canvas.drawPath(mPath, mGesturePaint);
402 }
403 }
404
405 private void setPaintAlpha(int alpha) {
406 alpha += alpha >> 7;
407 final int baseAlpha = mCurrentColor >>> 24;
408 final int useAlpha = baseAlpha * alpha >> 8;
409 mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
410 }
411
412 public void clear(boolean animated) {
Romain Guy03f0b212009-06-09 04:15:22 -0700413 clear(animated, false, true);
Romain Guy82f34952009-05-24 18:40:45 -0700414 }
415
Romain Guy03f0b212009-06-09 04:15:22 -0700416 private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
Romain Guy82f34952009-05-24 18:40:45 -0700417 setPaintAlpha(255);
Romain Guy9edc4e82009-05-25 19:08:31 -0700418 removeCallbacks(mFadingOut);
Romain Guy73d25892009-06-09 03:18:10 -0700419 mResetGesture = false;
Romain Guy9edc4e82009-05-25 19:08:31 -0700420 mFadingOut.fireActionPerformed = fireActionPerformed;
Romain Guy73d25892009-06-09 03:18:10 -0700421 mFadingOut.resetMultipleStrokes = false;
Romain Guy9edc4e82009-05-25 19:08:31 -0700422
Romain Guy82f34952009-05-24 18:40:45 -0700423 if (animated && mCurrentGesture != null) {
Romain Guy9edc4e82009-05-25 19:08:31 -0700424 mFadingAlpha = 1.0f;
425 mIsFadingOut = true;
426 mFadingHasStarted = false;
427 mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
428
Romain Guyd6a463a2009-05-21 23:10:10 -0700429 postDelayed(mFadingOut, mFadeOffset);
Yang Li35aa84b2009-05-18 18:29:05 -0700430 } else {
Romain Guy9edc4e82009-05-25 19:08:31 -0700431 mFadingAlpha = 1.0f;
432 mIsFadingOut = false;
433 mFadingHasStarted = false;
434
Romain Guy03f0b212009-06-09 04:15:22 -0700435 if (immediate) {
Romain Guy9edc4e82009-05-25 19:08:31 -0700436 mCurrentGesture = null;
437 mPath.rewind();
438 invalidate();
Romain Guy03f0b212009-06-09 04:15:22 -0700439 } else if (fireActionPerformed) {
440 postDelayed(mFadingOut, mFadeOffset);
Romain Guy73d25892009-06-09 03:18:10 -0700441 } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
442 mFadingOut.resetMultipleStrokes = true;
443 postDelayed(mFadingOut, mFadeOffset);
Romain Guy03f0b212009-06-09 04:15:22 -0700444 } else {
445 mCurrentGesture = null;
446 mPath.rewind();
447 invalidate();
Romain Guy9edc4e82009-05-25 19:08:31 -0700448 }
Yang Li35aa84b2009-05-18 18:29:05 -0700449 }
450 }
451
Romain Guy82f34952009-05-24 18:40:45 -0700452 public void cancelClearAnimation() {
453 setPaintAlpha(255);
Yang Li35aa84b2009-05-18 18:29:05 -0700454 mIsFadingOut = false;
Romain Guy82f34952009-05-24 18:40:45 -0700455 mFadingHasStarted = false;
Romain Guyd6a463a2009-05-21 23:10:10 -0700456 removeCallbacks(mFadingOut);
Romain Guy82f34952009-05-24 18:40:45 -0700457 mPath.rewind();
458 mCurrentGesture = null;
459 }
460
461 public void cancelGesture() {
462 mIsListeningForGestures = false;
463
464 // add the stroke to the current gesture
465 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
466
467 // pass the event to handlers
468 final long now = SystemClock.uptimeMillis();
469 final MotionEvent event = MotionEvent.obtain(now, now,
470 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
471
472 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
Romain Guy9af0b4f2009-06-02 21:56:27 -0700473 int count = listeners.size();
Romain Guy82f34952009-05-24 18:40:45 -0700474 for (int i = 0; i < count; i++) {
475 listeners.get(i).onGestureCancelled(this, event);
476 }
477
478 event.recycle();
479
480 clear(false);
481 mIsGesturing = false;
Romain Guy834f0392009-06-05 11:24:09 -0700482 mPreviousWasGesturing = false;
Romain Guy82f34952009-05-24 18:40:45 -0700483 mStrokeBuffer.clear();
Romain Guy9af0b4f2009-06-02 21:56:27 -0700484
485 final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners;
486 count = otherListeners.size();
487 for (int i = 0; i < count; i++) {
488 otherListeners.get(i).onGesturingEnded(this);
489 }
Yang Li35aa84b2009-05-18 18:29:05 -0700490 }
491
492 @Override
Romain Guy82f34952009-05-24 18:40:45 -0700493 protected void onDetachedFromWindow() {
Romain Guy46bfc482013-08-16 18:38:29 -0700494 super.onDetachedFromWindow();
Romain Guy82f34952009-05-24 18:40:45 -0700495 cancelClearAnimation();
496 }
497
498 @Override
499 public boolean dispatchTouchEvent(MotionEvent event) {
500 if (isEnabled()) {
Romain Guy834f0392009-06-05 11:24:09 -0700501 final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
502 mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
503 mInterceptEvents;
504
Romain Guy82f34952009-05-24 18:40:45 -0700505 processEvent(event);
506
507 if (cancelDispatch) {
508 event.setAction(MotionEvent.ACTION_CANCEL);
509 }
510
511 super.dispatchTouchEvent(event);
Romain Guy834f0392009-06-05 11:24:09 -0700512
Yang Li35aa84b2009-05-18 18:29:05 -0700513 return true;
514 }
515
Romain Guy82f34952009-05-24 18:40:45 -0700516 return super.dispatchTouchEvent(event);
Romain Guyd6a463a2009-05-21 23:10:10 -0700517 }
518
Romain Guy82f34952009-05-24 18:40:45 -0700519 private boolean processEvent(MotionEvent event) {
Yang Li35aa84b2009-05-18 18:29:05 -0700520 switch (event.getAction()) {
521 case MotionEvent.ACTION_DOWN:
Romain Guye7c36dd2009-05-25 13:51:00 -0700522 touchDown(event);
Romain Guy82f34952009-05-24 18:40:45 -0700523 invalidate();
524 return true;
Yang Li35aa84b2009-05-18 18:29:05 -0700525 case MotionEvent.ACTION_MOVE:
Romain Guy82f34952009-05-24 18:40:45 -0700526 if (mIsListeningForGestures) {
527 Rect rect = touchMove(event);
528 if (rect != null) {
529 invalidate(rect);
530 }
531 return true;
Yang Li35aa84b2009-05-18 18:29:05 -0700532 }
533 break;
534 case MotionEvent.ACTION_UP:
Romain Guy82f34952009-05-24 18:40:45 -0700535 if (mIsListeningForGestures) {
536 touchUp(event, false);
537 invalidate();
538 return true;
539 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700540 break;
541 case MotionEvent.ACTION_CANCEL:
Romain Guy82f34952009-05-24 18:40:45 -0700542 if (mIsListeningForGestures) {
543 touchUp(event, true);
544 invalidate();
545 return true;
546 }
Yang Li35aa84b2009-05-18 18:29:05 -0700547 }
Romain Guy82f34952009-05-24 18:40:45 -0700548
549 return false;
Yang Li35aa84b2009-05-18 18:29:05 -0700550 }
551
Romain Guye7c36dd2009-05-25 13:51:00 -0700552 private void touchDown(MotionEvent event) {
Romain Guy82f34952009-05-24 18:40:45 -0700553 mIsListeningForGestures = true;
554
Yang Li35aa84b2009-05-18 18:29:05 -0700555 float x = event.getX();
556 float y = event.getY();
557
558 mX = x;
559 mY = y;
560
Romain Guy82f34952009-05-24 18:40:45 -0700561 mTotalLength = 0;
562 mIsGesturing = false;
563
Romain Guy73d25892009-06-09 03:18:10 -0700564 if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
Romain Guy82f34952009-05-24 18:40:45 -0700565 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
Romain Guy73d25892009-06-09 03:18:10 -0700566 mResetGesture = false;
Romain Guy82f34952009-05-24 18:40:45 -0700567 mCurrentGesture = null;
568 mPath.rewind();
569 } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
570 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
571 }
572
573 // if there is fading out going on, stop it.
574 if (mFadingHasStarted) {
575 cancelClearAnimation();
576 } else if (mIsFadingOut) {
577 setPaintAlpha(255);
578 mIsFadingOut = false;
579 mFadingHasStarted = false;
580 removeCallbacks(mFadingOut);
581 }
582
Yang Li35aa84b2009-05-18 18:29:05 -0700583 if (mCurrentGesture == null) {
584 mCurrentGesture = new Gesture();
585 }
586
Romain Guy82f34952009-05-24 18:40:45 -0700587 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
Yang Li35aa84b2009-05-18 18:29:05 -0700588 mPath.moveTo(x, y);
589
Romain Guy82f34952009-05-24 18:40:45 -0700590 final int border = mInvalidateExtraBorder;
591 mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
592
Yang Li35aa84b2009-05-18 18:29:05 -0700593 mCurveEndX = x;
594 mCurveEndY = y;
Romain Guyff686ce2009-05-25 01:33:00 -0700595
596 // pass the event to handlers
597 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
598 final int count = listeners.size();
599 for (int i = 0; i < count; i++) {
600 listeners.get(i).onGestureStarted(this, event);
Romain Guy8d787562009-05-29 15:02:55 -0700601 }
Yang Li35aa84b2009-05-18 18:29:05 -0700602 }
603
604 private Rect touchMove(MotionEvent event) {
605 Rect areaToRefresh = null;
Romain Guy8d787562009-05-29 15:02:55 -0700606
Romain Guyd6a463a2009-05-21 23:10:10 -0700607 final float x = event.getX();
608 final float y = event.getY();
Yang Li35aa84b2009-05-18 18:29:05 -0700609
Romain Guyd6a463a2009-05-21 23:10:10 -0700610 final float previousX = mX;
611 final float previousY = mY;
612
613 final float dx = Math.abs(x - previousX);
614 final float dy = Math.abs(y - previousY);
Romain Guy8d787562009-05-29 15:02:55 -0700615
Romain Guyd6a463a2009-05-21 23:10:10 -0700616 if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
617 areaToRefresh = mInvalidRect;
618
Yang Li35aa84b2009-05-18 18:29:05 -0700619 // start with the curve end
Romain Guy82f34952009-05-24 18:40:45 -0700620 final int border = mInvalidateExtraBorder;
621 areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
622 (int) mCurveEndX + border, (int) mCurveEndY + border);
Romain Guy8d787562009-05-29 15:02:55 -0700623
Romain Guy82f34952009-05-24 18:40:45 -0700624 float cX = mCurveEndX = (x + previousX) / 2;
625 float cY = mCurveEndY = (y + previousY) / 2;
Romain Guyd6a463a2009-05-21 23:10:10 -0700626
Romain Guy82f34952009-05-24 18:40:45 -0700627 mPath.quadTo(previousX, previousY, cX, cY);
Romain Guy8d787562009-05-29 15:02:55 -0700628
Yang Li35aa84b2009-05-18 18:29:05 -0700629 // union with the control point of the new curve
Romain Guy82f34952009-05-24 18:40:45 -0700630 areaToRefresh.union((int) previousX - border, (int) previousY - border,
631 (int) previousX + border, (int) previousY + border);
Romain Guy8d787562009-05-29 15:02:55 -0700632
Yang Li35aa84b2009-05-18 18:29:05 -0700633 // union with the end point of the new curve
Romain Guy82f34952009-05-24 18:40:45 -0700634 areaToRefresh.union((int) cX - border, (int) cY - border,
635 (int) cX + border, (int) cY + border);
Yang Li35aa84b2009-05-18 18:29:05 -0700636
Yang Li35aa84b2009-05-18 18:29:05 -0700637 mX = x;
638 mY = y;
Yang Li35aa84b2009-05-18 18:29:05 -0700639
Romain Guyd1c67d42009-05-29 13:53:16 -0700640 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
Yang Li35aa84b2009-05-18 18:29:05 -0700641
Romain Guyd1c67d42009-05-29 13:53:16 -0700642 if (mHandleGestureActions && !mIsGesturing) {
643 mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
Romain Guy82f34952009-05-24 18:40:45 -0700644
Romain Guyd1c67d42009-05-29 13:53:16 -0700645 if (mTotalLength > mGestureStrokeLengthThreshold) {
646 final OrientedBoundingBox box =
Romain Guy46c53122010-02-04 14:19:50 -0800647 GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
Romain Guy82f34952009-05-24 18:40:45 -0700648
Romain Guyd1c67d42009-05-29 13:53:16 -0700649 float angle = Math.abs(box.orientation);
650 if (angle > 90) {
651 angle = 180 - angle;
652 }
Romain Guy82f34952009-05-24 18:40:45 -0700653
Romain Guyd1c67d42009-05-29 13:53:16 -0700654 if (box.squareness > mGestureStrokeSquarenessTreshold ||
655 (mOrientation == ORIENTATION_VERTICAL ?
656 angle < mGestureStrokeAngleThreshold :
657 angle > mGestureStrokeAngleThreshold)) {
Romain Guy82f34952009-05-24 18:40:45 -0700658
Romain Guyd1c67d42009-05-29 13:53:16 -0700659 mIsGesturing = true;
660 setCurrentColor(mCertainGestureColor);
Romain Guy9af0b4f2009-06-02 21:56:27 -0700661
662 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
663 int count = listeners.size();
664 for (int i = 0; i < count; i++) {
665 listeners.get(i).onGesturingStarted(this);
666 }
Romain Guyd1c67d42009-05-29 13:53:16 -0700667 }
Romain Guy82f34952009-05-24 18:40:45 -0700668 }
669 }
Romain Guy82f34952009-05-24 18:40:45 -0700670
Romain Guyd1c67d42009-05-29 13:53:16 -0700671 // pass the event to handlers
672 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
673 final int count = listeners.size();
674 for (int i = 0; i < count; i++) {
675 listeners.get(i).onGesture(this, event);
676 }
677 }
Romain Guyff686ce2009-05-25 01:33:00 -0700678
Yang Li35aa84b2009-05-18 18:29:05 -0700679 return areaToRefresh;
680 }
681
Romain Guyd6a463a2009-05-21 23:10:10 -0700682 private void touchUp(MotionEvent event, boolean cancel) {
Romain Guy82f34952009-05-24 18:40:45 -0700683 mIsListeningForGestures = false;
Yang Li35aa84b2009-05-18 18:29:05 -0700684
Romain Guyd1c67d42009-05-29 13:53:16 -0700685 // A gesture wasn't started or was cancelled
686 if (mCurrentGesture != null) {
687 // add the stroke to the current gesture
688 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
Romain Guyd6a463a2009-05-21 23:10:10 -0700689
Romain Guyd1c67d42009-05-29 13:53:16 -0700690 if (!cancel) {
691 // pass the event to handlers
692 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
693 int count = listeners.size();
694 for (int i = 0; i < count; i++) {
695 listeners.get(i).onGestureEnded(this, event);
696 }
Romain Guy82f34952009-05-24 18:40:45 -0700697
Romain Guy03f0b212009-06-09 04:15:22 -0700698 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
699 false);
Romain Guyd1c67d42009-05-29 13:53:16 -0700700 } else {
701 cancelGesture(event);
702
Romain Guy82f34952009-05-24 18:40:45 -0700703 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700704 } else {
Romain Guyd1c67d42009-05-29 13:53:16 -0700705 cancelGesture(event);
Yang Li35aa84b2009-05-18 18:29:05 -0700706 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700707
Romain Guy8d787562009-05-29 15:02:55 -0700708 mStrokeBuffer.clear();
Romain Guy834f0392009-06-05 11:24:09 -0700709 mPreviousWasGesturing = mIsGesturing;
Romain Guy82f34952009-05-24 18:40:45 -0700710 mIsGesturing = false;
Romain Guy9af0b4f2009-06-02 21:56:27 -0700711
712 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
713 int count = listeners.size();
714 for (int i = 0; i < count; i++) {
715 listeners.get(i).onGesturingEnded(this);
716 }
Yang Li35aa84b2009-05-18 18:29:05 -0700717 }
718
Romain Guyd1c67d42009-05-29 13:53:16 -0700719 private void cancelGesture(MotionEvent event) {
720 // pass the event to handlers
721 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
722 final int count = listeners.size();
723 for (int i = 0; i < count; i++) {
724 listeners.get(i).onGestureCancelled(this, event);
725 }
726
727 clear(false);
728 }
729
Romain Guyec25df92009-05-25 04:39:37 -0700730 private void fireOnGesturePerformed() {
Romain Guy9af0b4f2009-06-02 21:56:27 -0700731 final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
Romain Guyec25df92009-05-25 04:39:37 -0700732 final int count = actionListeners.size();
733 for (int i = 0; i < count; i++) {
Romain Guy9af0b4f2009-06-02 21:56:27 -0700734 actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
Romain Guyec25df92009-05-25 04:39:37 -0700735 }
Romain Guye7c36dd2009-05-25 13:51:00 -0700736 }
Romain Guyec25df92009-05-25 04:39:37 -0700737
Romain Guy82f34952009-05-24 18:40:45 -0700738 private class FadeOutRunnable implements Runnable {
739 boolean fireActionPerformed;
Romain Guy73d25892009-06-09 03:18:10 -0700740 boolean resetMultipleStrokes;
Romain Guy82f34952009-05-24 18:40:45 -0700741
742 public void run() {
743 if (mIsFadingOut) {
744 final long now = AnimationUtils.currentAnimationTimeMillis();
745 final long duration = now - mFadingStart;
746
747 if (duration > mFadeDuration) {
748 if (fireActionPerformed) {
Romain Guyec25df92009-05-25 04:39:37 -0700749 fireOnGesturePerformed();
Romain Guy82f34952009-05-24 18:40:45 -0700750 }
751
Romain Guy834f0392009-06-05 11:24:09 -0700752 mPreviousWasGesturing = false;
Romain Guy82f34952009-05-24 18:40:45 -0700753 mIsFadingOut = false;
754 mFadingHasStarted = false;
755 mPath.rewind();
756 mCurrentGesture = null;
757 setPaintAlpha(255);
758 } else {
759 mFadingHasStarted = true;
760 float interpolatedTime = Math.max(0.0f,
761 Math.min(1.0f, duration / (float) mFadeDuration));
762 mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
763 setPaintAlpha((int) (255 * mFadingAlpha));
764 postDelayed(this, FADE_ANIMATION_RATE);
765 }
Romain Guy73d25892009-06-09 03:18:10 -0700766 } else if (resetMultipleStrokes) {
767 mResetGesture = true;
Romain Guy9edc4e82009-05-25 19:08:31 -0700768 } else {
Romain Guyec25df92009-05-25 04:39:37 -0700769 fireOnGesturePerformed();
770
Romain Guyec25df92009-05-25 04:39:37 -0700771 mFadingHasStarted = false;
772 mPath.rewind();
773 mCurrentGesture = null;
Romain Guy834f0392009-06-05 11:24:09 -0700774 mPreviousWasGesturing = false;
Romain Guyec25df92009-05-25 04:39:37 -0700775 setPaintAlpha(255);
Romain Guy82f34952009-05-24 18:40:45 -0700776 }
Romain Guy9edc4e82009-05-25 19:08:31 -0700777
Romain Guy8d787562009-05-29 15:02:55 -0700778 invalidate();
Romain Guy82f34952009-05-24 18:40:45 -0700779 }
780 }
781
Romain Guy9af0b4f2009-06-02 21:56:27 -0700782 public static interface OnGesturingListener {
783 void onGesturingStarted(GestureOverlayView overlay);
784
785 void onGesturingEnded(GestureOverlayView overlay);
786 }
787
Romain Guydb567c32009-05-21 16:23:21 -0700788 public static interface OnGestureListener {
Romain Guyd6a463a2009-05-21 23:10:10 -0700789 void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
Romain Guydb567c32009-05-21 16:23:21 -0700790
Romain Guyd6a463a2009-05-21 23:10:10 -0700791 void onGesture(GestureOverlayView overlay, MotionEvent event);
Romain Guydb567c32009-05-21 16:23:21 -0700792
Romain Guyd6a463a2009-05-21 23:10:10 -0700793 void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
794
795 void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
Romain Guydb567c32009-05-21 16:23:21 -0700796 }
Romain Guy82f34952009-05-24 18:40:45 -0700797
798 public static interface OnGesturePerformedListener {
799 void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
800 }
Yang Li35aa84b2009-05-18 18:29:05 -0700801}