summaryrefslogtreecommitdiffstats
path: root/core/java/android/gesture/GestureOverlayView.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/gesture/GestureOverlayView.java')
-rwxr-xr-xcore/java/android/gesture/GestureOverlayView.java793
1 files changed, 793 insertions, 0 deletions
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java
new file mode 100755
index 0000000..5bfdcc4
--- /dev/null
+++ b/core/java/android/gesture/GestureOverlayView.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gesture;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.animation.AnimationUtils;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.os.SystemClock;
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * A transparent overlay for gesture input that can be placed on top of other
+ * widgets or contain other widgets.
+ *
+ * @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled
+ * @attr ref android.R.styleable#GestureOverlayView_fadeDuration
+ * @attr ref android.R.styleable#GestureOverlayView_fadeOffset
+ * @attr ref android.R.styleable#GestureOverlayView_fadeEnabled
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeAngleThreshold
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeLengthThreshold
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeSquarenessThreshold
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType
+ * @attr ref android.R.styleable#GestureOverlayView_gestureColor
+ * @attr ref android.R.styleable#GestureOverlayView_orientation
+ * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
+ */
+public class GestureOverlayView extends FrameLayout {
+ public static final int GESTURE_STROKE_TYPE_SINGLE = 0;
+ public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1;
+
+ public static final int ORIENTATION_HORIZONTAL = 0;
+ public static final int ORIENTATION_VERTICAL = 1;
+
+ private static final int FADE_ANIMATION_RATE = 16;
+ private static final boolean GESTURE_RENDERING_ANTIALIAS = true;
+ private static final boolean DITHER_FLAG = true;
+
+ private final Paint mGesturePaint = new Paint();
+
+ private long mFadeDuration = 150;
+ private long mFadeOffset = 420;
+ private long mFadingStart;
+ private boolean mFadingHasStarted;
+ private boolean mFadeEnabled = true;
+
+ private int mCurrentColor;
+ private int mCertainGestureColor = 0xFFFFFF00;
+ private int mUncertainGestureColor = 0x48FFFF00;
+ private float mGestureStrokeWidth = 12.0f;
+ private int mInvalidateExtraBorder = 10;
+
+ private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE;
+ private float mGestureStrokeLengthThreshold = 50.0f;
+ private float mGestureStrokeSquarenessTreshold = 0.275f;
+ private float mGestureStrokeAngleThreshold = 40.0f;
+
+ private int mOrientation = ORIENTATION_VERTICAL;
+
+ private final Rect mInvalidRect = new Rect();
+ private final Path mPath = new Path();
+ private boolean mGestureVisible = true;
+
+ private float mX;
+ private float mY;
+
+ private float mCurveEndX;
+ private float mCurveEndY;
+
+ private float mTotalLength;
+ private boolean mIsGesturing = false;
+ private boolean mPreviousWasGesturing = false;
+ private boolean mInterceptEvents = true;
+ private boolean mIsListeningForGestures;
+ private boolean mResetGesture;
+
+ // current gesture
+ private Gesture mCurrentGesture;
+ private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList<OnGestureListener> mOnGestureListeners =
+ new ArrayList<OnGestureListener>();
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList<OnGesturePerformedListener> mOnGesturePerformedListeners =
+ new ArrayList<OnGesturePerformedListener>();
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList<OnGesturingListener> mOnGesturingListeners =
+ new ArrayList<OnGesturingListener>();
+
+ private boolean mHandleGestureActions;
+
+ // fading out effect
+ private boolean mIsFadingOut = false;
+ private float mFadingAlpha = 1.0f;
+ private final AccelerateDecelerateInterpolator mInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ private final FadeOutRunnable mFadingOut = new FadeOutRunnable();
+
+ public GestureOverlayView(Context context) {
+ super(context);
+ init();
+ }
+
+ public GestureOverlayView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle);
+ }
+
+ public GestureOverlayView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.GestureOverlayView, defStyle, 0);
+
+ mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
+ mGestureStrokeWidth);
+ mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
+ mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
+ mCertainGestureColor);
+ mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
+ mUncertainGestureColor);
+ mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
+ mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
+ mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
+ mGestureStrokeType);
+ mGestureStrokeLengthThreshold = a.getFloat(
+ R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
+ mGestureStrokeLengthThreshold);
+ mGestureStrokeAngleThreshold = a.getFloat(
+ R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
+ mGestureStrokeAngleThreshold);
+ mGestureStrokeSquarenessTreshold = a.getFloat(
+ R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
+ mGestureStrokeSquarenessTreshold);
+ mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
+ mInterceptEvents);
+ mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
+ mFadeEnabled);
+ mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
+
+ a.recycle();
+
+ init();
+ }
+
+ private void init() {
+ setWillNotDraw(false);
+
+ final Paint gesturePaint = mGesturePaint;
+ gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
+ gesturePaint.setColor(mCertainGestureColor);
+ gesturePaint.setStyle(Paint.Style.STROKE);
+ gesturePaint.setStrokeJoin(Paint.Join.ROUND);
+ gesturePaint.setStrokeCap(Paint.Cap.ROUND);
+ gesturePaint.setStrokeWidth(mGestureStrokeWidth);
+ gesturePaint.setDither(DITHER_FLAG);
+
+ mCurrentColor = mCertainGestureColor;
+ setPaintAlpha(255);
+ }
+
+ public ArrayList<GesturePoint> getCurrentStroke() {
+ return mStrokeBuffer;
+ }
+
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ public void setOrientation(int orientation) {
+ mOrientation = orientation;
+ }
+
+ public void setGestureColor(int color) {
+ mCertainGestureColor = color;
+ }
+
+ public void setUncertainGestureColor(int color) {
+ mUncertainGestureColor = color;
+ }
+
+ public int getUncertainGestureColor() {
+ return mUncertainGestureColor;
+ }
+
+ public int getGestureColor() {
+ return mCertainGestureColor;
+ }
+
+ public float getGestureStrokeWidth() {
+ return mGestureStrokeWidth;
+ }
+
+ public void setGestureStrokeWidth(float gestureStrokeWidth) {
+ mGestureStrokeWidth = gestureStrokeWidth;
+ mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
+ mGesturePaint.setStrokeWidth(gestureStrokeWidth);
+ }
+
+ public int getGestureStrokeType() {
+ return mGestureStrokeType;
+ }
+
+ public void setGestureStrokeType(int gestureStrokeType) {
+ mGestureStrokeType = gestureStrokeType;
+ }
+
+ public float getGestureStrokeLengthThreshold() {
+ return mGestureStrokeLengthThreshold;
+ }
+
+ public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
+ mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
+ }
+
+ public float getGestureStrokeSquarenessTreshold() {
+ return mGestureStrokeSquarenessTreshold;
+ }
+
+ public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
+ mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
+ }
+
+ public float getGestureStrokeAngleThreshold() {
+ return mGestureStrokeAngleThreshold;
+ }
+
+ public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
+ mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
+ }
+
+ public boolean isEventsInterceptionEnabled() {
+ return mInterceptEvents;
+ }
+
+ public void setEventsInterceptionEnabled(boolean enabled) {
+ mInterceptEvents = enabled;
+ }
+
+ public boolean isFadeEnabled() {
+ return mFadeEnabled;
+ }
+
+ public void setFadeEnabled(boolean fadeEnabled) {
+ mFadeEnabled = fadeEnabled;
+ }
+
+ public Gesture getGesture() {
+ return mCurrentGesture;
+ }
+
+ public void setGesture(Gesture gesture) {
+ if (mCurrentGesture != null) {
+ clear(false);
+ }
+
+ setCurrentColor(mCertainGestureColor);
+ mCurrentGesture = gesture;
+
+ final Path path = mCurrentGesture.toPath();
+ final RectF bounds = new RectF();
+ path.computeBounds(bounds, true);
+
+ // TODO: The path should also be scaled to fit inside this view
+ mPath.rewind();
+ mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
+ -bounds.top + (getHeight() - bounds.height()) / 2.0f);
+
+ mResetGesture = true;
+
+ invalidate();
+ }
+
+ public Path getGesturePath() {
+ return mPath;
+ }
+
+ public Path getGesturePath(Path path) {
+ path.set(mPath);
+ return path;
+ }
+
+ public boolean isGestureVisible() {
+ return mGestureVisible;
+ }
+
+ public void setGestureVisible(boolean visible) {
+ mGestureVisible = visible;
+ }
+
+ public long getFadeOffset() {
+ return mFadeOffset;
+ }
+
+ public void setFadeOffset(long fadeOffset) {
+ mFadeOffset = fadeOffset;
+ }
+
+ public void addOnGestureListener(OnGestureListener listener) {
+ mOnGestureListeners.add(listener);
+ }
+
+ public void removeOnGestureListener(OnGestureListener listener) {
+ mOnGestureListeners.remove(listener);
+ }
+
+ public void removeAllOnGestureListeners() {
+ mOnGestureListeners.clear();
+ }
+
+ public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
+ mOnGesturePerformedListeners.add(listener);
+ if (mOnGesturePerformedListeners.size() > 0) {
+ mHandleGestureActions = true;
+ }
+ }
+
+ public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
+ mOnGesturePerformedListeners.remove(listener);
+ if (mOnGesturePerformedListeners.size() <= 0) {
+ mHandleGestureActions = false;
+ }
+ }
+
+ public void removeAllOnGesturePerformedListeners() {
+ mOnGesturePerformedListeners.clear();
+ mHandleGestureActions = false;
+ }
+
+ public void addOnGesturingListener(OnGesturingListener listener) {
+ mOnGesturingListeners.add(listener);
+ }
+
+ public void removeOnGesturingListener(OnGesturingListener listener) {
+ mOnGesturingListeners.remove(listener);
+ }
+
+ public void removeAllOnGesturingListeners() {
+ mOnGesturingListeners.clear();
+ }
+
+ public boolean isGesturing() {
+ return mIsGesturing;
+ }
+
+ private void setCurrentColor(int color) {
+ mCurrentColor = color;
+ if (mFadingHasStarted) {
+ setPaintAlpha((int) (255 * mFadingAlpha));
+ } else {
+ setPaintAlpha(255);
+ }
+ invalidate();
+ }
+
+ /**
+ * @hide
+ */
+ public Paint getGesturePaint() {
+ return mGesturePaint;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mCurrentGesture != null && mGestureVisible) {
+ canvas.drawPath(mPath, mGesturePaint);
+ }
+ }
+
+ private void setPaintAlpha(int alpha) {
+ alpha += alpha >> 7;
+ final int baseAlpha = mCurrentColor >>> 24;
+ final int useAlpha = baseAlpha * alpha >> 8;
+ mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
+ }
+
+ public void clear(boolean animated) {
+ clear(animated, false, true);
+ }
+
+ private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
+ setPaintAlpha(255);
+ removeCallbacks(mFadingOut);
+ mResetGesture = false;
+ mFadingOut.fireActionPerformed = fireActionPerformed;
+ mFadingOut.resetMultipleStrokes = false;
+
+ if (animated && mCurrentGesture != null) {
+ mFadingAlpha = 1.0f;
+ mIsFadingOut = true;
+ mFadingHasStarted = false;
+ mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
+
+ postDelayed(mFadingOut, mFadeOffset);
+ } else {
+ mFadingAlpha = 1.0f;
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+
+ if (immediate) {
+ mCurrentGesture = null;
+ mPath.rewind();
+ invalidate();
+ } else if (fireActionPerformed) {
+ postDelayed(mFadingOut, mFadeOffset);
+ } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
+ mFadingOut.resetMultipleStrokes = true;
+ postDelayed(mFadingOut, mFadeOffset);
+ } else {
+ mCurrentGesture = null;
+ mPath.rewind();
+ invalidate();
+ }
+ }
+ }
+
+ public void cancelClearAnimation() {
+ setPaintAlpha(255);
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+ removeCallbacks(mFadingOut);
+ mPath.rewind();
+ mCurrentGesture = null;
+ }
+
+ public void cancelGesture() {
+ mIsListeningForGestures = false;
+
+ // add the stroke to the current gesture
+ mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
+
+ // pass the event to handlers
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureCancelled(this, event);
+ }
+
+ event.recycle();
+
+ clear(false);
+ mIsGesturing = false;
+ mPreviousWasGesturing = false;
+ mStrokeBuffer.clear();
+
+ final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners;
+ count = otherListeners.size();
+ for (int i = 0; i < count; i++) {
+ otherListeners.get(i).onGesturingEnded(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ cancelClearAnimation();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (isEnabled()) {
+ final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
+ mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
+ mInterceptEvents;
+
+ processEvent(event);
+
+ if (cancelDispatch) {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ }
+
+ super.dispatchTouchEvent(event);
+
+ return true;
+ }
+
+ return super.dispatchTouchEvent(event);
+ }
+
+ private boolean processEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ touchDown(event);
+ invalidate();
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ if (mIsListeningForGestures) {
+ Rect rect = touchMove(event);
+ if (rect != null) {
+ invalidate(rect);
+ }
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mIsListeningForGestures) {
+ touchUp(event, false);
+ invalidate();
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsListeningForGestures) {
+ touchUp(event, true);
+ invalidate();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void touchDown(MotionEvent event) {
+ mIsListeningForGestures = true;
+
+ float x = event.getX();
+ float y = event.getY();
+
+ mX = x;
+ mY = y;
+
+ mTotalLength = 0;
+ mIsGesturing = false;
+
+ if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
+ if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
+ mResetGesture = false;
+ mCurrentGesture = null;
+ mPath.rewind();
+ } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
+ if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
+ }
+
+ // if there is fading out going on, stop it.
+ if (mFadingHasStarted) {
+ cancelClearAnimation();
+ } else if (mIsFadingOut) {
+ setPaintAlpha(255);
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+ removeCallbacks(mFadingOut);
+ }
+
+ if (mCurrentGesture == null) {
+ mCurrentGesture = new Gesture();
+ }
+
+ mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+ mPath.moveTo(x, y);
+
+ final int border = mInvalidateExtraBorder;
+ mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
+
+ mCurveEndX = x;
+ mCurveEndY = y;
+
+ // pass the event to handlers
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ final int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureStarted(this, event);
+ }
+ }
+
+ private Rect touchMove(MotionEvent event) {
+ Rect areaToRefresh = null;
+
+ final float x = event.getX();
+ final float y = event.getY();
+
+ final float previousX = mX;
+ final float previousY = mY;
+
+ final float dx = Math.abs(x - previousX);
+ final float dy = Math.abs(y - previousY);
+
+ if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
+ areaToRefresh = mInvalidRect;
+
+ // start with the curve end
+ final int border = mInvalidateExtraBorder;
+ areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
+ (int) mCurveEndX + border, (int) mCurveEndY + border);
+
+ float cX = mCurveEndX = (x + previousX) / 2;
+ float cY = mCurveEndY = (y + previousY) / 2;
+
+ mPath.quadTo(previousX, previousY, cX, cY);
+
+ // union with the control point of the new curve
+ areaToRefresh.union((int) previousX - border, (int) previousY - border,
+ (int) previousX + border, (int) previousY + border);
+
+ // union with the end point of the new curve
+ areaToRefresh.union((int) cX - border, (int) cY - border,
+ (int) cX + border, (int) cY + border);
+
+ mX = x;
+ mY = y;
+
+ mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+
+ if (mHandleGestureActions && !mIsGesturing) {
+ mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
+
+ if (mTotalLength > mGestureStrokeLengthThreshold) {
+ final OrientedBoundingBox box =
+ GestureUtilities.computeOrientedBoundingBox(mStrokeBuffer);
+
+ float angle = Math.abs(box.orientation);
+ if (angle > 90) {
+ angle = 180 - angle;
+ }
+
+ if (box.squareness > mGestureStrokeSquarenessTreshold ||
+ (mOrientation == ORIENTATION_VERTICAL ?
+ angle < mGestureStrokeAngleThreshold :
+ angle > mGestureStrokeAngleThreshold)) {
+
+ mIsGesturing = true;
+ setCurrentColor(mCertainGestureColor);
+
+ final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesturingStarted(this);
+ }
+ }
+ }
+ }
+
+ // pass the event to handlers
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ final int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesture(this, event);
+ }
+ }
+
+ return areaToRefresh;
+ }
+
+ private void touchUp(MotionEvent event, boolean cancel) {
+ mIsListeningForGestures = false;
+
+ // A gesture wasn't started or was cancelled
+ if (mCurrentGesture != null) {
+ // add the stroke to the current gesture
+ mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
+
+ if (!cancel) {
+ // pass the event to handlers
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureEnded(this, event);
+ }
+
+ clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
+ false);
+ } else {
+ cancelGesture(event);
+
+ }
+ } else {
+ cancelGesture(event);
+ }
+
+ mStrokeBuffer.clear();
+ mPreviousWasGesturing = mIsGesturing;
+ mIsGesturing = false;
+
+ final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesturingEnded(this);
+ }
+ }
+
+ private void cancelGesture(MotionEvent event) {
+ // pass the event to handlers
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ final int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureCancelled(this, event);
+ }
+
+ clear(false);
+ }
+
+ private void fireOnGesturePerformed() {
+ final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
+ final int count = actionListeners.size();
+ for (int i = 0; i < count; i++) {
+ actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
+ }
+ }
+
+ private class FadeOutRunnable implements Runnable {
+ boolean fireActionPerformed;
+ boolean resetMultipleStrokes;
+
+ public void run() {
+ if (mIsFadingOut) {
+ final long now = AnimationUtils.currentAnimationTimeMillis();
+ final long duration = now - mFadingStart;
+
+ if (duration > mFadeDuration) {
+ if (fireActionPerformed) {
+ fireOnGesturePerformed();
+ }
+
+ mPreviousWasGesturing = false;
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+ mPath.rewind();
+ mCurrentGesture = null;
+ setPaintAlpha(255);
+ } else {
+ mFadingHasStarted = true;
+ float interpolatedTime = Math.max(0.0f,
+ Math.min(1.0f, duration / (float) mFadeDuration));
+ mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
+ setPaintAlpha((int) (255 * mFadingAlpha));
+ postDelayed(this, FADE_ANIMATION_RATE);
+ }
+ } else if (resetMultipleStrokes) {
+ mResetGesture = true;
+ } else {
+ fireOnGesturePerformed();
+
+ mFadingHasStarted = false;
+ mPath.rewind();
+ mCurrentGesture = null;
+ mPreviousWasGesturing = false;
+ setPaintAlpha(255);
+ }
+
+ invalidate();
+ }
+ }
+
+ public static interface OnGesturingListener {
+ void onGesturingStarted(GestureOverlayView overlay);
+
+ void onGesturingEnded(GestureOverlayView overlay);
+ }
+
+ public static interface OnGestureListener {
+ void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
+
+ void onGesture(GestureOverlayView overlay, MotionEvent event);
+
+ void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
+
+ void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
+ }
+
+ public static interface OnGesturePerformedListener {
+ void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
+ }
+}