blob: e0d454cb44d05c73eb85d0c9ffdc1a0caaf35d82 [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
Tor Norbye80756e32015-03-02 09:39:27 -080019import android.annotation.ColorInt;
Yang Li35aa84b2009-05-18 18:29:05 -070020import android.content.Context;
Romain Guyd6a463a2009-05-21 23:10:10 -070021import android.content.res.TypedArray;
Yang Li35aa84b2009-05-18 18:29:05 -070022import android.graphics.Canvas;
Yang Li35aa84b2009-05-18 18:29:05 -070023import android.graphics.Paint;
24import android.graphics.Path;
25import android.graphics.Rect;
Romain Guy82f34952009-05-24 18:40:45 -070026import android.graphics.RectF;
Yang Li35aa84b2009-05-18 18:29:05 -070027import android.util.AttributeSet;
28import android.view.MotionEvent;
Romain Guyd6a463a2009-05-21 23:10:10 -070029import android.view.animation.AnimationUtils;
30import android.view.animation.AccelerateDecelerateInterpolator;
Romain Guy82f34952009-05-24 18:40:45 -070031import android.widget.FrameLayout;
32import android.os.SystemClock;
Romain Guy51099c52009-09-11 14:29:15 -070033import android.annotation.Widget;
Romain Guyd6a463a2009-05-21 23:10:10 -070034import com.android.internal.R;
Yang Li35aa84b2009-05-18 18:29:05 -070035
36import java.util.ArrayList;
37
38/**
Romain Guy82f34952009-05-24 18:40:45 -070039 * A transparent overlay for gesture input that can be placed on top of other
40 * widgets or contain other widgets.
Romain Guyd6a463a2009-05-21 23:10:10 -070041 *
Romain Guy82f34952009-05-24 18:40:45 -070042 * @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled
Romain Guyd6a463a2009-05-21 23:10:10 -070043 * @attr ref android.R.styleable#GestureOverlayView_fadeDuration
44 * @attr ref android.R.styleable#GestureOverlayView_fadeOffset
Romain Guyec25df92009-05-25 04:39:37 -070045 * @attr ref android.R.styleable#GestureOverlayView_fadeEnabled
Romain Guy82f34952009-05-24 18:40:45 -070046 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth
47 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeAngleThreshold
48 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeLengthThreshold
49 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeSquarenessThreshold
Romain Guy8d787562009-05-29 15:02:55 -070050 * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType
Romain Guy82f34952009-05-24 18:40:45 -070051 * @attr ref android.R.styleable#GestureOverlayView_gestureColor
Romain Guy8d787562009-05-29 15:02:55 -070052 * @attr ref android.R.styleable#GestureOverlayView_orientation
Romain Guy82f34952009-05-24 18:40:45 -070053 * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
Yang Li35aa84b2009-05-18 18:29:05 -070054 */
Romain Guy51099c52009-09-11 14:29:15 -070055@Widget
Romain Guy82f34952009-05-24 18:40:45 -070056public class GestureOverlayView extends FrameLayout {
57 public static final int GESTURE_STROKE_TYPE_SINGLE = 0;
58 public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1;
59
Romain Guye7c36dd2009-05-25 13:51:00 -070060 public static final int ORIENTATION_HORIZONTAL = 0;
61 public static final int ORIENTATION_VERTICAL = 1;
62
Romain Guy82f34952009-05-24 18:40:45 -070063 private static final int FADE_ANIMATION_RATE = 16;
Yang Li35aa84b2009-05-18 18:29:05 -070064 private static final boolean GESTURE_RENDERING_ANTIALIAS = true;
Yang Li35aa84b2009-05-18 18:29:05 -070065 private static final boolean DITHER_FLAG = true;
66
Romain Guy82f34952009-05-24 18:40:45 -070067 private final Paint mGesturePaint = new Paint();
Yang Li35aa84b2009-05-18 18:29:05 -070068
Romain Guy82f34952009-05-24 18:40:45 -070069 private long mFadeDuration = 150;
70 private long mFadeOffset = 420;
Romain Guyd6a463a2009-05-21 23:10:10 -070071 private long mFadingStart;
Romain Guy82f34952009-05-24 18:40:45 -070072 private boolean mFadingHasStarted;
Romain Guyec25df92009-05-25 04:39:37 -070073 private boolean mFadeEnabled = true;
Romain Guyd6a463a2009-05-21 23:10:10 -070074
Romain Guy82f34952009-05-24 18:40:45 -070075 private int mCurrentColor;
Romain Guyd6a463a2009-05-21 23:10:10 -070076 private int mCertainGestureColor = 0xFFFFFF00;
Romain Guy82f34952009-05-24 18:40:45 -070077 private int mUncertainGestureColor = 0x48FFFF00;
78 private float mGestureStrokeWidth = 12.0f;
Romain Guyd6a463a2009-05-21 23:10:10 -070079 private int mInvalidateExtraBorder = 10;
Romain Guydb567c32009-05-21 16:23:21 -070080
Romain Guy82f34952009-05-24 18:40:45 -070081 private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE;
Romain Guyd1c67d42009-05-29 13:53:16 -070082 private float mGestureStrokeLengthThreshold = 50.0f;
Romain Guy82f34952009-05-24 18:40:45 -070083 private float mGestureStrokeSquarenessTreshold = 0.275f;
84 private float mGestureStrokeAngleThreshold = 40.0f;
85
Romain Guye7c36dd2009-05-25 13:51:00 -070086 private int mOrientation = ORIENTATION_VERTICAL;
87
Romain Guyd6a463a2009-05-21 23:10:10 -070088 private final Rect mInvalidRect = new Rect();
89 private final Path mPath = new Path();
Romain Guye705f2c2009-06-16 17:07:21 -070090 private boolean mGestureVisible = true;
Yang Li35aa84b2009-05-18 18:29:05 -070091
92 private float mX;
Yang Li35aa84b2009-05-18 18:29:05 -070093 private float mY;
Romain Guy8d787562009-05-29 15:02:55 -070094
Yang Li35aa84b2009-05-18 18:29:05 -070095 private float mCurveEndX;
Yang Li35aa84b2009-05-18 18:29:05 -070096 private float mCurveEndY;
97
Romain Guy82f34952009-05-24 18:40:45 -070098 private float mTotalLength;
99 private boolean mIsGesturing = false;
Romain Guy834f0392009-06-05 11:24:09 -0700100 private boolean mPreviousWasGesturing = false;
Romain Guy82f34952009-05-24 18:40:45 -0700101 private boolean mInterceptEvents = true;
102 private boolean mIsListeningForGestures;
Romain Guy73d25892009-06-09 03:18:10 -0700103 private boolean mResetGesture;
Romain Guy82f34952009-05-24 18:40:45 -0700104
Yang Li35aa84b2009-05-18 18:29:05 -0700105 // current gesture
Romain Guy82f34952009-05-24 18:40:45 -0700106 private Gesture mCurrentGesture;
107 private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
Yang Li35aa84b2009-05-18 18:29:05 -0700108
Romain Guyc5347272009-05-20 10:37:13 -0700109 // TODO: Make this a list of WeakReferences
Romain Guyd6a463a2009-05-21 23:10:10 -0700110 private final ArrayList<OnGestureListener> mOnGestureListeners =
111 new ArrayList<OnGestureListener>();
Romain Guy82f34952009-05-24 18:40:45 -0700112 // TODO: Make this a list of WeakReferences
113 private final ArrayList<OnGesturePerformedListener> mOnGesturePerformedListeners =
114 new ArrayList<OnGesturePerformedListener>();
Romain Guy9af0b4f2009-06-02 21:56:27 -0700115 // TODO: Make this a list of WeakReferences
116 private final ArrayList<OnGesturingListener> mOnGesturingListeners =
117 new ArrayList<OnGesturingListener>();
Romain Guy82f34952009-05-24 18:40:45 -0700118
119 private boolean mHandleGestureActions;
Yang Li35aa84b2009-05-18 18:29:05 -0700120
121 // fading out effect
122 private boolean mIsFadingOut = false;
Romain Guy82f34952009-05-24 18:40:45 -0700123 private float mFadingAlpha = 1.0f;
Romain Guyd6a463a2009-05-21 23:10:10 -0700124 private final AccelerateDecelerateInterpolator mInterpolator =
125 new AccelerateDecelerateInterpolator();
Yang Li35aa84b2009-05-18 18:29:05 -0700126
Romain Guy82f34952009-05-24 18:40:45 -0700127 private final FadeOutRunnable mFadingOut = new FadeOutRunnable();
Yang Li35aa84b2009-05-18 18:29:05 -0700128
Romain Guydb567c32009-05-21 16:23:21 -0700129 public GestureOverlayView(Context context) {
Yang Li35aa84b2009-05-18 18:29:05 -0700130 super(context);
131 init();
132 }
133
Romain Guydb567c32009-05-21 16:23:21 -0700134 public GestureOverlayView(Context context, AttributeSet attrs) {
Romain Guyd6a463a2009-05-21 23:10:10 -0700135 this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle);
136 }
137
Alan Viverette617feb92013-09-09 18:09:13 -0700138 public GestureOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
139 this(context, attrs, defStyleAttr, 0);
140 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700141
Alan Viverette617feb92013-09-09 18:09:13 -0700142 public GestureOverlayView(
143 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
144 super(context, attrs, defStyleAttr, defStyleRes);
145
146 final TypedArray a = context.obtainStyledAttributes(
147 attrs, R.styleable.GestureOverlayView, defStyleAttr, defStyleRes);
Romain Guyd6a463a2009-05-21 23:10:10 -0700148
Romain Guy82f34952009-05-24 18:40:45 -0700149 mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
150 mGestureStrokeWidth);
151 mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
Romain Guyd6a463a2009-05-21 23:10:10 -0700152 mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
153 mCertainGestureColor);
154 mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
155 mUncertainGestureColor);
156 mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
157 mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
Romain Guy82f34952009-05-24 18:40:45 -0700158 mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
159 mGestureStrokeType);
160 mGestureStrokeLengthThreshold = a.getFloat(
161 R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
162 mGestureStrokeLengthThreshold);
163 mGestureStrokeAngleThreshold = a.getFloat(
164 R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
165 mGestureStrokeAngleThreshold);
166 mGestureStrokeSquarenessTreshold = a.getFloat(
167 R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
168 mGestureStrokeSquarenessTreshold);
169 mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
170 mInterceptEvents);
Romain Guyec25df92009-05-25 04:39:37 -0700171 mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
172 mFadeEnabled);
Romain Guye7c36dd2009-05-25 13:51:00 -0700173 mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
Romain Guyd6a463a2009-05-21 23:10:10 -0700174
175 a.recycle();
176
Yang Li35aa84b2009-05-18 18:29:05 -0700177 init();
178 }
179
Romain Guy82f34952009-05-24 18:40:45 -0700180 private void init() {
181 setWillNotDraw(false);
182
183 final Paint gesturePaint = mGesturePaint;
184 gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
185 gesturePaint.setColor(mCertainGestureColor);
186 gesturePaint.setStyle(Paint.Style.STROKE);
187 gesturePaint.setStrokeJoin(Paint.Join.ROUND);
188 gesturePaint.setStrokeCap(Paint.Cap.ROUND);
189 gesturePaint.setStrokeWidth(mGestureStrokeWidth);
190 gesturePaint.setDither(DITHER_FLAG);
191
192 mCurrentColor = mCertainGestureColor;
193 setPaintAlpha(255);
194 }
195
Yang Li35aa84b2009-05-18 18:29:05 -0700196 public ArrayList<GesturePoint> getCurrentStroke() {
Romain Guy82f34952009-05-24 18:40:45 -0700197 return mStrokeBuffer;
Yang Li35aa84b2009-05-18 18:29:05 -0700198 }
199
Romain Guye7c36dd2009-05-25 13:51:00 -0700200 public int getOrientation() {
201 return mOrientation;
202 }
203
204 public void setOrientation(int orientation) {
205 mOrientation = orientation;
206 }
207
Tor Norbye80756e32015-03-02 09:39:27 -0800208 public void setGestureColor(@ColorInt int color) {
Romain Guydb567c32009-05-21 16:23:21 -0700209 mCertainGestureColor = color;
210 }
211
Tor Norbye80756e32015-03-02 09:39:27 -0800212 public void setUncertainGestureColor(@ColorInt int color) {
Romain Guydb567c32009-05-21 16:23:21 -0700213 mUncertainGestureColor = color;
214 }
215
Tor Norbye80756e32015-03-02 09:39:27 -0800216 @ColorInt
Romain Guydb567c32009-05-21 16:23:21 -0700217 public int getUncertainGestureColor() {
218 return mUncertainGestureColor;
219 }
220
Tor Norbye80756e32015-03-02 09:39:27 -0800221 @ColorInt
Romain Guydb567c32009-05-21 16:23:21 -0700222 public int getGestureColor() {
223 return mCertainGestureColor;
224 }
225
Romain Guy82f34952009-05-24 18:40:45 -0700226 public float getGestureStrokeWidth() {
227 return mGestureStrokeWidth;
Romain Guyd6a463a2009-05-21 23:10:10 -0700228 }
229
Romain Guy82f34952009-05-24 18:40:45 -0700230 public void setGestureStrokeWidth(float gestureStrokeWidth) {
231 mGestureStrokeWidth = gestureStrokeWidth;
232 mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
233 mGesturePaint.setStrokeWidth(gestureStrokeWidth);
Romain Guyd6a463a2009-05-21 23:10:10 -0700234 }
235
Romain Guy82f34952009-05-24 18:40:45 -0700236 public int getGestureStrokeType() {
237 return mGestureStrokeType;
238 }
239
240 public void setGestureStrokeType(int gestureStrokeType) {
241 mGestureStrokeType = gestureStrokeType;
242 }
243
244 public float getGestureStrokeLengthThreshold() {
245 return mGestureStrokeLengthThreshold;
246 }
247
248 public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
249 mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
250 }
251
252 public float getGestureStrokeSquarenessTreshold() {
253 return mGestureStrokeSquarenessTreshold;
254 }
255
256 public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
257 mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
258 }
259
260 public float getGestureStrokeAngleThreshold() {
261 return mGestureStrokeAngleThreshold;
262 }
263
264 public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
265 mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
266 }
267
268 public boolean isEventsInterceptionEnabled() {
269 return mInterceptEvents;
270 }
271
272 public void setEventsInterceptionEnabled(boolean enabled) {
273 mInterceptEvents = enabled;
274 }
275
Romain Guyec25df92009-05-25 04:39:37 -0700276 public boolean isFadeEnabled() {
277 return mFadeEnabled;
278 }
279
280 public void setFadeEnabled(boolean fadeEnabled) {
281 mFadeEnabled = fadeEnabled;
282 }
283
Romain Guy82f34952009-05-24 18:40:45 -0700284 public Gesture getGesture() {
285 return mCurrentGesture;
286 }
287
288 public void setGesture(Gesture gesture) {
Yang Li35aa84b2009-05-18 18:29:05 -0700289 if (mCurrentGesture != null) {
290 clear(false);
291 }
292
Romain Guy82f34952009-05-24 18:40:45 -0700293 setCurrentColor(mCertainGestureColor);
Yang Li35aa84b2009-05-18 18:29:05 -0700294 mCurrentGesture = gesture;
295
Romain Guy82f34952009-05-24 18:40:45 -0700296 final Path path = mCurrentGesture.toPath();
297 final RectF bounds = new RectF();
298 path.computeBounds(bounds, true);
Yang Li35aa84b2009-05-18 18:29:05 -0700299
Romain Guycfbe8cf2009-06-10 01:21:20 -0700300 // TODO: The path should also be scaled to fit inside this view
Romain Guy82f34952009-05-24 18:40:45 -0700301 mPath.rewind();
Romain Guy03f0b212009-06-09 04:15:22 -0700302 mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
303 -bounds.top + (getHeight() - bounds.height()) / 2.0f);
304
305 mResetGesture = true;
Romain Guyc5347272009-05-20 10:37:13 -0700306
Romain Guy82f34952009-05-24 18:40:45 -0700307 invalidate();
Yang Li35aa84b2009-05-18 18:29:05 -0700308 }
309
Romain Guyb973eef2009-06-16 15:53:27 -0700310 public Path getGesturePath() {
311 return mPath;
312 }
313
314 public Path getGesturePath(Path path) {
315 path.set(mPath);
316 return path;
317 }
318
319 public boolean isGestureVisible() {
320 return mGestureVisible;
321 }
322
323 public void setGestureVisible(boolean visible) {
324 mGestureVisible = visible;
325 }
326
327 public long getFadeOffset() {
328 return mFadeOffset;
329 }
330
331 public void setFadeOffset(long fadeOffset) {
332 mFadeOffset = fadeOffset;
333 }
334
Romain Guydb567c32009-05-21 16:23:21 -0700335 public void addOnGestureListener(OnGestureListener listener) {
336 mOnGestureListeners.add(listener);
Yang Li35aa84b2009-05-18 18:29:05 -0700337 }
338
Romain Guydb567c32009-05-21 16:23:21 -0700339 public void removeOnGestureListener(OnGestureListener listener) {
340 mOnGestureListeners.remove(listener);
Yang Li35aa84b2009-05-18 18:29:05 -0700341 }
342
Romain Guyd6a463a2009-05-21 23:10:10 -0700343 public void removeAllOnGestureListeners() {
344 mOnGestureListeners.clear();
345 }
346
Romain Guy82f34952009-05-24 18:40:45 -0700347 public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
348 mOnGesturePerformedListeners.add(listener);
349 if (mOnGesturePerformedListeners.size() > 0) {
350 mHandleGestureActions = true;
Yang Li35aa84b2009-05-18 18:29:05 -0700351 }
Yang Li35aa84b2009-05-18 18:29:05 -0700352 }
353
Romain Guy82f34952009-05-24 18:40:45 -0700354 public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
355 mOnGesturePerformedListeners.remove(listener);
356 if (mOnGesturePerformedListeners.size() <= 0) {
357 mHandleGestureActions = false;
358 }
359 }
360
361 public void removeAllOnGesturePerformedListeners() {
362 mOnGesturePerformedListeners.clear();
363 mHandleGestureActions = false;
364 }
365
Romain Guy9af0b4f2009-06-02 21:56:27 -0700366 public void addOnGesturingListener(OnGesturingListener listener) {
367 mOnGesturingListeners.add(listener);
368 }
369
370 public void removeOnGesturingListener(OnGesturingListener listener) {
371 mOnGesturingListeners.remove(listener);
372 }
373
374 public void removeAllOnGesturingListeners() {
375 mOnGesturingListeners.clear();
376 }
377
Romain Guy82f34952009-05-24 18:40:45 -0700378 public boolean isGesturing() {
379 return mIsGesturing;
380 }
381
382 private void setCurrentColor(int color) {
383 mCurrentColor = color;
384 if (mFadingHasStarted) {
385 setPaintAlpha((int) (255 * mFadingAlpha));
386 } else {
387 setPaintAlpha(255);
388 }
389 invalidate();
390 }
391
Romain Guy27a2b502009-06-09 04:36:14 -0700392 /**
393 * @hide
394 */
395 public Paint getGesturePaint() {
396 return mGesturePaint;
397 }
398
Romain Guy82f34952009-05-24 18:40:45 -0700399 @Override
400 public void draw(Canvas canvas) {
401 super.draw(canvas);
402
Romain Guyb973eef2009-06-16 15:53:27 -0700403 if (mCurrentGesture != null && mGestureVisible) {
Romain Guy82f34952009-05-24 18:40:45 -0700404 canvas.drawPath(mPath, mGesturePaint);
405 }
406 }
407
408 private void setPaintAlpha(int alpha) {
409 alpha += alpha >> 7;
410 final int baseAlpha = mCurrentColor >>> 24;
411 final int useAlpha = baseAlpha * alpha >> 8;
412 mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
413 }
414
415 public void clear(boolean animated) {
Romain Guy03f0b212009-06-09 04:15:22 -0700416 clear(animated, false, true);
Romain Guy82f34952009-05-24 18:40:45 -0700417 }
418
Romain Guy03f0b212009-06-09 04:15:22 -0700419 private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
Romain Guy82f34952009-05-24 18:40:45 -0700420 setPaintAlpha(255);
Romain Guy9edc4e82009-05-25 19:08:31 -0700421 removeCallbacks(mFadingOut);
Romain Guy73d25892009-06-09 03:18:10 -0700422 mResetGesture = false;
Romain Guy9edc4e82009-05-25 19:08:31 -0700423 mFadingOut.fireActionPerformed = fireActionPerformed;
Romain Guy73d25892009-06-09 03:18:10 -0700424 mFadingOut.resetMultipleStrokes = false;
Romain Guy9edc4e82009-05-25 19:08:31 -0700425
Romain Guy82f34952009-05-24 18:40:45 -0700426 if (animated && mCurrentGesture != null) {
Romain Guy9edc4e82009-05-25 19:08:31 -0700427 mFadingAlpha = 1.0f;
428 mIsFadingOut = true;
429 mFadingHasStarted = false;
430 mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
431
Romain Guyd6a463a2009-05-21 23:10:10 -0700432 postDelayed(mFadingOut, mFadeOffset);
Yang Li35aa84b2009-05-18 18:29:05 -0700433 } else {
Romain Guy9edc4e82009-05-25 19:08:31 -0700434 mFadingAlpha = 1.0f;
435 mIsFadingOut = false;
436 mFadingHasStarted = false;
437
Romain Guy03f0b212009-06-09 04:15:22 -0700438 if (immediate) {
Romain Guy9edc4e82009-05-25 19:08:31 -0700439 mCurrentGesture = null;
440 mPath.rewind();
441 invalidate();
Romain Guy03f0b212009-06-09 04:15:22 -0700442 } else if (fireActionPerformed) {
443 postDelayed(mFadingOut, mFadeOffset);
Romain Guy73d25892009-06-09 03:18:10 -0700444 } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
445 mFadingOut.resetMultipleStrokes = true;
446 postDelayed(mFadingOut, mFadeOffset);
Romain Guy03f0b212009-06-09 04:15:22 -0700447 } else {
448 mCurrentGesture = null;
449 mPath.rewind();
450 invalidate();
Romain Guy9edc4e82009-05-25 19:08:31 -0700451 }
Yang Li35aa84b2009-05-18 18:29:05 -0700452 }
453 }
454
Romain Guy82f34952009-05-24 18:40:45 -0700455 public void cancelClearAnimation() {
456 setPaintAlpha(255);
Yang Li35aa84b2009-05-18 18:29:05 -0700457 mIsFadingOut = false;
Romain Guy82f34952009-05-24 18:40:45 -0700458 mFadingHasStarted = false;
Romain Guyd6a463a2009-05-21 23:10:10 -0700459 removeCallbacks(mFadingOut);
Romain Guy82f34952009-05-24 18:40:45 -0700460 mPath.rewind();
461 mCurrentGesture = null;
462 }
463
464 public void cancelGesture() {
465 mIsListeningForGestures = false;
466
467 // add the stroke to the current gesture
468 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
469
470 // pass the event to handlers
471 final long now = SystemClock.uptimeMillis();
472 final MotionEvent event = MotionEvent.obtain(now, now,
473 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
474
475 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
Romain Guy9af0b4f2009-06-02 21:56:27 -0700476 int count = listeners.size();
Romain Guy82f34952009-05-24 18:40:45 -0700477 for (int i = 0; i < count; i++) {
478 listeners.get(i).onGestureCancelled(this, event);
479 }
480
481 event.recycle();
482
483 clear(false);
484 mIsGesturing = false;
Romain Guy834f0392009-06-05 11:24:09 -0700485 mPreviousWasGesturing = false;
Romain Guy82f34952009-05-24 18:40:45 -0700486 mStrokeBuffer.clear();
Romain Guy9af0b4f2009-06-02 21:56:27 -0700487
488 final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners;
489 count = otherListeners.size();
490 for (int i = 0; i < count; i++) {
491 otherListeners.get(i).onGesturingEnded(this);
492 }
Yang Li35aa84b2009-05-18 18:29:05 -0700493 }
494
495 @Override
Romain Guy82f34952009-05-24 18:40:45 -0700496 protected void onDetachedFromWindow() {
Romain Guy46bfc482013-08-16 18:38:29 -0700497 super.onDetachedFromWindow();
Romain Guy82f34952009-05-24 18:40:45 -0700498 cancelClearAnimation();
499 }
500
501 @Override
502 public boolean dispatchTouchEvent(MotionEvent event) {
503 if (isEnabled()) {
Romain Guy834f0392009-06-05 11:24:09 -0700504 final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
505 mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
506 mInterceptEvents;
507
Romain Guy82f34952009-05-24 18:40:45 -0700508 processEvent(event);
509
510 if (cancelDispatch) {
511 event.setAction(MotionEvent.ACTION_CANCEL);
512 }
513
514 super.dispatchTouchEvent(event);
Romain Guy834f0392009-06-05 11:24:09 -0700515
Yang Li35aa84b2009-05-18 18:29:05 -0700516 return true;
517 }
518
Romain Guy82f34952009-05-24 18:40:45 -0700519 return super.dispatchTouchEvent(event);
Romain Guyd6a463a2009-05-21 23:10:10 -0700520 }
521
Romain Guy82f34952009-05-24 18:40:45 -0700522 private boolean processEvent(MotionEvent event) {
Yang Li35aa84b2009-05-18 18:29:05 -0700523 switch (event.getAction()) {
524 case MotionEvent.ACTION_DOWN:
Romain Guye7c36dd2009-05-25 13:51:00 -0700525 touchDown(event);
Romain Guy82f34952009-05-24 18:40:45 -0700526 invalidate();
527 return true;
Yang Li35aa84b2009-05-18 18:29:05 -0700528 case MotionEvent.ACTION_MOVE:
Romain Guy82f34952009-05-24 18:40:45 -0700529 if (mIsListeningForGestures) {
530 Rect rect = touchMove(event);
531 if (rect != null) {
532 invalidate(rect);
533 }
534 return true;
Yang Li35aa84b2009-05-18 18:29:05 -0700535 }
536 break;
537 case MotionEvent.ACTION_UP:
Romain Guy82f34952009-05-24 18:40:45 -0700538 if (mIsListeningForGestures) {
539 touchUp(event, false);
540 invalidate();
541 return true;
542 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700543 break;
544 case MotionEvent.ACTION_CANCEL:
Romain Guy82f34952009-05-24 18:40:45 -0700545 if (mIsListeningForGestures) {
546 touchUp(event, true);
547 invalidate();
548 return true;
549 }
Yang Li35aa84b2009-05-18 18:29:05 -0700550 }
Romain Guy82f34952009-05-24 18:40:45 -0700551
552 return false;
Yang Li35aa84b2009-05-18 18:29:05 -0700553 }
554
Romain Guye7c36dd2009-05-25 13:51:00 -0700555 private void touchDown(MotionEvent event) {
Romain Guy82f34952009-05-24 18:40:45 -0700556 mIsListeningForGestures = true;
557
Yang Li35aa84b2009-05-18 18:29:05 -0700558 float x = event.getX();
559 float y = event.getY();
560
561 mX = x;
562 mY = y;
563
Romain Guy82f34952009-05-24 18:40:45 -0700564 mTotalLength = 0;
565 mIsGesturing = false;
566
Romain Guy73d25892009-06-09 03:18:10 -0700567 if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
Romain Guy82f34952009-05-24 18:40:45 -0700568 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
Romain Guy73d25892009-06-09 03:18:10 -0700569 mResetGesture = false;
Romain Guy82f34952009-05-24 18:40:45 -0700570 mCurrentGesture = null;
571 mPath.rewind();
572 } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
573 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
574 }
575
576 // if there is fading out going on, stop it.
577 if (mFadingHasStarted) {
578 cancelClearAnimation();
579 } else if (mIsFadingOut) {
580 setPaintAlpha(255);
581 mIsFadingOut = false;
582 mFadingHasStarted = false;
583 removeCallbacks(mFadingOut);
584 }
585
Yang Li35aa84b2009-05-18 18:29:05 -0700586 if (mCurrentGesture == null) {
587 mCurrentGesture = new Gesture();
588 }
589
Romain Guy82f34952009-05-24 18:40:45 -0700590 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
Yang Li35aa84b2009-05-18 18:29:05 -0700591 mPath.moveTo(x, y);
592
Romain Guy82f34952009-05-24 18:40:45 -0700593 final int border = mInvalidateExtraBorder;
594 mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
595
Yang Li35aa84b2009-05-18 18:29:05 -0700596 mCurveEndX = x;
597 mCurveEndY = y;
Romain Guyff686ce2009-05-25 01:33:00 -0700598
599 // pass the event to handlers
600 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
601 final int count = listeners.size();
602 for (int i = 0; i < count; i++) {
603 listeners.get(i).onGestureStarted(this, event);
Romain Guy8d787562009-05-29 15:02:55 -0700604 }
Yang Li35aa84b2009-05-18 18:29:05 -0700605 }
606
607 private Rect touchMove(MotionEvent event) {
608 Rect areaToRefresh = null;
Romain Guy8d787562009-05-29 15:02:55 -0700609
Romain Guyd6a463a2009-05-21 23:10:10 -0700610 final float x = event.getX();
611 final float y = event.getY();
Yang Li35aa84b2009-05-18 18:29:05 -0700612
Romain Guyd6a463a2009-05-21 23:10:10 -0700613 final float previousX = mX;
614 final float previousY = mY;
615
616 final float dx = Math.abs(x - previousX);
617 final float dy = Math.abs(y - previousY);
Romain Guy8d787562009-05-29 15:02:55 -0700618
Romain Guyd6a463a2009-05-21 23:10:10 -0700619 if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
620 areaToRefresh = mInvalidRect;
621
Yang Li35aa84b2009-05-18 18:29:05 -0700622 // start with the curve end
Romain Guy82f34952009-05-24 18:40:45 -0700623 final int border = mInvalidateExtraBorder;
624 areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
625 (int) mCurveEndX + border, (int) mCurveEndY + border);
Romain Guy8d787562009-05-29 15:02:55 -0700626
Romain Guy82f34952009-05-24 18:40:45 -0700627 float cX = mCurveEndX = (x + previousX) / 2;
628 float cY = mCurveEndY = (y + previousY) / 2;
Romain Guyd6a463a2009-05-21 23:10:10 -0700629
Romain Guy82f34952009-05-24 18:40:45 -0700630 mPath.quadTo(previousX, previousY, cX, cY);
Romain Guy8d787562009-05-29 15:02:55 -0700631
Yang Li35aa84b2009-05-18 18:29:05 -0700632 // union with the control point of the new curve
Romain Guy82f34952009-05-24 18:40:45 -0700633 areaToRefresh.union((int) previousX - border, (int) previousY - border,
634 (int) previousX + border, (int) previousY + border);
Romain Guy8d787562009-05-29 15:02:55 -0700635
Yang Li35aa84b2009-05-18 18:29:05 -0700636 // union with the end point of the new curve
Romain Guy82f34952009-05-24 18:40:45 -0700637 areaToRefresh.union((int) cX - border, (int) cY - border,
638 (int) cX + border, (int) cY + border);
Yang Li35aa84b2009-05-18 18:29:05 -0700639
Yang Li35aa84b2009-05-18 18:29:05 -0700640 mX = x;
641 mY = y;
Yang Li35aa84b2009-05-18 18:29:05 -0700642
Romain Guyd1c67d42009-05-29 13:53:16 -0700643 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
Yang Li35aa84b2009-05-18 18:29:05 -0700644
Romain Guyd1c67d42009-05-29 13:53:16 -0700645 if (mHandleGestureActions && !mIsGesturing) {
Neil Fuller33253a42014-10-01 11:55:10 +0100646 mTotalLength += (float) Math.hypot(dx, dy);
Romain Guy82f34952009-05-24 18:40:45 -0700647
Romain Guyd1c67d42009-05-29 13:53:16 -0700648 if (mTotalLength > mGestureStrokeLengthThreshold) {
649 final OrientedBoundingBox box =
Romain Guy46c53122010-02-04 14:19:50 -0800650 GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
Romain Guy82f34952009-05-24 18:40:45 -0700651
Romain Guyd1c67d42009-05-29 13:53:16 -0700652 float angle = Math.abs(box.orientation);
653 if (angle > 90) {
654 angle = 180 - angle;
655 }
Romain Guy82f34952009-05-24 18:40:45 -0700656
Romain Guyd1c67d42009-05-29 13:53:16 -0700657 if (box.squareness > mGestureStrokeSquarenessTreshold ||
658 (mOrientation == ORIENTATION_VERTICAL ?
659 angle < mGestureStrokeAngleThreshold :
660 angle > mGestureStrokeAngleThreshold)) {
Romain Guy82f34952009-05-24 18:40:45 -0700661
Romain Guyd1c67d42009-05-29 13:53:16 -0700662 mIsGesturing = true;
663 setCurrentColor(mCertainGestureColor);
Romain Guy9af0b4f2009-06-02 21:56:27 -0700664
665 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
666 int count = listeners.size();
667 for (int i = 0; i < count; i++) {
668 listeners.get(i).onGesturingStarted(this);
669 }
Romain Guyd1c67d42009-05-29 13:53:16 -0700670 }
Romain Guy82f34952009-05-24 18:40:45 -0700671 }
672 }
Romain Guy82f34952009-05-24 18:40:45 -0700673
Romain Guyd1c67d42009-05-29 13:53:16 -0700674 // pass the event to handlers
675 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
676 final int count = listeners.size();
677 for (int i = 0; i < count; i++) {
678 listeners.get(i).onGesture(this, event);
679 }
680 }
Romain Guyff686ce2009-05-25 01:33:00 -0700681
Yang Li35aa84b2009-05-18 18:29:05 -0700682 return areaToRefresh;
683 }
684
Romain Guyd6a463a2009-05-21 23:10:10 -0700685 private void touchUp(MotionEvent event, boolean cancel) {
Romain Guy82f34952009-05-24 18:40:45 -0700686 mIsListeningForGestures = false;
Yang Li35aa84b2009-05-18 18:29:05 -0700687
Romain Guyd1c67d42009-05-29 13:53:16 -0700688 // A gesture wasn't started or was cancelled
689 if (mCurrentGesture != null) {
690 // add the stroke to the current gesture
691 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
Romain Guyd6a463a2009-05-21 23:10:10 -0700692
Romain Guyd1c67d42009-05-29 13:53:16 -0700693 if (!cancel) {
694 // pass the event to handlers
695 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
696 int count = listeners.size();
697 for (int i = 0; i < count; i++) {
698 listeners.get(i).onGestureEnded(this, event);
699 }
Romain Guy82f34952009-05-24 18:40:45 -0700700
Romain Guy03f0b212009-06-09 04:15:22 -0700701 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
702 false);
Romain Guyd1c67d42009-05-29 13:53:16 -0700703 } else {
704 cancelGesture(event);
705
Romain Guy82f34952009-05-24 18:40:45 -0700706 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700707 } else {
Romain Guyd1c67d42009-05-29 13:53:16 -0700708 cancelGesture(event);
Yang Li35aa84b2009-05-18 18:29:05 -0700709 }
Romain Guyd6a463a2009-05-21 23:10:10 -0700710
Romain Guy8d787562009-05-29 15:02:55 -0700711 mStrokeBuffer.clear();
Romain Guy834f0392009-06-05 11:24:09 -0700712 mPreviousWasGesturing = mIsGesturing;
Romain Guy82f34952009-05-24 18:40:45 -0700713 mIsGesturing = false;
Romain Guy9af0b4f2009-06-02 21:56:27 -0700714
715 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
716 int count = listeners.size();
717 for (int i = 0; i < count; i++) {
718 listeners.get(i).onGesturingEnded(this);
719 }
Yang Li35aa84b2009-05-18 18:29:05 -0700720 }
721
Romain Guyd1c67d42009-05-29 13:53:16 -0700722 private void cancelGesture(MotionEvent event) {
723 // pass the event to handlers
724 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
725 final int count = listeners.size();
726 for (int i = 0; i < count; i++) {
727 listeners.get(i).onGestureCancelled(this, event);
728 }
729
730 clear(false);
731 }
732
Romain Guyec25df92009-05-25 04:39:37 -0700733 private void fireOnGesturePerformed() {
Romain Guy9af0b4f2009-06-02 21:56:27 -0700734 final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
Romain Guyec25df92009-05-25 04:39:37 -0700735 final int count = actionListeners.size();
736 for (int i = 0; i < count; i++) {
Romain Guy9af0b4f2009-06-02 21:56:27 -0700737 actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
Romain Guyec25df92009-05-25 04:39:37 -0700738 }
Romain Guye7c36dd2009-05-25 13:51:00 -0700739 }
Romain Guyec25df92009-05-25 04:39:37 -0700740
Romain Guy82f34952009-05-24 18:40:45 -0700741 private class FadeOutRunnable implements Runnable {
742 boolean fireActionPerformed;
Romain Guy73d25892009-06-09 03:18:10 -0700743 boolean resetMultipleStrokes;
Romain Guy82f34952009-05-24 18:40:45 -0700744
745 public void run() {
746 if (mIsFadingOut) {
747 final long now = AnimationUtils.currentAnimationTimeMillis();
748 final long duration = now - mFadingStart;
749
750 if (duration > mFadeDuration) {
751 if (fireActionPerformed) {
Romain Guyec25df92009-05-25 04:39:37 -0700752 fireOnGesturePerformed();
Romain Guy82f34952009-05-24 18:40:45 -0700753 }
754
Romain Guy834f0392009-06-05 11:24:09 -0700755 mPreviousWasGesturing = false;
Romain Guy82f34952009-05-24 18:40:45 -0700756 mIsFadingOut = false;
757 mFadingHasStarted = false;
758 mPath.rewind();
759 mCurrentGesture = null;
760 setPaintAlpha(255);
761 } else {
762 mFadingHasStarted = true;
763 float interpolatedTime = Math.max(0.0f,
764 Math.min(1.0f, duration / (float) mFadeDuration));
765 mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
766 setPaintAlpha((int) (255 * mFadingAlpha));
767 postDelayed(this, FADE_ANIMATION_RATE);
768 }
Romain Guy73d25892009-06-09 03:18:10 -0700769 } else if (resetMultipleStrokes) {
770 mResetGesture = true;
Romain Guy9edc4e82009-05-25 19:08:31 -0700771 } else {
Romain Guyec25df92009-05-25 04:39:37 -0700772 fireOnGesturePerformed();
773
Romain Guyec25df92009-05-25 04:39:37 -0700774 mFadingHasStarted = false;
775 mPath.rewind();
776 mCurrentGesture = null;
Romain Guy834f0392009-06-05 11:24:09 -0700777 mPreviousWasGesturing = false;
Romain Guyec25df92009-05-25 04:39:37 -0700778 setPaintAlpha(255);
Romain Guy82f34952009-05-24 18:40:45 -0700779 }
Romain Guy9edc4e82009-05-25 19:08:31 -0700780
Romain Guy8d787562009-05-29 15:02:55 -0700781 invalidate();
Romain Guy82f34952009-05-24 18:40:45 -0700782 }
783 }
784
Romain Guy9af0b4f2009-06-02 21:56:27 -0700785 public static interface OnGesturingListener {
786 void onGesturingStarted(GestureOverlayView overlay);
787
788 void onGesturingEnded(GestureOverlayView overlay);
789 }
790
Romain Guydb567c32009-05-21 16:23:21 -0700791 public static interface OnGestureListener {
Romain Guyd6a463a2009-05-21 23:10:10 -0700792 void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
Romain Guydb567c32009-05-21 16:23:21 -0700793
Romain Guyd6a463a2009-05-21 23:10:10 -0700794 void onGesture(GestureOverlayView overlay, MotionEvent event);
Romain Guydb567c32009-05-21 16:23:21 -0700795
Romain Guyd6a463a2009-05-21 23:10:10 -0700796 void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
797
798 void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
Romain Guydb567c32009-05-21 16:23:21 -0700799 }
Romain Guy82f34952009-05-24 18:40:45 -0700800
801 public static interface OnGesturePerformedListener {
802 void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
803 }
Yang Li35aa84b2009-05-18 18:29:05 -0700804}