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