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