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