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 cancelClearAnimation(); 490 } 491 492 @Override 493 public boolean dispatchTouchEvent(MotionEvent event) { 494 if (isEnabled()) { 495 final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null && 496 mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) && 497 mInterceptEvents; 498 499 processEvent(event); 500 501 if (cancelDispatch) { 502 event.setAction(MotionEvent.ACTION_CANCEL); 503 } 504 505 super.dispatchTouchEvent(event); 506 507 return true; 508 } 509 510 return super.dispatchTouchEvent(event); 511 } 512 513 private boolean processEvent(MotionEvent event) { 514 switch (event.getAction()) { 515 case MotionEvent.ACTION_DOWN: 516 touchDown(event); 517 invalidate(); 518 return true; 519 case MotionEvent.ACTION_MOVE: 520 if (mIsListeningForGestures) { 521 Rect rect = touchMove(event); 522 if (rect != null) { 523 invalidate(rect); 524 } 525 return true; 526 } 527 break; 528 case MotionEvent.ACTION_UP: 529 if (mIsListeningForGestures) { 530 touchUp(event, false); 531 invalidate(); 532 return true; 533 } 534 break; 535 case MotionEvent.ACTION_CANCEL: 536 if (mIsListeningForGestures) { 537 touchUp(event, true); 538 invalidate(); 539 return true; 540 } 541 } 542 543 return false; 544 } 545 546 private void touchDown(MotionEvent event) { 547 mIsListeningForGestures = true; 548 549 float x = event.getX(); 550 float y = event.getY(); 551 552 mX = x; 553 mY = y; 554 555 mTotalLength = 0; 556 mIsGesturing = false; 557 558 if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) { 559 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor); 560 mResetGesture = false; 561 mCurrentGesture = null; 562 mPath.rewind(); 563 } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) { 564 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor); 565 } 566 567 // if there is fading out going on, stop it. 568 if (mFadingHasStarted) { 569 cancelClearAnimation(); 570 } else if (mIsFadingOut) { 571 setPaintAlpha(255); 572 mIsFadingOut = false; 573 mFadingHasStarted = false; 574 removeCallbacks(mFadingOut); 575 } 576 577 if (mCurrentGesture == null) { 578 mCurrentGesture = new Gesture(); 579 } 580 581 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 582 mPath.moveTo(x, y); 583 584 final int border = mInvalidateExtraBorder; 585 mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border); 586 587 mCurveEndX = x; 588 mCurveEndY = y; 589 590 // pass the event to handlers 591 final ArrayList<OnGestureListener> listeners = mOnGestureListeners; 592 final int count = listeners.size(); 593 for (int i = 0; i < count; i++) { 594 listeners.get(i).onGestureStarted(this, event); 595 } 596 } 597 598 private Rect touchMove(MotionEvent event) { 599 Rect areaToRefresh = null; 600 601 final float x = event.getX(); 602 final float y = event.getY(); 603 604 final float previousX = mX; 605 final float previousY = mY; 606 607 final float dx = Math.abs(x - previousX); 608 final float dy = Math.abs(y - previousY); 609 610 if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) { 611 areaToRefresh = mInvalidRect; 612 613 // start with the curve end 614 final int border = mInvalidateExtraBorder; 615 areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border, 616 (int) mCurveEndX + border, (int) mCurveEndY + border); 617 618 float cX = mCurveEndX = (x + previousX) / 2; 619 float cY = mCurveEndY = (y + previousY) / 2; 620 621 mPath.quadTo(previousX, previousY, cX, cY); 622 623 // union with the control point of the new curve 624 areaToRefresh.union((int) previousX - border, (int) previousY - border, 625 (int) previousX + border, (int) previousY + border); 626 627 // union with the end point of the new curve 628 areaToRefresh.union((int) cX - border, (int) cY - border, 629 (int) cX + border, (int) cY + border); 630 631 mX = x; 632 mY = y; 633 634 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 635 636 if (mHandleGestureActions && !mIsGesturing) { 637 mTotalLength += (float) Math.sqrt(dx * dx + dy * dy); 638 639 if (mTotalLength > mGestureStrokeLengthThreshold) { 640 final OrientedBoundingBox box = 641 GestureUtils.computeOrientedBoundingBox(mStrokeBuffer); 642 643 float angle = Math.abs(box.orientation); 644 if (angle > 90) { 645 angle = 180 - angle; 646 } 647 648 if (box.squareness > mGestureStrokeSquarenessTreshold || 649 (mOrientation == ORIENTATION_VERTICAL ? 650 angle < mGestureStrokeAngleThreshold : 651 angle > mGestureStrokeAngleThreshold)) { 652 653 mIsGesturing = true; 654 setCurrentColor(mCertainGestureColor); 655 656 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners; 657 int count = listeners.size(); 658 for (int i = 0; i < count; i++) { 659 listeners.get(i).onGesturingStarted(this); 660 } 661 } 662 } 663 } 664 665 // pass the event to handlers 666 final ArrayList<OnGestureListener> listeners = mOnGestureListeners; 667 final int count = listeners.size(); 668 for (int i = 0; i < count; i++) { 669 listeners.get(i).onGesture(this, event); 670 } 671 } 672 673 return areaToRefresh; 674 } 675 676 private void touchUp(MotionEvent event, boolean cancel) { 677 mIsListeningForGestures = false; 678 679 // A gesture wasn't started or was cancelled 680 if (mCurrentGesture != null) { 681 // add the stroke to the current gesture 682 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer)); 683 684 if (!cancel) { 685 // pass the event to handlers 686 final ArrayList<OnGestureListener> listeners = mOnGestureListeners; 687 int count = listeners.size(); 688 for (int i = 0; i < count; i++) { 689 listeners.get(i).onGestureEnded(this, event); 690 } 691 692 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing, 693 false); 694 } else { 695 cancelGesture(event); 696 697 } 698 } else { 699 cancelGesture(event); 700 } 701 702 mStrokeBuffer.clear(); 703 mPreviousWasGesturing = mIsGesturing; 704 mIsGesturing = false; 705 706 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners; 707 int count = listeners.size(); 708 for (int i = 0; i < count; i++) { 709 listeners.get(i).onGesturingEnded(this); 710 } 711 } 712 713 private void cancelGesture(MotionEvent event) { 714 // pass the event to handlers 715 final ArrayList<OnGestureListener> listeners = mOnGestureListeners; 716 final int count = listeners.size(); 717 for (int i = 0; i < count; i++) { 718 listeners.get(i).onGestureCancelled(this, event); 719 } 720 721 clear(false); 722 } 723 724 private void fireOnGesturePerformed() { 725 final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners; 726 final int count = actionListeners.size(); 727 for (int i = 0; i < count; i++) { 728 actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture); 729 } 730 } 731 732 private class FadeOutRunnable implements Runnable { 733 boolean fireActionPerformed; 734 boolean resetMultipleStrokes; 735 736 public void run() { 737 if (mIsFadingOut) { 738 final long now = AnimationUtils.currentAnimationTimeMillis(); 739 final long duration = now - mFadingStart; 740 741 if (duration > mFadeDuration) { 742 if (fireActionPerformed) { 743 fireOnGesturePerformed(); 744 } 745 746 mPreviousWasGesturing = false; 747 mIsFadingOut = false; 748 mFadingHasStarted = false; 749 mPath.rewind(); 750 mCurrentGesture = null; 751 setPaintAlpha(255); 752 } else { 753 mFadingHasStarted = true; 754 float interpolatedTime = Math.max(0.0f, 755 Math.min(1.0f, duration / (float) mFadeDuration)); 756 mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime); 757 setPaintAlpha((int) (255 * mFadingAlpha)); 758 postDelayed(this, FADE_ANIMATION_RATE); 759 } 760 } else if (resetMultipleStrokes) { 761 mResetGesture = true; 762 } else { 763 fireOnGesturePerformed(); 764 765 mFadingHasStarted = false; 766 mPath.rewind(); 767 mCurrentGesture = null; 768 mPreviousWasGesturing = false; 769 setPaintAlpha(255); 770 } 771 772 invalidate(); 773 } 774 } 775 776 public static interface OnGesturingListener { 777 void onGesturingStarted(GestureOverlayView overlay); 778 779 void onGesturingEnded(GestureOverlayView overlay); 780 } 781 782 public static interface OnGestureListener { 783 void onGestureStarted(GestureOverlayView overlay, MotionEvent event); 784 785 void onGesture(GestureOverlayView overlay, MotionEvent event); 786 787 void onGestureEnded(GestureOverlayView overlay, MotionEvent event); 788 789 void onGestureCancelled(GestureOverlayView overlay, MotionEvent event); 790 } 791 792 public static interface OnGesturePerformedListener { 793 void onGesturePerformed(GestureOverlayView overlay, Gesture gesture); 794 } 795 } 796