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