blob: 2d47f289b38efa676922d710229465d992089c39 [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
137 public GestureOverlayView(Context context, AttributeSet attrs, int defStyle) {
138 super(context, attrs, defStyle);
139
140 TypedArray a = context.obtainStyledAttributes(attrs,
141 R.styleable.GestureOverlayView, defStyle, 0);
142
Romain Guy82f34952009-05-24 18:40:45 -0700143 mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
144 mGestureStrokeWidth);
145 mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
Romain Guyd6a463a2009-05-21 23:10:10 -0700146 mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
147 mCertainGestureColor);
148 mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
149 mUncertainGestureColor);
150 mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
151 mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
Romain Guy82f34952009-05-24 18:40:45 -0700152 mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
153 mGestureStrokeType);
154 mGestureStrokeLengthThreshold = a.getFloat(
155 R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
156 mGestureStrokeLengthThreshold);
157 mGestureStrokeAngleThreshold = a.getFloat(
158 R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
159 mGestureStrokeAngleThreshold);
160 mGestureStrokeSquarenessTreshold = a.getFloat(
161 R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
162 mGestureStrokeSquarenessTreshold);
163 mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
164 mInterceptEvents);
Romain Guyec25df92009-05-25 04:39:37 -0700165 mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
166 mFadeEnabled);
Romain Guye7c36dd2009-05-25 13:51:00 -0700167 mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
Romain Guyd6a463a2009-05-21 23:10:10 -0700168
169 a.recycle();
170
Yang Li35aa84b2009-05-18 18:29:05 -0700171 init();
172 }
173
Romain Guy82f34952009-05-24 18:40:45 -0700174 private void init() {
175 setWillNotDraw(false);
176
177 final Paint gesturePaint = mGesturePaint;
178 gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
179 gesturePaint.setColor(mCertainGestureColor);
180 gesturePaint.setStyle(Paint.Style.STROKE);
181 gesturePaint.setStrokeJoin(Paint.Join.ROUND);
182 gesturePaint.setStrokeCap(Paint.Cap.ROUND);
183 gesturePaint.setStrokeWidth(mGestureStrokeWidth);
184 gesturePaint.setDither(DITHER_FLAG);
185
186 mCurrentColor = mCertainGestureColor;
187 setPaintAlpha(255);
188 }
189
Yang Li35aa84b2009-05-18 18:29:05 -0700190 public ArrayList<GesturePoint> getCurrentStroke() {
Romain Guy82f34952009-05-24 18:40:45 -0700191 return mStrokeBuffer;
Yang Li35aa84b2009-05-18 18:29:05 -0700192 }
193
Romain Guye7c36dd2009-05-25 13:51:00 -0700194 public int getOrientation() {
195 return mOrientation;
196 }
197
198 public void setOrientation(int orientation) {
199 mOrientation = orientation;
200 }
201
Romain Guydb567c32009-05-21 16:23:21 -0700202 public void setGestureColor(int color) {
203 mCertainGestureColor = color;
204 }
205
206 public void setUncertainGestureColor(int color) {
207 mUncertainGestureColor = color;
208 }
209
210 public int getUncertainGestureColor() {
211 return mUncertainGestureColor;
212 }
213
214 public int getGestureColor() {
215 return mCertainGestureColor;
216 }
217
Romain Guy82f34952009-05-24 18:40:45 -0700218 public float getGestureStrokeWidth() {
219 return mGestureStrokeWidth;
Romain Guyd6a463a2009-05-21 23:10:10 -0700220 }
221
Romain Guy82f34952009-05-24 18:40:45 -0700222 public void setGestureStrokeWidth(float gestureStrokeWidth) {
223 mGestureStrokeWidth = gestureStrokeWidth;
224 mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
225 mGesturePaint.setStrokeWidth(gestureStrokeWidth);
Romain Guyd6a463a2009-05-21 23:10:10 -0700226 }
227
Romain Guy82f34952009-05-24 18:40:45 -0700228 public int getGestureStrokeType() {
229 return mGestureStrokeType;
230 }
231
232 public void setGestureStrokeType(int gestureStrokeType) {
233 mGestureStrokeType = gestureStrokeType;
234 }
235
236 public float getGestureStrokeLengthThreshold() {
237 return mGestureStrokeLengthThreshold;
238 }
239
240 public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
241 mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
242 }
243
244 public float getGestureStrokeSquarenessTreshold() {
245 return mGestureStrokeSquarenessTreshold;
246 }
247
248 public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
249 mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
250 }
251
252 public float getGestureStrokeAngleThreshold() {
253 return mGestureStrokeAngleThreshold;
254 }
255
256 public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
257 mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
258 }
259
260 public boolean isEventsInterceptionEnabled() {
261 return mInterceptEvents;
262 }
263
264 public void setEventsInterceptionEnabled(boolean enabled) {
265 mInterceptEvents = enabled;
266 }
267
Romain Guyec25df92009-05-25 04:39:37 -0700268 public boolean isFadeEnabled() {
269 return mFadeEnabled;
270 }
271
272 public void setFadeEnabled(boolean fadeEnabled) {
273 mFadeEnabled = fadeEnabled;
274 }
275
Romain Guy82f34952009-05-24 18:40:45 -0700276 public Gesture getGesture() {
277 return mCurrentGesture;
278 }
279
280 public void setGesture(Gesture gesture) {
Yang Li35aa84b2009-05-18 18:29:05 -0700281 if (mCurrentGesture != null) {
282 clear(false);
283 }
284
Romain Guy82f34952009-05-24 18:40:45 -0700285 setCurrentColor(mCertainGestureColor);
Yang Li35aa84b2009-05-18 18:29:05 -0700286 mCurrentGesture = gesture;
287
Romain Guy82f34952009-05-24 18:40:45 -0700288 final Path path = mCurrentGesture.toPath();
289 final RectF bounds = new RectF();
290 path.computeBounds(bounds, true);
Yang Li35aa84b2009-05-18 18:29:05 -0700291
Romain Guycfbe8cf2009-06-10 01:21:20 -0700292 // TODO: The path should also be scaled to fit inside this view
Romain Guy82f34952009-05-24 18:40:45 -0700293 mPath.rewind();
Romain Guy03f0b212009-06-09 04:15:22 -0700294 mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
295 -bounds.top + (getHeight() - bounds.height()) / 2.0f);
296
297 mResetGesture = true;
Romain Guyc5347272009-05-20 10:37:13 -0700298
Romain Guy82f34952009-05-24 18:40:45 -0700299 invalidate();
Yang Li35aa84b2009-05-18 18:29:05 -0700300 }
301
Romain Guyb973eef2009-06-16 15:53:27 -0700302 public Path getGesturePath() {
303 return mPath;
304 }
305
306 public Path getGesturePath(Path path) {
307 path.set(mPath);
308 return path;
309 }
310
311 public boolean isGestureVisible() {
312 return mGestureVisible;
313 }
314
315 public void setGestureVisible(boolean visible) {
316 mGestureVisible = visible;
317 }
318
319 public long getFadeOffset() {
320 return mFadeOffset;
321 }
322
323 public void setFadeOffset(long fadeOffset) {
324 mFadeOffset = fadeOffset;
325 }
326
Romain Guydb567c32009-05-21 16:23:21 -0700327 public void addOnGestureListener(OnGestureListener listener) {
328 mOnGestureListeners.add(listener);
Yang Li35aa84b2009-05-18 18:29:05 -0700329 }
330
Romain Guydb567c32009-05-21 16:23:21 -0700331 public void removeOnGestureListener(OnGestureListener listener) {
332 mOnGestureListeners.remove(listener);
Yang Li35aa84b2009-05-18 18:29:05 -0700333 }
334
Romain Guyd6a463a2009-05-21 23:10:10 -0700335 public void removeAllOnGestureListeners() {
336 mOnGestureListeners.clear();
337 }
338
Romain Guy82f34952009-05-24 18:40:45 -0700339 public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
340 mOnGesturePerformedListeners.add(listener);
341 if (mOnGesturePerformedListeners.size() > 0) {
342 mHandleGestureActions = true;
Yang Li35aa84b2009-05-18 18:29:05 -0700343 }
Yang Li35aa84b2009-05-18 18:29:05 -0700344 }
345
Romain Guy82f34952009-05-24 18:40:45 -0700346 public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
347 mOnGesturePerformedListeners.remove(listener);
348 if (mOnGesturePerformedListeners.size() <= 0) {
349 mHandleGestureActions = false;
350 }
351 }
352
353 public void removeAllOnGesturePerformedListeners() {
354 mOnGesturePerformedListeners.clear();
355 mHandleGestureActions = false;
356 }
357
Romain Guy9af0b4f2009-06-02 21:56:27 -0700358 public void addOnGesturingListener(OnGesturingListener listener) {
359 mOnGesturingListeners.add(listener);
360 }
361
362 public void removeOnGesturingListener(OnGesturingListener listener) {
363 mOnGesturingListeners.remove(listener);
364 }
365
366 public void removeAllOnGesturingListeners() {
367 mOnGesturingListeners.clear();
368 }
369
Romain Guy82f34952009-05-24 18:40:45 -0700370 public boolean isGesturing() {
371 return mIsGesturing;
372 }
373
374 private void setCurrentColor(int color) {
375 mCurrentColor = color;
376 if (mFadingHasStarted) {
377 setPaintAlpha((int) (255 * mFadingAlpha));
378 } else {
379 setPaintAlpha(255);
380 }
381 invalidate();
382 }
383
Romain Guy27a2b502009-06-09 04:36:14 -0700384 /**
385 * @hide
386 */
387 public Paint getGesturePaint() {
388 return mGesturePaint;
389 }
390
Romain Guy82f34952009-05-24 18:40:45 -0700391 @Override
392 public void draw(Canvas canvas) {
393 super.draw(canvas);
394
Romain Guyb973eef2009-06-16 15:53:27 -0700395 if (mCurrentGesture != null && mGestureVisible) {
Romain Guy82f34952009-05-24 18:40:45 -0700396 canvas.drawPath(mPath, mGesturePaint);
397 }
398 }
399
400 private void setPaintAlpha(int alpha) {
401 alpha += alpha >> 7;
402 final int baseAlpha = mCurrentColor >>> 24;
403 final int useAlpha = baseAlpha * alpha >> 8;
404 mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
405 }
406
407 public void clear(boolean animated) {
Romain Guy03f0b212009-06-09 04:15:22 -0700408 clear(animated, false, true);
Romain Guy82f34952009-05-24 18:40:45 -0700409 }
410
Romain Guy03f0b212009-06-09 04:15:22 -0700411 private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
Romain Guy82f34952009-05-24 18:40:45 -0700412 setPaintAlpha(255);
Romain Guy9edc4e82009-05-25 19:08:31 -0700413 removeCallbacks(mFadingOut);
Romain Guy73d25892009-06-09 03:18:10 -0700414 mResetGesture = false;
Romain Guy9edc4e82009-05-25 19:08:31 -0700415 mFadingOut.fireActionPerformed = fireActionPerformed;
Romain Guy73d25892009-06-09 03:18:10 -0700416 mFadingOut.resetMultipleStrokes = false;
Romain Guy9edc4e82009-05-25 19:08:31 -0700417
Romain Guy82f34952009-05-24 18:40:45 -0700418 if (animated && mCurrentGesture != null) {
Romain Guy9edc4e82009-05-25 19:08:31 -0700419 mFadingAlpha = 1.0f;
420 mIsFadingOut = true;
421 mFadingHasStarted = false;
422 mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
423
Romain Guyd6a463a2009-05-21 23:10:10 -0700424 postDelayed(mFadingOut, mFadeOffset);
Yang Li35aa84b2009-05-18 18:29:05 -0700425 } else {
Romain Guy9edc4e82009-05-25 19:08:31 -0700426 mFadingAlpha = 1.0f;
427 mIsFadingOut = false;
428 mFadingHasStarted = false;
429
Romain Guy03f0b212009-06-09 04:15:22 -0700430 if (immediate) {
Romain Guy9edc4e82009-05-25 19:08:31 -0700431 mCurrentGesture = null;
432 mPath.rewind();
433 invalidate();
Romain Guy03f0b212009-06-09 04:15:22 -0700434 } else if (fireActionPerformed) {
435 postDelayed(mFadingOut, mFadeOffset);
Romain Guy73d25892009-06-09 03:18:10 -0700436 } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
437 mFadingOut.resetMultipleStrokes = true;
438 postDelayed(mFadingOut, mFadeOffset);
Romain Guy03f0b212009-06-09 04:15:22 -0700439 } else {
440 mCurrentGesture = null;
441 mPath.rewind();
442 invalidate();
Romain Guy9edc4e82009-05-25 19:08:31 -0700443 }
Yang Li35aa84b2009-05-18 18:29:05 -0700444 }
445 }
446
Romain Guy82f34952009-05-24 18:40:45 -0700447 public void cancelClearAnimation() {
448 setPaintAlpha(255);
Yang Li35aa84b2009-05-18 18:29:05 -0700449 mIsFadingOut = false;
Romain Guy82f34952009-05-24 18:40:45 -0700450 mFadingHasStarted = false;
Romain Guyd6a463a2009-05-21 23:10:10 -0700451 removeCallbacks(mFadingOut);
Romain Guy82f34952009-05-24 18:40:45 -0700452 mPath.rewind();
453 mCurrentGesture = null;
454 }
455
456 public void cancelGesture() {
457 mIsListeningForGestures = false;
458
459 // add the stroke to the current gesture
460 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
461
462 // pass the event to handlers
463 final long now = SystemClock.uptimeMillis();
464 final MotionEvent event = MotionEvent.obtain(now, now,
465 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
466
467 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
Romain Guy9af0b4f2009-06-02 21:56:27 -0700468 int count = listeners.size();
Romain Guy82f34952009-05-24 18:40:45 -0700469 for (int i = 0; i < count; i++) {
470 listeners.get(i).onGestureCancelled(this, event);
471 }
472
473 event.recycle();
474
475 clear(false);
476 mIsGesturing = false;
Romain Guy834f0392009-06-05 11:24:09 -0700477 mPreviousWasGesturing = false;
Romain Guy82f34952009-05-24 18:40:45 -0700478 mStrokeBuffer.clear();
Romain Guy9af0b4f2009-06-02 21:56:27 -0700479
480 final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners;
481 count = otherListeners.size();
482 for (int i = 0; i < count; i++) {
483 otherListeners.get(i).onGesturingEnded(this);
484 }
Yang Li35aa84b2009-05-18 18:29:05 -0700485 }
486
487 @Override
Romain Guy82f34952009-05-24 18:40:45 -0700488 protected void onDetachedFromWindow() {
Romain Guy46bfc482013-08-16 18:38:29 -0700489 super.onDetachedFromWindow();
Romain Guy82f34952009-05-24 18:40:45 -0700490 cancelClearAnimation();
491 }
492
493 @Override
494 public boolean dispatchTouchEvent(MotionEvent event) {
495 if (isEnabled()) {
Romain Guy834f0392009-06-05 11:24:09 -0700496 final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
497 mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
498 mInterceptEvents;
499
Romain Guy82f34952009-05-24 18:40:45 -0700500 processEvent(event);
501
502 if (cancelDispatch) {
503 event.setAction(MotionEvent.ACTION_CANCEL);
504 }
505
506 super.dispatchTouchEvent(event);
Romain Guy834f0392009-06-05 11:24:09 -0700507
Yang Li35aa84b2009-05-18 18:29:05 -0700508 return true;
509 }
510
Romain Guy82f34952009-05-24 18:40:45 -0700511 return super.dispatchTouchEvent(event);
Romain Guyd6a463a2009-05-21 23:10:10 -0700512 }
513
Romain Guy82f34952009-05-24 18:40:45 -0700514 private boolean processEvent(MotionEvent event) {
Yang Li35aa84b2009-05-18 18:29:05 -0700515 switch (event.getAction()) {
516 case MotionEvent.ACTION_DOWN:
Romain Guye7c36dd2009-05-25 13:51:00 -0700517 touchDown(event);
Romain Guy82f34952009-05-24 18:40:45 -0700518 invalidate();
519 return true;
Yang Li35aa84b2009-05-18 18:29:05 -0700520 case MotionEvent.ACTION_MOVE:
Romain Guy82f34952009-05-24 18:40:45 -0700521 if (mIsListeningForGestures) {
522 Rect rect = touchMove(event);
523 if (rect != null) {
524 invalidate(rect);
525 }
526 return true;
Yang Li35aa84b2009-05-18 18:29:05 -0700527 }
528 break;
529 case MotionEvent.ACTION_UP:
Romain Guy82f34952009-05-24 18:40:45 -0700530 if (mIsListeningForGestures) {
531 touchUp(event, false);
532 invalidate();
533 return true;
534 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700535 break;
536 case MotionEvent.ACTION_CANCEL:
Romain Guy82f34952009-05-24 18:40:45 -0700537 if (mIsListeningForGestures) {
538 touchUp(event, true);
539 invalidate();
540 return true;
541 }
Yang Li35aa84b2009-05-18 18:29:05 -0700542 }
Romain Guy82f34952009-05-24 18:40:45 -0700543
544 return false;
Yang Li35aa84b2009-05-18 18:29:05 -0700545 }
546
Romain Guye7c36dd2009-05-25 13:51:00 -0700547 private void touchDown(MotionEvent event) {
Romain Guy82f34952009-05-24 18:40:45 -0700548 mIsListeningForGestures = true;
549
Yang Li35aa84b2009-05-18 18:29:05 -0700550 float x = event.getX();
551 float y = event.getY();
552
553 mX = x;
554 mY = y;
555
Romain Guy82f34952009-05-24 18:40:45 -0700556 mTotalLength = 0;
557 mIsGesturing = false;
558
Romain Guy73d25892009-06-09 03:18:10 -0700559 if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
Romain Guy82f34952009-05-24 18:40:45 -0700560 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
Romain Guy73d25892009-06-09 03:18:10 -0700561 mResetGesture = false;
Romain Guy82f34952009-05-24 18:40:45 -0700562 mCurrentGesture = null;
563 mPath.rewind();
564 } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
565 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
566 }
567
568 // if there is fading out going on, stop it.
569 if (mFadingHasStarted) {
570 cancelClearAnimation();
571 } else if (mIsFadingOut) {
572 setPaintAlpha(255);
573 mIsFadingOut = false;
574 mFadingHasStarted = false;
575 removeCallbacks(mFadingOut);
576 }
577
Yang Li35aa84b2009-05-18 18:29:05 -0700578 if (mCurrentGesture == null) {
579 mCurrentGesture = new Gesture();
580 }
581
Romain Guy82f34952009-05-24 18:40:45 -0700582 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
Yang Li35aa84b2009-05-18 18:29:05 -0700583 mPath.moveTo(x, y);
584
Romain Guy82f34952009-05-24 18:40:45 -0700585 final int border = mInvalidateExtraBorder;
586 mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
587
Yang Li35aa84b2009-05-18 18:29:05 -0700588 mCurveEndX = x;
589 mCurveEndY = y;
Romain Guyff686ce2009-05-25 01:33:00 -0700590
591 // pass the event to handlers
592 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
593 final int count = listeners.size();
594 for (int i = 0; i < count; i++) {
595 listeners.get(i).onGestureStarted(this, event);
Romain Guy8d787562009-05-29 15:02:55 -0700596 }
Yang Li35aa84b2009-05-18 18:29:05 -0700597 }
598
599 private Rect touchMove(MotionEvent event) {
600 Rect areaToRefresh = null;
Romain Guy8d787562009-05-29 15:02:55 -0700601
Romain Guyd6a463a2009-05-21 23:10:10 -0700602 final float x = event.getX();
603 final float y = event.getY();
Yang Li35aa84b2009-05-18 18:29:05 -0700604
Romain Guyd6a463a2009-05-21 23:10:10 -0700605 final float previousX = mX;
606 final float previousY = mY;
607
608 final float dx = Math.abs(x - previousX);
609 final float dy = Math.abs(y - previousY);
Romain Guy8d787562009-05-29 15:02:55 -0700610
Romain Guyd6a463a2009-05-21 23:10:10 -0700611 if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
612 areaToRefresh = mInvalidRect;
613
Yang Li35aa84b2009-05-18 18:29:05 -0700614 // start with the curve end
Romain Guy82f34952009-05-24 18:40:45 -0700615 final int border = mInvalidateExtraBorder;
616 areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
617 (int) mCurveEndX + border, (int) mCurveEndY + border);
Romain Guy8d787562009-05-29 15:02:55 -0700618
Romain Guy82f34952009-05-24 18:40:45 -0700619 float cX = mCurveEndX = (x + previousX) / 2;
620 float cY = mCurveEndY = (y + previousY) / 2;
Romain Guyd6a463a2009-05-21 23:10:10 -0700621
Romain Guy82f34952009-05-24 18:40:45 -0700622 mPath.quadTo(previousX, previousY, cX, cY);
Romain Guy8d787562009-05-29 15:02:55 -0700623
Yang Li35aa84b2009-05-18 18:29:05 -0700624 // union with the control point of the new curve
Romain Guy82f34952009-05-24 18:40:45 -0700625 areaToRefresh.union((int) previousX - border, (int) previousY - border,
626 (int) previousX + border, (int) previousY + border);
Romain Guy8d787562009-05-29 15:02:55 -0700627
Yang Li35aa84b2009-05-18 18:29:05 -0700628 // union with the end point of the new curve
Romain Guy82f34952009-05-24 18:40:45 -0700629 areaToRefresh.union((int) cX - border, (int) cY - border,
630 (int) cX + border, (int) cY + border);
Yang Li35aa84b2009-05-18 18:29:05 -0700631
Yang Li35aa84b2009-05-18 18:29:05 -0700632 mX = x;
633 mY = y;
Yang Li35aa84b2009-05-18 18:29:05 -0700634
Romain Guyd1c67d42009-05-29 13:53:16 -0700635 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
Yang Li35aa84b2009-05-18 18:29:05 -0700636
Romain Guyd1c67d42009-05-29 13:53:16 -0700637 if (mHandleGestureActions && !mIsGesturing) {
638 mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
Romain Guy82f34952009-05-24 18:40:45 -0700639
Romain Guyd1c67d42009-05-29 13:53:16 -0700640 if (mTotalLength > mGestureStrokeLengthThreshold) {
641 final OrientedBoundingBox box =
Romain Guy46c53122010-02-04 14:19:50 -0800642 GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
Romain Guy82f34952009-05-24 18:40:45 -0700643
Romain Guyd1c67d42009-05-29 13:53:16 -0700644 float angle = Math.abs(box.orientation);
645 if (angle > 90) {
646 angle = 180 - angle;
647 }
Romain Guy82f34952009-05-24 18:40:45 -0700648
Romain Guyd1c67d42009-05-29 13:53:16 -0700649 if (box.squareness > mGestureStrokeSquarenessTreshold ||
650 (mOrientation == ORIENTATION_VERTICAL ?
651 angle < mGestureStrokeAngleThreshold :
652 angle > mGestureStrokeAngleThreshold)) {
Romain Guy82f34952009-05-24 18:40:45 -0700653
Romain Guyd1c67d42009-05-29 13:53:16 -0700654 mIsGesturing = true;
655 setCurrentColor(mCertainGestureColor);
Romain Guy9af0b4f2009-06-02 21:56:27 -0700656
657 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
658 int count = listeners.size();
659 for (int i = 0; i < count; i++) {
660 listeners.get(i).onGesturingStarted(this);
661 }
Romain Guyd1c67d42009-05-29 13:53:16 -0700662 }
Romain Guy82f34952009-05-24 18:40:45 -0700663 }
664 }
Romain Guy82f34952009-05-24 18:40:45 -0700665
Romain Guyd1c67d42009-05-29 13:53:16 -0700666 // pass the event to handlers
667 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
668 final int count = listeners.size();
669 for (int i = 0; i < count; i++) {
670 listeners.get(i).onGesture(this, event);
671 }
672 }
Romain Guyff686ce2009-05-25 01:33:00 -0700673
Yang Li35aa84b2009-05-18 18:29:05 -0700674 return areaToRefresh;
675 }
676
Romain Guyd6a463a2009-05-21 23:10:10 -0700677 private void touchUp(MotionEvent event, boolean cancel) {
Romain Guy82f34952009-05-24 18:40:45 -0700678 mIsListeningForGestures = false;
Yang Li35aa84b2009-05-18 18:29:05 -0700679
Romain Guyd1c67d42009-05-29 13:53:16 -0700680 // A gesture wasn't started or was cancelled
681 if (mCurrentGesture != null) {
682 // add the stroke to the current gesture
683 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
Romain Guyd6a463a2009-05-21 23:10:10 -0700684
Romain Guyd1c67d42009-05-29 13:53:16 -0700685 if (!cancel) {
686 // pass the event to handlers
687 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
688 int count = listeners.size();
689 for (int i = 0; i < count; i++) {
690 listeners.get(i).onGestureEnded(this, event);
691 }
Romain Guy82f34952009-05-24 18:40:45 -0700692
Romain Guy03f0b212009-06-09 04:15:22 -0700693 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
694 false);
Romain Guyd1c67d42009-05-29 13:53:16 -0700695 } else {
696 cancelGesture(event);
697
Romain Guy82f34952009-05-24 18:40:45 -0700698 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700699 } else {
Romain Guyd1c67d42009-05-29 13:53:16 -0700700 cancelGesture(event);
Yang Li35aa84b2009-05-18 18:29:05 -0700701 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700702
Romain Guy8d787562009-05-29 15:02:55 -0700703 mStrokeBuffer.clear();
Romain Guy834f0392009-06-05 11:24:09 -0700704 mPreviousWasGesturing = mIsGesturing;
Romain Guy82f34952009-05-24 18:40:45 -0700705 mIsGesturing = false;
Romain Guy9af0b4f2009-06-02 21:56:27 -0700706
707 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
708 int count = listeners.size();
709 for (int i = 0; i < count; i++) {
710 listeners.get(i).onGesturingEnded(this);
711 }
Yang Li35aa84b2009-05-18 18:29:05 -0700712 }
713
Romain Guyd1c67d42009-05-29 13:53:16 -0700714 private void cancelGesture(MotionEvent event) {
715 // pass the event to handlers
716 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
717 final int count = listeners.size();
718 for (int i = 0; i < count; i++) {
719 listeners.get(i).onGestureCancelled(this, event);
720 }
721
722 clear(false);
723 }
724
Romain Guyec25df92009-05-25 04:39:37 -0700725 private void fireOnGesturePerformed() {
Romain Guy9af0b4f2009-06-02 21:56:27 -0700726 final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
Romain Guyec25df92009-05-25 04:39:37 -0700727 final int count = actionListeners.size();
728 for (int i = 0; i < count; i++) {
Romain Guy9af0b4f2009-06-02 21:56:27 -0700729 actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
Romain Guyec25df92009-05-25 04:39:37 -0700730 }
Romain Guye7c36dd2009-05-25 13:51:00 -0700731 }
Romain Guyec25df92009-05-25 04:39:37 -0700732
Romain Guy82f34952009-05-24 18:40:45 -0700733 private class FadeOutRunnable implements Runnable {
734 boolean fireActionPerformed;
Romain Guy73d25892009-06-09 03:18:10 -0700735 boolean resetMultipleStrokes;
Romain Guy82f34952009-05-24 18:40:45 -0700736
737 public void run() {
738 if (mIsFadingOut) {
739 final long now = AnimationUtils.currentAnimationTimeMillis();
740 final long duration = now - mFadingStart;
741
742 if (duration > mFadeDuration) {
743 if (fireActionPerformed) {
Romain Guyec25df92009-05-25 04:39:37 -0700744 fireOnGesturePerformed();
Romain Guy82f34952009-05-24 18:40:45 -0700745 }
746
Romain Guy834f0392009-06-05 11:24:09 -0700747 mPreviousWasGesturing = false;
Romain Guy82f34952009-05-24 18:40:45 -0700748 mIsFadingOut = false;
749 mFadingHasStarted = false;
750 mPath.rewind();
751 mCurrentGesture = null;
752 setPaintAlpha(255);
753 } else {
754 mFadingHasStarted = true;
755 float interpolatedTime = Math.max(0.0f,
756 Math.min(1.0f, duration / (float) mFadeDuration));
757 mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
758 setPaintAlpha((int) (255 * mFadingAlpha));
759 postDelayed(this, FADE_ANIMATION_RATE);
760 }
Romain Guy73d25892009-06-09 03:18:10 -0700761 } else if (resetMultipleStrokes) {
762 mResetGesture = true;
Romain Guy9edc4e82009-05-25 19:08:31 -0700763 } else {
Romain Guyec25df92009-05-25 04:39:37 -0700764 fireOnGesturePerformed();
765
Romain Guyec25df92009-05-25 04:39:37 -0700766 mFadingHasStarted = false;
767 mPath.rewind();
768 mCurrentGesture = null;
Romain Guy834f0392009-06-05 11:24:09 -0700769 mPreviousWasGesturing = false;
Romain Guyec25df92009-05-25 04:39:37 -0700770 setPaintAlpha(255);
Romain Guy82f34952009-05-24 18:40:45 -0700771 }
Romain Guy9edc4e82009-05-25 19:08:31 -0700772
Romain Guy8d787562009-05-29 15:02:55 -0700773 invalidate();
Romain Guy82f34952009-05-24 18:40:45 -0700774 }
775 }
776
Romain Guy9af0b4f2009-06-02 21:56:27 -0700777 public static interface OnGesturingListener {
778 void onGesturingStarted(GestureOverlayView overlay);
779
780 void onGesturingEnded(GestureOverlayView overlay);
781 }
782
Romain Guydb567c32009-05-21 16:23:21 -0700783 public static interface OnGestureListener {
Romain Guyd6a463a2009-05-21 23:10:10 -0700784 void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
Romain Guydb567c32009-05-21 16:23:21 -0700785
Romain Guyd6a463a2009-05-21 23:10:10 -0700786 void onGesture(GestureOverlayView overlay, MotionEvent event);
Romain Guydb567c32009-05-21 16:23:21 -0700787
Romain Guyd6a463a2009-05-21 23:10:10 -0700788 void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
789
790 void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
Romain Guydb567c32009-05-21 16:23:21 -0700791 }
Romain Guy82f34952009-05-24 18:40:45 -0700792
793 public static interface OnGesturePerformedListener {
794 void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
795 }
Yang Li35aa84b2009-05-18 18:29:05 -0700796}