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