GestureOverlayView.java revision 27a2b50dedd4315b921d502a30ac0fda51fb6a7c
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 void setGesture(Gesture gesture) { 278 if (mCurrentGesture != null) { 279 clear(false); 280 } 281 282 setCurrentColor(mCertainGestureColor); 283 mCurrentGesture = gesture; 284 285 final Path path = mCurrentGesture.toPath(); 286 final RectF bounds = new RectF(); 287 path.computeBounds(bounds, true); 288 289 mPath.rewind(); 290 mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f, 291 -bounds.top + (getHeight() - bounds.height()) / 2.0f); 292 293 mResetGesture = true; 294 295 invalidate(); 296 } 297 298 public void addOnGestureListener(OnGestureListener listener) { 299 mOnGestureListeners.add(listener); 300 } 301 302 public void removeOnGestureListener(OnGestureListener listener) { 303 mOnGestureListeners.remove(listener); 304 } 305 306 public void removeAllOnGestureListeners() { 307 mOnGestureListeners.clear(); 308 } 309 310 public void addOnGesturePerformedListener(OnGesturePerformedListener listener) { 311 mOnGesturePerformedListeners.add(listener); 312 if (mOnGesturePerformedListeners.size() > 0) { 313 mHandleGestureActions = true; 314 } 315 } 316 317 public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) { 318 mOnGesturePerformedListeners.remove(listener); 319 if (mOnGesturePerformedListeners.size() <= 0) { 320 mHandleGestureActions = false; 321 } 322 } 323 324 public void removeAllOnGesturePerformedListeners() { 325 mOnGesturePerformedListeners.clear(); 326 mHandleGestureActions = false; 327 } 328 329 public void addOnGesturingListener(OnGesturingListener listener) { 330 mOnGesturingListeners.add(listener); 331 } 332 333 public void removeOnGesturingListener(OnGesturingListener listener) { 334 mOnGesturingListeners.remove(listener); 335 } 336 337 public void removeAllOnGesturingListeners() { 338 mOnGesturingListeners.clear(); 339 } 340 341 public boolean isGesturing() { 342 return mIsGesturing; 343 } 344 345 private void setCurrentColor(int color) { 346 mCurrentColor = color; 347 if (mFadingHasStarted) { 348 setPaintAlpha((int) (255 * mFadingAlpha)); 349 } else { 350 setPaintAlpha(255); 351 } 352 invalidate(); 353 } 354 355 /** 356 * @hide 357 */ 358 public Paint getGesturePaint() { 359 return mGesturePaint; 360 } 361 362 @Override 363 public void draw(Canvas canvas) { 364 super.draw(canvas); 365 366 if (mCurrentGesture != null) { 367 canvas.drawPath(mPath, mGesturePaint); 368 } 369 } 370 371 private void setPaintAlpha(int alpha) { 372 alpha += alpha >> 7; 373 final int baseAlpha = mCurrentColor >>> 24; 374 final int useAlpha = baseAlpha * alpha >> 8; 375 mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24)); 376 } 377 378 public void clear(boolean animated) { 379 clear(animated, false, true); 380 } 381 382 private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) { 383 setPaintAlpha(255); 384 removeCallbacks(mFadingOut); 385 mResetGesture = false; 386 mFadingOut.fireActionPerformed = fireActionPerformed; 387 mFadingOut.resetMultipleStrokes = false; 388 389 if (animated && mCurrentGesture != null) { 390 mFadingAlpha = 1.0f; 391 mIsFadingOut = true; 392 mFadingHasStarted = false; 393 mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset; 394 395 postDelayed(mFadingOut, mFadeOffset); 396 } else { 397 mFadingAlpha = 1.0f; 398 mIsFadingOut = false; 399 mFadingHasStarted = false; 400 401 if (immediate) { 402 mCurrentGesture = null; 403 mPath.rewind(); 404 invalidate(); 405 } else if (fireActionPerformed) { 406 postDelayed(mFadingOut, mFadeOffset); 407 } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) { 408 mFadingOut.resetMultipleStrokes = true; 409 postDelayed(mFadingOut, mFadeOffset); 410 } else { 411 mCurrentGesture = null; 412 mPath.rewind(); 413 invalidate(); 414 } 415 } 416 } 417 418 public void cancelClearAnimation() { 419 setPaintAlpha(255); 420 mIsFadingOut = false; 421 mFadingHasStarted = false; 422 removeCallbacks(mFadingOut); 423 mPath.rewind(); 424 mCurrentGesture = null; 425 } 426 427 public void cancelGesture() { 428 mIsListeningForGestures = false; 429 430 // add the stroke to the current gesture 431 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer)); 432 433 // pass the event to handlers 434 final long now = SystemClock.uptimeMillis(); 435 final MotionEvent event = MotionEvent.obtain(now, now, 436 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 437 438 final ArrayList<OnGestureListener> listeners = mOnGestureListeners; 439 int count = listeners.size(); 440 for (int i = 0; i < count; i++) { 441 listeners.get(i).onGestureCancelled(this, event); 442 } 443 444 event.recycle(); 445 446 clear(false); 447 mIsGesturing = false; 448 mPreviousWasGesturing = false; 449 mStrokeBuffer.clear(); 450 451 final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners; 452 count = otherListeners.size(); 453 for (int i = 0; i < count; i++) { 454 otherListeners.get(i).onGesturingEnded(this); 455 } 456 } 457 458 @Override 459 protected void onDetachedFromWindow() { 460 cancelClearAnimation(); 461 } 462 463 @Override 464 public boolean dispatchTouchEvent(MotionEvent event) { 465 if (isEnabled()) { 466 final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null && 467 mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) && 468 mInterceptEvents; 469 470 processEvent(event); 471 472 if (cancelDispatch) { 473 event.setAction(MotionEvent.ACTION_CANCEL); 474 } 475 476 super.dispatchTouchEvent(event); 477 478 return true; 479 } 480 481 return super.dispatchTouchEvent(event); 482 } 483 484 private boolean processEvent(MotionEvent event) { 485 switch (event.getAction()) { 486 case MotionEvent.ACTION_DOWN: 487 touchDown(event); 488 invalidate(); 489 return true; 490 case MotionEvent.ACTION_MOVE: 491 if (mIsListeningForGestures) { 492 Rect rect = touchMove(event); 493 if (rect != null) { 494 invalidate(rect); 495 } 496 return true; 497 } 498 break; 499 case MotionEvent.ACTION_UP: 500 if (mIsListeningForGestures) { 501 touchUp(event, false); 502 invalidate(); 503 return true; 504 } 505 break; 506 case MotionEvent.ACTION_CANCEL: 507 if (mIsListeningForGestures) { 508 touchUp(event, true); 509 invalidate(); 510 return true; 511 } 512 } 513 514 return false; 515 } 516 517 private void touchDown(MotionEvent event) { 518 mIsListeningForGestures = true; 519 520 float x = event.getX(); 521 float y = event.getY(); 522 523 mX = x; 524 mY = y; 525 526 mTotalLength = 0; 527 mIsGesturing = false; 528 529 if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) { 530 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor); 531 mResetGesture = false; 532 mCurrentGesture = null; 533 mPath.rewind(); 534 } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) { 535 if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor); 536 } 537 538 // if there is fading out going on, stop it. 539 if (mFadingHasStarted) { 540 cancelClearAnimation(); 541 } else if (mIsFadingOut) { 542 setPaintAlpha(255); 543 mIsFadingOut = false; 544 mFadingHasStarted = false; 545 removeCallbacks(mFadingOut); 546 } 547 548 if (mCurrentGesture == null) { 549 mCurrentGesture = new Gesture(); 550 } 551 552 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 553 mPath.moveTo(x, y); 554 555 final int border = mInvalidateExtraBorder; 556 mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border); 557 558 mCurveEndX = x; 559 mCurveEndY = y; 560 561 // pass the event to handlers 562 final ArrayList<OnGestureListener> listeners = mOnGestureListeners; 563 final int count = listeners.size(); 564 for (int i = 0; i < count; i++) { 565 listeners.get(i).onGestureStarted(this, event); 566 } 567 } 568 569 private Rect touchMove(MotionEvent event) { 570 Rect areaToRefresh = null; 571 572 final float x = event.getX(); 573 final float y = event.getY(); 574 575 final float previousX = mX; 576 final float previousY = mY; 577 578 final float dx = Math.abs(x - previousX); 579 final float dy = Math.abs(y - previousY); 580 581 if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) { 582 areaToRefresh = mInvalidRect; 583 584 // start with the curve end 585 final int border = mInvalidateExtraBorder; 586 areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border, 587 (int) mCurveEndX + border, (int) mCurveEndY + border); 588 589 float cX = mCurveEndX = (x + previousX) / 2; 590 float cY = mCurveEndY = (y + previousY) / 2; 591 592 mPath.quadTo(previousX, previousY, cX, cY); 593 594 // union with the control point of the new curve 595 areaToRefresh.union((int) previousX - border, (int) previousY - border, 596 (int) previousX + border, (int) previousY + border); 597 598 // union with the end point of the new curve 599 areaToRefresh.union((int) cX - border, (int) cY - border, 600 (int) cX + border, (int) cY + border); 601 602 mX = x; 603 mY = y; 604 605 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 606 607 if (mHandleGestureActions && !mIsGesturing) { 608 mTotalLength += (float) Math.sqrt(dx * dx + dy * dy); 609 610 if (mTotalLength > mGestureStrokeLengthThreshold) { 611 final OrientedBoundingBox box = 612 GestureUtilities.computeOrientedBoundingBox(mStrokeBuffer); 613 614 float angle = Math.abs(box.orientation); 615 if (angle > 90) { 616 angle = 180 - angle; 617 } 618 619 if (box.squareness > mGestureStrokeSquarenessTreshold || 620 (mOrientation == ORIENTATION_VERTICAL ? 621 angle < mGestureStrokeAngleThreshold : 622 angle > mGestureStrokeAngleThreshold)) { 623 624 mIsGesturing = true; 625 setCurrentColor(mCertainGestureColor); 626 627 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners; 628 int count = listeners.size(); 629 for (int i = 0; i < count; i++) { 630 listeners.get(i).onGesturingStarted(this); 631 } 632 } 633 } 634 } 635 636 // pass the event to handlers 637 final ArrayList<OnGestureListener> listeners = mOnGestureListeners; 638 final int count = listeners.size(); 639 for (int i = 0; i < count; i++) { 640 listeners.get(i).onGesture(this, event); 641 } 642 } 643 644 return areaToRefresh; 645 } 646 647 private void touchUp(MotionEvent event, boolean cancel) { 648 mIsListeningForGestures = false; 649 650 // A gesture wasn't started or was cancelled 651 if (mCurrentGesture != null) { 652 // add the stroke to the current gesture 653 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer)); 654 655 if (!cancel) { 656 // pass the event to handlers 657 final ArrayList<OnGestureListener> listeners = mOnGestureListeners; 658 int count = listeners.size(); 659 for (int i = 0; i < count; i++) { 660 listeners.get(i).onGestureEnded(this, event); 661 } 662 663 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing, 664 false); 665 } else { 666 cancelGesture(event); 667 668 } 669 } else { 670 cancelGesture(event); 671 } 672 673 mStrokeBuffer.clear(); 674 mPreviousWasGesturing = mIsGesturing; 675 mIsGesturing = false; 676 677 final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners; 678 int count = listeners.size(); 679 for (int i = 0; i < count; i++) { 680 listeners.get(i).onGesturingEnded(this); 681 } 682 } 683 684 private void cancelGesture(MotionEvent event) { 685 // pass the event to handlers 686 final ArrayList<OnGestureListener> listeners = mOnGestureListeners; 687 final int count = listeners.size(); 688 for (int i = 0; i < count; i++) { 689 listeners.get(i).onGestureCancelled(this, event); 690 } 691 692 clear(false); 693 } 694 695 private void fireOnGesturePerformed() { 696 final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners; 697 final int count = actionListeners.size(); 698 for (int i = 0; i < count; i++) { 699 actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture); 700 } 701 } 702 703 private class FadeOutRunnable implements Runnable { 704 boolean fireActionPerformed; 705 boolean resetMultipleStrokes; 706 707 public void run() { 708 if (mIsFadingOut) { 709 final long now = AnimationUtils.currentAnimationTimeMillis(); 710 final long duration = now - mFadingStart; 711 712 if (duration > mFadeDuration) { 713 if (fireActionPerformed) { 714 fireOnGesturePerformed(); 715 } 716 717 mPreviousWasGesturing = false; 718 mIsFadingOut = false; 719 mFadingHasStarted = false; 720 mPath.rewind(); 721 mCurrentGesture = null; 722 setPaintAlpha(255); 723 } else { 724 mFadingHasStarted = true; 725 float interpolatedTime = Math.max(0.0f, 726 Math.min(1.0f, duration / (float) mFadeDuration)); 727 mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime); 728 setPaintAlpha((int) (255 * mFadingAlpha)); 729 postDelayed(this, FADE_ANIMATION_RATE); 730 } 731 } else if (resetMultipleStrokes) { 732 mResetGesture = true; 733 } else { 734 fireOnGesturePerformed(); 735 736 mFadingHasStarted = false; 737 mPath.rewind(); 738 mCurrentGesture = null; 739 mPreviousWasGesturing = false; 740 setPaintAlpha(255); 741 } 742 743 invalidate(); 744 } 745 } 746 747 public static interface OnGesturingListener { 748 void onGesturingStarted(GestureOverlayView overlay); 749 750 void onGesturingEnded(GestureOverlayView overlay); 751 } 752 753 public static interface OnGestureListener { 754 void onGestureStarted(GestureOverlayView overlay, MotionEvent event); 755 756 void onGesture(GestureOverlayView overlay, MotionEvent event); 757 758 void onGestureEnded(GestureOverlayView overlay, MotionEvent event); 759 760 void onGestureCancelled(GestureOverlayView overlay, MotionEvent event); 761 } 762 763 public static interface OnGesturePerformedListener { 764 void onGesturePerformed(GestureOverlayView overlay, Gesture gesture); 765 } 766} 767