• 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 defStyleAttr)137     public GestureOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
138         this(context, attrs, defStyleAttr, 0);
139     }
140 
GestureOverlayView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)141     public GestureOverlayView(
142             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
143         super(context, attrs, defStyleAttr, defStyleRes);
144 
145         final TypedArray a = context.obtainStyledAttributes(
146                 attrs, R.styleable.GestureOverlayView, defStyleAttr, defStyleRes);
147 
148         mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
149                 mGestureStrokeWidth);
150         mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
151         mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
152                 mCertainGestureColor);
153         mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
154                 mUncertainGestureColor);
155         mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
156         mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
157         mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
158                 mGestureStrokeType);
159         mGestureStrokeLengthThreshold = a.getFloat(
160                 R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
161                 mGestureStrokeLengthThreshold);
162         mGestureStrokeAngleThreshold = a.getFloat(
163                 R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
164                 mGestureStrokeAngleThreshold);
165         mGestureStrokeSquarenessTreshold = a.getFloat(
166                 R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
167                 mGestureStrokeSquarenessTreshold);
168         mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
169                 mInterceptEvents);
170         mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
171                 mFadeEnabled);
172         mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
173 
174         a.recycle();
175 
176         init();
177     }
178 
init()179     private void init() {
180         setWillNotDraw(false);
181 
182         final Paint gesturePaint = mGesturePaint;
183         gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
184         gesturePaint.setColor(mCertainGestureColor);
185         gesturePaint.setStyle(Paint.Style.STROKE);
186         gesturePaint.setStrokeJoin(Paint.Join.ROUND);
187         gesturePaint.setStrokeCap(Paint.Cap.ROUND);
188         gesturePaint.setStrokeWidth(mGestureStrokeWidth);
189         gesturePaint.setDither(DITHER_FLAG);
190 
191         mCurrentColor = mCertainGestureColor;
192         setPaintAlpha(255);
193     }
194 
getCurrentStroke()195     public ArrayList<GesturePoint> getCurrentStroke() {
196         return mStrokeBuffer;
197     }
198 
getOrientation()199     public int getOrientation() {
200         return mOrientation;
201     }
202 
setOrientation(int orientation)203     public void setOrientation(int orientation) {
204         mOrientation = orientation;
205     }
206 
setGestureColor(int color)207     public void setGestureColor(int color) {
208         mCertainGestureColor = color;
209     }
210 
setUncertainGestureColor(int color)211     public void setUncertainGestureColor(int color) {
212         mUncertainGestureColor = color;
213     }
214 
getUncertainGestureColor()215     public int getUncertainGestureColor() {
216         return mUncertainGestureColor;
217     }
218 
getGestureColor()219     public int getGestureColor() {
220         return mCertainGestureColor;
221     }
222 
getGestureStrokeWidth()223     public float getGestureStrokeWidth() {
224         return mGestureStrokeWidth;
225     }
226 
setGestureStrokeWidth(float gestureStrokeWidth)227     public void setGestureStrokeWidth(float gestureStrokeWidth) {
228         mGestureStrokeWidth = gestureStrokeWidth;
229         mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
230         mGesturePaint.setStrokeWidth(gestureStrokeWidth);
231     }
232 
getGestureStrokeType()233     public int getGestureStrokeType() {
234         return mGestureStrokeType;
235     }
236 
setGestureStrokeType(int gestureStrokeType)237     public void setGestureStrokeType(int gestureStrokeType) {
238         mGestureStrokeType = gestureStrokeType;
239     }
240 
getGestureStrokeLengthThreshold()241     public float getGestureStrokeLengthThreshold() {
242         return mGestureStrokeLengthThreshold;
243     }
244 
setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold)245     public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
246         mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
247     }
248 
getGestureStrokeSquarenessTreshold()249     public float getGestureStrokeSquarenessTreshold() {
250         return mGestureStrokeSquarenessTreshold;
251     }
252 
setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold)253     public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
254         mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
255     }
256 
getGestureStrokeAngleThreshold()257     public float getGestureStrokeAngleThreshold() {
258         return mGestureStrokeAngleThreshold;
259     }
260 
setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold)261     public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
262         mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
263     }
264 
isEventsInterceptionEnabled()265     public boolean isEventsInterceptionEnabled() {
266         return mInterceptEvents;
267     }
268 
setEventsInterceptionEnabled(boolean enabled)269     public void setEventsInterceptionEnabled(boolean enabled) {
270         mInterceptEvents = enabled;
271     }
272 
isFadeEnabled()273     public boolean isFadeEnabled() {
274         return mFadeEnabled;
275     }
276 
setFadeEnabled(boolean fadeEnabled)277     public void setFadeEnabled(boolean fadeEnabled) {
278         mFadeEnabled = fadeEnabled;
279     }
280 
getGesture()281     public Gesture getGesture() {
282         return mCurrentGesture;
283     }
284 
setGesture(Gesture gesture)285     public void setGesture(Gesture gesture) {
286         if (mCurrentGesture != null) {
287             clear(false);
288         }
289 
290         setCurrentColor(mCertainGestureColor);
291         mCurrentGesture = gesture;
292 
293         final Path path = mCurrentGesture.toPath();
294         final RectF bounds = new RectF();
295         path.computeBounds(bounds, true);
296 
297         // TODO: The path should also be scaled to fit inside this view
298         mPath.rewind();
299         mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
300                 -bounds.top + (getHeight() - bounds.height()) / 2.0f);
301 
302         mResetGesture = true;
303 
304         invalidate();
305     }
306 
getGesturePath()307     public Path getGesturePath() {
308         return mPath;
309     }
310 
getGesturePath(Path path)311     public Path getGesturePath(Path path) {
312         path.set(mPath);
313         return path;
314     }
315 
isGestureVisible()316     public boolean isGestureVisible() {
317         return mGestureVisible;
318     }
319 
setGestureVisible(boolean visible)320     public void setGestureVisible(boolean visible) {
321         mGestureVisible = visible;
322     }
323 
getFadeOffset()324     public long getFadeOffset() {
325         return mFadeOffset;
326     }
327 
setFadeOffset(long fadeOffset)328     public void setFadeOffset(long fadeOffset) {
329         mFadeOffset = fadeOffset;
330     }
331 
addOnGestureListener(OnGestureListener listener)332     public void addOnGestureListener(OnGestureListener listener) {
333         mOnGestureListeners.add(listener);
334     }
335 
removeOnGestureListener(OnGestureListener listener)336     public void removeOnGestureListener(OnGestureListener listener) {
337         mOnGestureListeners.remove(listener);
338     }
339 
removeAllOnGestureListeners()340     public void removeAllOnGestureListeners() {
341         mOnGestureListeners.clear();
342     }
343 
addOnGesturePerformedListener(OnGesturePerformedListener listener)344     public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
345         mOnGesturePerformedListeners.add(listener);
346         if (mOnGesturePerformedListeners.size() > 0) {
347             mHandleGestureActions = true;
348         }
349     }
350 
removeOnGesturePerformedListener(OnGesturePerformedListener listener)351     public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
352         mOnGesturePerformedListeners.remove(listener);
353         if (mOnGesturePerformedListeners.size() <= 0) {
354             mHandleGestureActions = false;
355         }
356     }
357 
removeAllOnGesturePerformedListeners()358     public void removeAllOnGesturePerformedListeners() {
359         mOnGesturePerformedListeners.clear();
360         mHandleGestureActions = false;
361     }
362 
addOnGesturingListener(OnGesturingListener listener)363     public void addOnGesturingListener(OnGesturingListener listener) {
364         mOnGesturingListeners.add(listener);
365     }
366 
removeOnGesturingListener(OnGesturingListener listener)367     public void removeOnGesturingListener(OnGesturingListener listener) {
368         mOnGesturingListeners.remove(listener);
369     }
370 
removeAllOnGesturingListeners()371     public void removeAllOnGesturingListeners() {
372         mOnGesturingListeners.clear();
373     }
374 
isGesturing()375     public boolean isGesturing() {
376         return mIsGesturing;
377     }
378 
setCurrentColor(int color)379     private void setCurrentColor(int color) {
380         mCurrentColor = color;
381         if (mFadingHasStarted) {
382             setPaintAlpha((int) (255 * mFadingAlpha));
383         } else {
384             setPaintAlpha(255);
385         }
386         invalidate();
387     }
388 
389     /**
390      * @hide
391      */
getGesturePaint()392     public Paint getGesturePaint() {
393         return mGesturePaint;
394     }
395 
396     @Override
draw(Canvas canvas)397     public void draw(Canvas canvas) {
398         super.draw(canvas);
399 
400         if (mCurrentGesture != null && mGestureVisible) {
401             canvas.drawPath(mPath, mGesturePaint);
402         }
403     }
404 
setPaintAlpha(int alpha)405     private void setPaintAlpha(int alpha) {
406         alpha += alpha >> 7;
407         final int baseAlpha = mCurrentColor >>> 24;
408         final int useAlpha = baseAlpha * alpha >> 8;
409         mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
410     }
411 
clear(boolean animated)412     public void clear(boolean animated) {
413         clear(animated, false, true);
414     }
415 
clear(boolean animated, boolean fireActionPerformed, boolean immediate)416     private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
417         setPaintAlpha(255);
418         removeCallbacks(mFadingOut);
419         mResetGesture = false;
420         mFadingOut.fireActionPerformed = fireActionPerformed;
421         mFadingOut.resetMultipleStrokes = false;
422 
423         if (animated && mCurrentGesture != null) {
424             mFadingAlpha = 1.0f;
425             mIsFadingOut = true;
426             mFadingHasStarted = false;
427             mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
428 
429             postDelayed(mFadingOut, mFadeOffset);
430         } else {
431             mFadingAlpha = 1.0f;
432             mIsFadingOut = false;
433             mFadingHasStarted = false;
434 
435             if (immediate) {
436                 mCurrentGesture = null;
437                 mPath.rewind();
438                 invalidate();
439             } else if (fireActionPerformed) {
440                 postDelayed(mFadingOut, mFadeOffset);
441             } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
442                 mFadingOut.resetMultipleStrokes = true;
443                 postDelayed(mFadingOut, mFadeOffset);
444             } else {
445                 mCurrentGesture = null;
446                 mPath.rewind();
447                 invalidate();
448             }
449         }
450     }
451 
cancelClearAnimation()452     public void cancelClearAnimation() {
453         setPaintAlpha(255);
454         mIsFadingOut = false;
455         mFadingHasStarted = false;
456         removeCallbacks(mFadingOut);
457         mPath.rewind();
458         mCurrentGesture = null;
459     }
460 
cancelGesture()461     public void cancelGesture() {
462         mIsListeningForGestures = false;
463 
464         // add the stroke to the current gesture
465         mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
466 
467         // pass the event to handlers
468         final long now = SystemClock.uptimeMillis();
469         final MotionEvent event = MotionEvent.obtain(now, now,
470                 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
471 
472         final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
473         int count = listeners.size();
474         for (int i = 0; i < count; i++) {
475             listeners.get(i).onGestureCancelled(this, event);
476         }
477 
478         event.recycle();
479 
480         clear(false);
481         mIsGesturing = false;
482         mPreviousWasGesturing = false;
483         mStrokeBuffer.clear();
484 
485         final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners;
486         count = otherListeners.size();
487         for (int i = 0; i < count; i++) {
488             otherListeners.get(i).onGesturingEnded(this);
489         }
490     }
491 
492     @Override
onDetachedFromWindow()493     protected void onDetachedFromWindow() {
494         super.onDetachedFromWindow();
495         cancelClearAnimation();
496     }
497 
498     @Override
dispatchTouchEvent(MotionEvent event)499     public boolean dispatchTouchEvent(MotionEvent event) {
500         if (isEnabled()) {
501             final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
502                     mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
503                     mInterceptEvents;
504 
505             processEvent(event);
506 
507             if (cancelDispatch) {
508                 event.setAction(MotionEvent.ACTION_CANCEL);
509             }
510 
511             super.dispatchTouchEvent(event);
512 
513             return true;
514         }
515 
516         return super.dispatchTouchEvent(event);
517     }
518 
processEvent(MotionEvent event)519     private boolean processEvent(MotionEvent event) {
520         switch (event.getAction()) {
521             case MotionEvent.ACTION_DOWN:
522                 touchDown(event);
523                 invalidate();
524                 return true;
525             case MotionEvent.ACTION_MOVE:
526                 if (mIsListeningForGestures) {
527                     Rect rect = touchMove(event);
528                     if (rect != null) {
529                         invalidate(rect);
530                     }
531                     return true;
532                 }
533                 break;
534             case MotionEvent.ACTION_UP:
535                 if (mIsListeningForGestures) {
536                     touchUp(event, false);
537                     invalidate();
538                     return true;
539                 }
540                 break;
541             case MotionEvent.ACTION_CANCEL:
542                 if (mIsListeningForGestures) {
543                     touchUp(event, true);
544                     invalidate();
545                     return true;
546                 }
547         }
548 
549         return false;
550     }
551 
touchDown(MotionEvent event)552     private void touchDown(MotionEvent event) {
553         mIsListeningForGestures = true;
554 
555         float x = event.getX();
556         float y = event.getY();
557 
558         mX = x;
559         mY = y;
560 
561         mTotalLength = 0;
562         mIsGesturing = false;
563 
564         if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
565             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
566             mResetGesture = false;
567             mCurrentGesture = null;
568             mPath.rewind();
569         } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
570             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
571         }
572 
573         // if there is fading out going on, stop it.
574         if (mFadingHasStarted) {
575             cancelClearAnimation();
576         } else if (mIsFadingOut) {
577             setPaintAlpha(255);
578             mIsFadingOut = false;
579             mFadingHasStarted = false;
580             removeCallbacks(mFadingOut);
581         }
582 
583         if (mCurrentGesture == null) {
584             mCurrentGesture = new Gesture();
585         }
586 
587         mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
588         mPath.moveTo(x, y);
589 
590         final int border = mInvalidateExtraBorder;
591         mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
592 
593         mCurveEndX = x;
594         mCurveEndY = y;
595 
596         // pass the event to handlers
597         final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
598         final int count = listeners.size();
599         for (int i = 0; i < count; i++) {
600             listeners.get(i).onGestureStarted(this, event);
601         }
602     }
603 
touchMove(MotionEvent event)604     private Rect touchMove(MotionEvent event) {
605         Rect areaToRefresh = null;
606 
607         final float x = event.getX();
608         final float y = event.getY();
609 
610         final float previousX = mX;
611         final float previousY = mY;
612 
613         final float dx = Math.abs(x - previousX);
614         final float dy = Math.abs(y - previousY);
615 
616         if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
617             areaToRefresh = mInvalidRect;
618 
619             // start with the curve end
620             final int border = mInvalidateExtraBorder;
621             areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
622                     (int) mCurveEndX + border, (int) mCurveEndY + border);
623 
624             float cX = mCurveEndX = (x + previousX) / 2;
625             float cY = mCurveEndY = (y + previousY) / 2;
626 
627             mPath.quadTo(previousX, previousY, cX, cY);
628 
629             // union with the control point of the new curve
630             areaToRefresh.union((int) previousX - border, (int) previousY - border,
631                     (int) previousX + border, (int) previousY + border);
632 
633             // union with the end point of the new curve
634             areaToRefresh.union((int) cX - border, (int) cY - border,
635                     (int) cX + border, (int) cY + border);
636 
637             mX = x;
638             mY = y;
639 
640             mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
641 
642             if (mHandleGestureActions && !mIsGesturing) {
643                 mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
644 
645                 if (mTotalLength > mGestureStrokeLengthThreshold) {
646                     final OrientedBoundingBox box =
647                             GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
648 
649                     float angle = Math.abs(box.orientation);
650                     if (angle > 90) {
651                         angle = 180 - angle;
652                     }
653 
654                     if (box.squareness > mGestureStrokeSquarenessTreshold ||
655                             (mOrientation == ORIENTATION_VERTICAL ?
656                                     angle < mGestureStrokeAngleThreshold :
657                                     angle > mGestureStrokeAngleThreshold)) {
658 
659                         mIsGesturing = true;
660                         setCurrentColor(mCertainGestureColor);
661 
662                         final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
663                         int count = listeners.size();
664                         for (int i = 0; i < count; i++) {
665                             listeners.get(i).onGesturingStarted(this);
666                         }
667                     }
668                 }
669             }
670 
671             // pass the event to handlers
672             final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
673             final int count = listeners.size();
674             for (int i = 0; i < count; i++) {
675                 listeners.get(i).onGesture(this, event);
676             }
677         }
678 
679         return areaToRefresh;
680     }
681 
touchUp(MotionEvent event, boolean cancel)682     private void touchUp(MotionEvent event, boolean cancel) {
683         mIsListeningForGestures = false;
684 
685         // A gesture wasn't started or was cancelled
686         if (mCurrentGesture != null) {
687             // add the stroke to the current gesture
688             mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
689 
690             if (!cancel) {
691                 // pass the event to handlers
692                 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
693                 int count = listeners.size();
694                 for (int i = 0; i < count; i++) {
695                     listeners.get(i).onGestureEnded(this, event);
696                 }
697 
698                 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
699                         false);
700             } else {
701                 cancelGesture(event);
702 
703             }
704         } else {
705             cancelGesture(event);
706         }
707 
708         mStrokeBuffer.clear();
709         mPreviousWasGesturing = mIsGesturing;
710         mIsGesturing = false;
711 
712         final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
713         int count = listeners.size();
714         for (int i = 0; i < count; i++) {
715             listeners.get(i).onGesturingEnded(this);
716         }
717     }
718 
cancelGesture(MotionEvent event)719     private void cancelGesture(MotionEvent event) {
720         // pass the event to handlers
721         final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
722         final int count = listeners.size();
723         for (int i = 0; i < count; i++) {
724             listeners.get(i).onGestureCancelled(this, event);
725         }
726 
727         clear(false);
728     }
729 
fireOnGesturePerformed()730     private void fireOnGesturePerformed() {
731         final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
732         final int count = actionListeners.size();
733         for (int i = 0; i < count; i++) {
734             actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
735         }
736     }
737 
738     private class FadeOutRunnable implements Runnable {
739         boolean fireActionPerformed;
740         boolean resetMultipleStrokes;
741 
run()742         public void run() {
743             if (mIsFadingOut) {
744                 final long now = AnimationUtils.currentAnimationTimeMillis();
745                 final long duration = now - mFadingStart;
746 
747                 if (duration > mFadeDuration) {
748                     if (fireActionPerformed) {
749                         fireOnGesturePerformed();
750                     }
751 
752                     mPreviousWasGesturing = false;
753                     mIsFadingOut = false;
754                     mFadingHasStarted = false;
755                     mPath.rewind();
756                     mCurrentGesture = null;
757                     setPaintAlpha(255);
758                 } else {
759                     mFadingHasStarted = true;
760                     float interpolatedTime = Math.max(0.0f,
761                             Math.min(1.0f, duration / (float) mFadeDuration));
762                     mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
763                     setPaintAlpha((int) (255 * mFadingAlpha));
764                     postDelayed(this, FADE_ANIMATION_RATE);
765                 }
766             } else if (resetMultipleStrokes) {
767                 mResetGesture = true;
768             } else {
769                 fireOnGesturePerformed();
770 
771                 mFadingHasStarted = false;
772                 mPath.rewind();
773                 mCurrentGesture = null;
774                 mPreviousWasGesturing = false;
775                 setPaintAlpha(255);
776             }
777 
778             invalidate();
779         }
780     }
781 
782     public static interface OnGesturingListener {
onGesturingStarted(GestureOverlayView overlay)783         void onGesturingStarted(GestureOverlayView overlay);
784 
onGesturingEnded(GestureOverlayView overlay)785         void onGesturingEnded(GestureOverlayView overlay);
786     }
787 
788     public static interface OnGestureListener {
onGestureStarted(GestureOverlayView overlay, MotionEvent event)789         void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
790 
onGesture(GestureOverlayView overlay, MotionEvent event)791         void onGesture(GestureOverlayView overlay, MotionEvent event);
792 
onGestureEnded(GestureOverlayView overlay, MotionEvent event)793         void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
794 
onGestureCancelled(GestureOverlayView overlay, MotionEvent event)795         void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
796     }
797 
798     public static interface OnGesturePerformedListener {
onGesturePerformed(GestureOverlayView overlay, Gesture gesture)799         void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
800     }
801 }
802