• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
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 
17 package android.gesture;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.Path;
24 import android.graphics.Rect;
25 import android.graphics.RectF;
26 import android.util.AttributeSet;
27 import android.view.MotionEvent;
28 import android.view.animation.AnimationUtils;
29 import android.view.animation.AccelerateDecelerateInterpolator;
30 import android.widget.FrameLayout;
31 import android.os.SystemClock;
32 import android.annotation.Widget;
33 import com.android.internal.R;
34 
35 import java.util.ArrayList;
36 
37 /**
38  * A transparent overlay for gesture input that can be placed on top of other
39  * widgets or contain other widgets.
40  *
41  * @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled
42  * @attr ref android.R.styleable#GestureOverlayView_fadeDuration
43  * @attr ref android.R.styleable#GestureOverlayView_fadeOffset
44  * @attr ref android.R.styleable#GestureOverlayView_fadeEnabled
45  * @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
49  * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType
50  * @attr ref android.R.styleable#GestureOverlayView_gestureColor
51  * @attr ref android.R.styleable#GestureOverlayView_orientation
52  * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
53  */
54 @Widget
55 public 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 
59     public static final int ORIENTATION_HORIZONTAL = 0;
60     public static final int ORIENTATION_VERTICAL = 1;
61 
62     private static final int FADE_ANIMATION_RATE = 16;
63     private static final boolean GESTURE_RENDERING_ANTIALIAS = true;
64     private static final boolean DITHER_FLAG = true;
65 
66     private final Paint mGesturePaint = new Paint();
67 
68     private long mFadeDuration = 150;
69     private long mFadeOffset = 420;
70     private long mFadingStart;
71     private boolean mFadingHasStarted;
72     private boolean mFadeEnabled = true;
73 
74     private int mCurrentColor;
75     private int mCertainGestureColor = 0xFFFFFF00;
76     private int mUncertainGestureColor = 0x48FFFF00;
77     private float mGestureStrokeWidth = 12.0f;
78     private int mInvalidateExtraBorder = 10;
79 
80     private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE;
81     private float mGestureStrokeLengthThreshold = 50.0f;
82     private float mGestureStrokeSquarenessTreshold = 0.275f;
83     private float mGestureStrokeAngleThreshold = 40.0f;
84 
85     private int mOrientation = ORIENTATION_VERTICAL;
86 
87     private final Rect mInvalidRect = new Rect();
88     private final Path mPath = new Path();
89     private boolean mGestureVisible = true;
90 
91     private float mX;
92     private float mY;
93 
94     private float mCurveEndX;
95     private float mCurveEndY;
96 
97     private float mTotalLength;
98     private boolean mIsGesturing = false;
99     private boolean mPreviousWasGesturing = false;
100     private boolean mInterceptEvents = true;
101     private boolean mIsListeningForGestures;
102     private boolean mResetGesture;
103 
104     // current gesture
105     private Gesture mCurrentGesture;
106     private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
107 
108     // TODO: Make this a list of WeakReferences
109     private final ArrayList<OnGestureListener> mOnGestureListeners =
110             new ArrayList<OnGestureListener>();
111     // TODO: Make this a list of WeakReferences
112     private final ArrayList<OnGesturePerformedListener> mOnGesturePerformedListeners =
113             new ArrayList<OnGesturePerformedListener>();
114     // TODO: Make this a list of WeakReferences
115     private final ArrayList<OnGesturingListener> mOnGesturingListeners =
116             new ArrayList<OnGesturingListener>();
117 
118     private boolean mHandleGestureActions;
119 
120     // fading out effect
121     private boolean mIsFadingOut = false;
122     private float mFadingAlpha = 1.0f;
123     private final AccelerateDecelerateInterpolator mInterpolator =
124             new AccelerateDecelerateInterpolator();
125 
126     private final FadeOutRunnable mFadingOut = new FadeOutRunnable();
127 
GestureOverlayView(Context context)128     public GestureOverlayView(Context context) {
129         super(context);
130         init();
131     }
132 
GestureOverlayView(Context context, AttributeSet attrs)133     public GestureOverlayView(Context context, AttributeSet attrs) {
134         this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle);
135     }
136 
GestureOverlayView(Context context, AttributeSet attrs, int defStyle)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 
143         mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
144                 mGestureStrokeWidth);
145         mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
146         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);
152         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);
165         mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
166                 mFadeEnabled);
167         mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
168 
169         a.recycle();
170 
171         init();
172     }
173 
init()174     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 
getCurrentStroke()190     public ArrayList<GesturePoint> getCurrentStroke() {
191         return mStrokeBuffer;
192     }
193 
getOrientation()194     public int getOrientation() {
195         return mOrientation;
196     }
197 
setOrientation(int orientation)198     public void setOrientation(int orientation) {
199         mOrientation = orientation;
200     }
201 
setGestureColor(int color)202     public void setGestureColor(int color) {
203         mCertainGestureColor = color;
204     }
205 
setUncertainGestureColor(int color)206     public void setUncertainGestureColor(int color) {
207         mUncertainGestureColor = color;
208     }
209 
getUncertainGestureColor()210     public int getUncertainGestureColor() {
211         return mUncertainGestureColor;
212     }
213 
getGestureColor()214     public int getGestureColor() {
215         return mCertainGestureColor;
216     }
217 
getGestureStrokeWidth()218     public float getGestureStrokeWidth() {
219         return mGestureStrokeWidth;
220     }
221 
setGestureStrokeWidth(float gestureStrokeWidth)222     public void setGestureStrokeWidth(float gestureStrokeWidth) {
223         mGestureStrokeWidth = gestureStrokeWidth;
224         mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
225         mGesturePaint.setStrokeWidth(gestureStrokeWidth);
226     }
227 
getGestureStrokeType()228     public int getGestureStrokeType() {
229         return mGestureStrokeType;
230     }
231 
setGestureStrokeType(int gestureStrokeType)232     public void setGestureStrokeType(int gestureStrokeType) {
233         mGestureStrokeType = gestureStrokeType;
234     }
235 
getGestureStrokeLengthThreshold()236     public float getGestureStrokeLengthThreshold() {
237         return mGestureStrokeLengthThreshold;
238     }
239 
setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold)240     public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
241         mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
242     }
243 
getGestureStrokeSquarenessTreshold()244     public float getGestureStrokeSquarenessTreshold() {
245         return mGestureStrokeSquarenessTreshold;
246     }
247 
setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold)248     public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
249         mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
250     }
251 
getGestureStrokeAngleThreshold()252     public float getGestureStrokeAngleThreshold() {
253         return mGestureStrokeAngleThreshold;
254     }
255 
setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold)256     public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
257         mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
258     }
259 
isEventsInterceptionEnabled()260     public boolean isEventsInterceptionEnabled() {
261         return mInterceptEvents;
262     }
263 
setEventsInterceptionEnabled(boolean enabled)264     public void setEventsInterceptionEnabled(boolean enabled) {
265         mInterceptEvents = enabled;
266     }
267 
isFadeEnabled()268     public boolean isFadeEnabled() {
269         return mFadeEnabled;
270     }
271 
setFadeEnabled(boolean fadeEnabled)272     public void setFadeEnabled(boolean fadeEnabled) {
273         mFadeEnabled = fadeEnabled;
274     }
275 
getGesture()276     public Gesture getGesture() {
277         return mCurrentGesture;
278     }
279 
setGesture(Gesture gesture)280     public void setGesture(Gesture gesture) {
281         if (mCurrentGesture != null) {
282             clear(false);
283         }
284 
285         setCurrentColor(mCertainGestureColor);
286         mCurrentGesture = gesture;
287 
288         final Path path = mCurrentGesture.toPath();
289         final RectF bounds = new RectF();
290         path.computeBounds(bounds, true);
291 
292         // TODO: The path should also be scaled to fit inside this view
293         mPath.rewind();
294         mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
295                 -bounds.top + (getHeight() - bounds.height()) / 2.0f);
296 
297         mResetGesture = true;
298 
299         invalidate();
300     }
301 
getGesturePath()302     public Path getGesturePath() {
303         return mPath;
304     }
305 
getGesturePath(Path path)306     public Path getGesturePath(Path path) {
307         path.set(mPath);
308         return path;
309     }
310 
isGestureVisible()311     public boolean isGestureVisible() {
312         return mGestureVisible;
313     }
314 
setGestureVisible(boolean visible)315     public void setGestureVisible(boolean visible) {
316         mGestureVisible = visible;
317     }
318 
getFadeOffset()319     public long getFadeOffset() {
320         return mFadeOffset;
321     }
322 
setFadeOffset(long fadeOffset)323     public void setFadeOffset(long fadeOffset) {
324         mFadeOffset = fadeOffset;
325     }
326 
addOnGestureListener(OnGestureListener listener)327     public void addOnGestureListener(OnGestureListener listener) {
328         mOnGestureListeners.add(listener);
329     }
330 
removeOnGestureListener(OnGestureListener listener)331     public void removeOnGestureListener(OnGestureListener listener) {
332         mOnGestureListeners.remove(listener);
333     }
334 
removeAllOnGestureListeners()335     public void removeAllOnGestureListeners() {
336         mOnGestureListeners.clear();
337     }
338 
addOnGesturePerformedListener(OnGesturePerformedListener listener)339     public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
340         mOnGesturePerformedListeners.add(listener);
341         if (mOnGesturePerformedListeners.size() > 0) {
342             mHandleGestureActions = true;
343         }
344     }
345 
removeOnGesturePerformedListener(OnGesturePerformedListener listener)346     public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
347         mOnGesturePerformedListeners.remove(listener);
348         if (mOnGesturePerformedListeners.size() <= 0) {
349             mHandleGestureActions = false;
350         }
351     }
352 
removeAllOnGesturePerformedListeners()353     public void removeAllOnGesturePerformedListeners() {
354         mOnGesturePerformedListeners.clear();
355         mHandleGestureActions = false;
356     }
357 
addOnGesturingListener(OnGesturingListener listener)358     public void addOnGesturingListener(OnGesturingListener listener) {
359         mOnGesturingListeners.add(listener);
360     }
361 
removeOnGesturingListener(OnGesturingListener listener)362     public void removeOnGesturingListener(OnGesturingListener listener) {
363         mOnGesturingListeners.remove(listener);
364     }
365 
removeAllOnGesturingListeners()366     public void removeAllOnGesturingListeners() {
367         mOnGesturingListeners.clear();
368     }
369 
isGesturing()370     public boolean isGesturing() {
371         return mIsGesturing;
372     }
373 
setCurrentColor(int color)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 
384     /**
385      * @hide
386      */
getGesturePaint()387     public Paint getGesturePaint() {
388         return mGesturePaint;
389     }
390 
391     @Override
draw(Canvas canvas)392     public void draw(Canvas canvas) {
393         super.draw(canvas);
394 
395         if (mCurrentGesture != null && mGestureVisible) {
396             canvas.drawPath(mPath, mGesturePaint);
397         }
398     }
399 
setPaintAlpha(int alpha)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 
clear(boolean animated)407     public void clear(boolean animated) {
408         clear(animated, false, true);
409     }
410 
clear(boolean animated, boolean fireActionPerformed, boolean immediate)411     private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
412         setPaintAlpha(255);
413         removeCallbacks(mFadingOut);
414         mResetGesture = false;
415         mFadingOut.fireActionPerformed = fireActionPerformed;
416         mFadingOut.resetMultipleStrokes = false;
417 
418         if (animated && mCurrentGesture != null) {
419             mFadingAlpha = 1.0f;
420             mIsFadingOut = true;
421             mFadingHasStarted = false;
422             mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
423 
424             postDelayed(mFadingOut, mFadeOffset);
425         } else {
426             mFadingAlpha = 1.0f;
427             mIsFadingOut = false;
428             mFadingHasStarted = false;
429 
430             if (immediate) {
431                 mCurrentGesture = null;
432                 mPath.rewind();
433                 invalidate();
434             } else if (fireActionPerformed) {
435                 postDelayed(mFadingOut, mFadeOffset);
436             } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
437                 mFadingOut.resetMultipleStrokes = true;
438                 postDelayed(mFadingOut, mFadeOffset);
439             } else {
440                 mCurrentGesture = null;
441                 mPath.rewind();
442                 invalidate();
443             }
444         }
445     }
446 
cancelClearAnimation()447     public void cancelClearAnimation() {
448         setPaintAlpha(255);
449         mIsFadingOut = false;
450         mFadingHasStarted = false;
451         removeCallbacks(mFadingOut);
452         mPath.rewind();
453         mCurrentGesture = null;
454     }
455 
cancelGesture()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;
468         int count = listeners.size();
469         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;
477         mPreviousWasGesturing = false;
478         mStrokeBuffer.clear();
479 
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         }
485     }
486 
487     @Override
onDetachedFromWindow()488     protected void onDetachedFromWindow() {
489         cancelClearAnimation();
490     }
491 
492     @Override
dispatchTouchEvent(MotionEvent event)493     public boolean dispatchTouchEvent(MotionEvent event) {
494         if (isEnabled()) {
495             final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
496                     mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
497                     mInterceptEvents;
498 
499             processEvent(event);
500 
501             if (cancelDispatch) {
502                 event.setAction(MotionEvent.ACTION_CANCEL);
503             }
504 
505             super.dispatchTouchEvent(event);
506 
507             return true;
508         }
509 
510         return super.dispatchTouchEvent(event);
511     }
512 
processEvent(MotionEvent event)513     private boolean processEvent(MotionEvent event) {
514         switch (event.getAction()) {
515             case MotionEvent.ACTION_DOWN:
516                 touchDown(event);
517                 invalidate();
518                 return true;
519             case MotionEvent.ACTION_MOVE:
520                 if (mIsListeningForGestures) {
521                     Rect rect = touchMove(event);
522                     if (rect != null) {
523                         invalidate(rect);
524                     }
525                     return true;
526                 }
527                 break;
528             case MotionEvent.ACTION_UP:
529                 if (mIsListeningForGestures) {
530                     touchUp(event, false);
531                     invalidate();
532                     return true;
533                 }
534                 break;
535             case MotionEvent.ACTION_CANCEL:
536                 if (mIsListeningForGestures) {
537                     touchUp(event, true);
538                     invalidate();
539                     return true;
540                 }
541         }
542 
543         return false;
544     }
545 
touchDown(MotionEvent event)546     private void touchDown(MotionEvent event) {
547         mIsListeningForGestures = true;
548 
549         float x = event.getX();
550         float y = event.getY();
551 
552         mX = x;
553         mY = y;
554 
555         mTotalLength = 0;
556         mIsGesturing = false;
557 
558         if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
559             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
560             mResetGesture = false;
561             mCurrentGesture = null;
562             mPath.rewind();
563         } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
564             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
565         }
566 
567         // if there is fading out going on, stop it.
568         if (mFadingHasStarted) {
569             cancelClearAnimation();
570         } else if (mIsFadingOut) {
571             setPaintAlpha(255);
572             mIsFadingOut = false;
573             mFadingHasStarted = false;
574             removeCallbacks(mFadingOut);
575         }
576 
577         if (mCurrentGesture == null) {
578             mCurrentGesture = new Gesture();
579         }
580 
581         mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
582         mPath.moveTo(x, y);
583 
584         final int border = mInvalidateExtraBorder;
585         mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
586 
587         mCurveEndX = x;
588         mCurveEndY = y;
589 
590         // pass the event to handlers
591         final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
592         final int count = listeners.size();
593         for (int i = 0; i < count; i++) {
594             listeners.get(i).onGestureStarted(this, event);
595         }
596     }
597 
touchMove(MotionEvent event)598     private Rect touchMove(MotionEvent event) {
599         Rect areaToRefresh = null;
600 
601         final float x = event.getX();
602         final float y = event.getY();
603 
604         final float previousX = mX;
605         final float previousY = mY;
606 
607         final float dx = Math.abs(x - previousX);
608         final float dy = Math.abs(y - previousY);
609 
610         if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
611             areaToRefresh = mInvalidRect;
612 
613             // start with the curve end
614             final int border = mInvalidateExtraBorder;
615             areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
616                     (int) mCurveEndX + border, (int) mCurveEndY + border);
617 
618             float cX = mCurveEndX = (x + previousX) / 2;
619             float cY = mCurveEndY = (y + previousY) / 2;
620 
621             mPath.quadTo(previousX, previousY, cX, cY);
622 
623             // union with the control point of the new curve
624             areaToRefresh.union((int) previousX - border, (int) previousY - border,
625                     (int) previousX + border, (int) previousY + border);
626 
627             // union with the end point of the new curve
628             areaToRefresh.union((int) cX - border, (int) cY - border,
629                     (int) cX + border, (int) cY + border);
630 
631             mX = x;
632             mY = y;
633 
634             mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
635 
636             if (mHandleGestureActions && !mIsGesturing) {
637                 mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
638 
639                 if (mTotalLength > mGestureStrokeLengthThreshold) {
640                     final OrientedBoundingBox box =
641                             GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
642 
643                     float angle = Math.abs(box.orientation);
644                     if (angle > 90) {
645                         angle = 180 - angle;
646                     }
647 
648                     if (box.squareness > mGestureStrokeSquarenessTreshold ||
649                             (mOrientation == ORIENTATION_VERTICAL ?
650                                     angle < mGestureStrokeAngleThreshold :
651                                     angle > mGestureStrokeAngleThreshold)) {
652 
653                         mIsGesturing = true;
654                         setCurrentColor(mCertainGestureColor);
655 
656                         final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
657                         int count = listeners.size();
658                         for (int i = 0; i < count; i++) {
659                             listeners.get(i).onGesturingStarted(this);
660                         }
661                     }
662                 }
663             }
664 
665             // pass the event to handlers
666             final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
667             final int count = listeners.size();
668             for (int i = 0; i < count; i++) {
669                 listeners.get(i).onGesture(this, event);
670             }
671         }
672 
673         return areaToRefresh;
674     }
675 
touchUp(MotionEvent event, boolean cancel)676     private void touchUp(MotionEvent event, boolean cancel) {
677         mIsListeningForGestures = false;
678 
679         // A gesture wasn't started or was cancelled
680         if (mCurrentGesture != null) {
681             // add the stroke to the current gesture
682             mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
683 
684             if (!cancel) {
685                 // pass the event to handlers
686                 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
687                 int count = listeners.size();
688                 for (int i = 0; i < count; i++) {
689                     listeners.get(i).onGestureEnded(this, event);
690                 }
691 
692                 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
693                         false);
694             } else {
695                 cancelGesture(event);
696 
697             }
698         } else {
699             cancelGesture(event);
700         }
701 
702         mStrokeBuffer.clear();
703         mPreviousWasGesturing = mIsGesturing;
704         mIsGesturing = false;
705 
706         final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
707         int count = listeners.size();
708         for (int i = 0; i < count; i++) {
709             listeners.get(i).onGesturingEnded(this);
710         }
711     }
712 
cancelGesture(MotionEvent event)713     private void cancelGesture(MotionEvent event) {
714         // pass the event to handlers
715         final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
716         final int count = listeners.size();
717         for (int i = 0; i < count; i++) {
718             listeners.get(i).onGestureCancelled(this, event);
719         }
720 
721         clear(false);
722     }
723 
fireOnGesturePerformed()724     private void fireOnGesturePerformed() {
725         final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
726         final int count = actionListeners.size();
727         for (int i = 0; i < count; i++) {
728             actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
729         }
730     }
731 
732     private class FadeOutRunnable implements Runnable {
733         boolean fireActionPerformed;
734         boolean resetMultipleStrokes;
735 
run()736         public void run() {
737             if (mIsFadingOut) {
738                 final long now = AnimationUtils.currentAnimationTimeMillis();
739                 final long duration = now - mFadingStart;
740 
741                 if (duration > mFadeDuration) {
742                     if (fireActionPerformed) {
743                         fireOnGesturePerformed();
744                     }
745 
746                     mPreviousWasGesturing = false;
747                     mIsFadingOut = false;
748                     mFadingHasStarted = false;
749                     mPath.rewind();
750                     mCurrentGesture = null;
751                     setPaintAlpha(255);
752                 } else {
753                     mFadingHasStarted = true;
754                     float interpolatedTime = Math.max(0.0f,
755                             Math.min(1.0f, duration / (float) mFadeDuration));
756                     mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
757                     setPaintAlpha((int) (255 * mFadingAlpha));
758                     postDelayed(this, FADE_ANIMATION_RATE);
759                 }
760             } else if (resetMultipleStrokes) {
761                 mResetGesture = true;
762             } else {
763                 fireOnGesturePerformed();
764 
765                 mFadingHasStarted = false;
766                 mPath.rewind();
767                 mCurrentGesture = null;
768                 mPreviousWasGesturing = false;
769                 setPaintAlpha(255);
770             }
771 
772             invalidate();
773         }
774     }
775 
776     public static interface OnGesturingListener {
onGesturingStarted(GestureOverlayView overlay)777         void onGesturingStarted(GestureOverlayView overlay);
778 
onGesturingEnded(GestureOverlayView overlay)779         void onGesturingEnded(GestureOverlayView overlay);
780     }
781 
782     public static interface OnGestureListener {
onGestureStarted(GestureOverlayView overlay, MotionEvent event)783         void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
784 
onGesture(GestureOverlayView overlay, MotionEvent event)785         void onGesture(GestureOverlayView overlay, MotionEvent event);
786 
onGestureEnded(GestureOverlayView overlay, MotionEvent event)787         void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
788 
onGestureCancelled(GestureOverlayView overlay, MotionEvent event)789         void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
790     }
791 
792     public static interface OnGesturePerformedListener {
onGesturePerformed(GestureOverlayView overlay, Gesture gesture)793         void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
794     }
795 }
796