1 /* 2 * Copyright (C) 2011 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 com.android.systemui; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.ValueAnimator; 23 import android.animation.ValueAnimator.AnimatorUpdateListener; 24 import android.content.Context; 25 import android.graphics.RectF; 26 import android.os.Handler; 27 import android.util.Log; 28 import android.view.MotionEvent; 29 import android.view.VelocityTracker; 30 import android.view.View; 31 import android.view.ViewConfiguration; 32 import android.view.accessibility.AccessibilityEvent; 33 34 import com.android.systemui.classifier.FalsingManager; 35 import com.android.systemui.statusbar.FlingAnimationUtils; 36 37 import java.util.HashMap; 38 39 public class SwipeHelper implements Gefingerpoken { 40 static final String TAG = "com.android.systemui.SwipeHelper"; 41 private static final boolean DEBUG = false; 42 private static final boolean DEBUG_INVALIDATE = false; 43 private static final boolean SLOW_ANIMATIONS = false; // DEBUG; 44 private static final boolean CONSTRAIN_SWIPE = true; 45 private static final boolean FADE_OUT_DURING_SWIPE = true; 46 private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true; 47 48 public static final int X = 0; 49 public static final int Y = 1; 50 51 private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec 52 private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms 53 private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms 54 private int MAX_DISMISS_VELOCITY = 4000; // dp/sec 55 private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms 56 57 static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width 58 // beyond which swipe progress->0 59 private float mMinSwipeProgress = 0f; 60 private float mMaxSwipeProgress = 1f; 61 62 private FlingAnimationUtils mFlingAnimationUtils; 63 private float mPagingTouchSlop; 64 private Callback mCallback; 65 private Handler mHandler; 66 private int mSwipeDirection; 67 private VelocityTracker mVelocityTracker; 68 private FalsingManager mFalsingManager; 69 70 private float mInitialTouchPos; 71 private float mPerpendicularInitialTouchPos; 72 private boolean mDragging; 73 private boolean mSnappingChild; 74 private View mCurrView; 75 private boolean mCanCurrViewBeDimissed; 76 private float mDensityScale; 77 private float mTranslation = 0; 78 79 private boolean mLongPressSent; 80 private LongPressListener mLongPressListener; 81 private Runnable mWatchLongPress; 82 private long mLongPressTimeout; 83 84 final private int[] mTmpPos = new int[2]; 85 private int mFalsingThreshold; 86 private boolean mTouchAboveFalsingThreshold; 87 private boolean mDisableHwLayers; 88 89 private HashMap<View, Animator> mDismissPendingMap = new HashMap<>(); 90 91 public SwipeHelper(int swipeDirection, Callback callback, Context context) { 92 mCallback = callback; 93 mHandler = new Handler(); 94 mSwipeDirection = swipeDirection; 95 mVelocityTracker = VelocityTracker.obtain(); 96 mDensityScale = context.getResources().getDisplayMetrics().density; 97 mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop(); 98 99 mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); // extra long-press! 100 mFalsingThreshold = context.getResources().getDimensionPixelSize( 101 R.dimen.swipe_helper_falsing_threshold); 102 mFalsingManager = FalsingManager.getInstance(context); 103 mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f); 104 } 105 106 public void setLongPressListener(LongPressListener listener) { 107 mLongPressListener = listener; 108 } 109 110 public void setDensityScale(float densityScale) { 111 mDensityScale = densityScale; 112 } 113 114 public void setPagingTouchSlop(float pagingTouchSlop) { 115 mPagingTouchSlop = pagingTouchSlop; 116 } 117 118 public void setDisableHardwareLayers(boolean disableHwLayers) { 119 mDisableHwLayers = disableHwLayers; 120 } 121 122 private float getPos(MotionEvent ev) { 123 return mSwipeDirection == X ? ev.getX() : ev.getY(); 124 } 125 126 private float getPerpendicularPos(MotionEvent ev) { 127 return mSwipeDirection == X ? ev.getY() : ev.getX(); 128 } 129 130 protected float getTranslation(View v) { 131 return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY(); 132 } 133 134 private float getVelocity(VelocityTracker vt) { 135 return mSwipeDirection == X ? vt.getXVelocity() : 136 vt.getYVelocity(); 137 } 138 139 protected ObjectAnimator createTranslationAnimation(View v, float newPos) { 140 ObjectAnimator anim = ObjectAnimator.ofFloat(v, 141 mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos); 142 return anim; 143 } 144 145 private float getPerpendicularVelocity(VelocityTracker vt) { 146 return mSwipeDirection == X ? vt.getYVelocity() : 147 vt.getXVelocity(); 148 } 149 150 protected Animator getViewTranslationAnimator(View v, float target, 151 AnimatorUpdateListener listener) { 152 ObjectAnimator anim = createTranslationAnimation(v, target); 153 if (listener != null) { 154 anim.addUpdateListener(listener); 155 } 156 return anim; 157 } 158 159 protected void setTranslation(View v, float translate) { 160 if (v == null) { 161 return; 162 } 163 if (mSwipeDirection == X) { 164 v.setTranslationX(translate); 165 } else { 166 v.setTranslationY(translate); 167 } 168 } 169 170 protected float getSize(View v) { 171 return mSwipeDirection == X ? v.getMeasuredWidth() : 172 v.getMeasuredHeight(); 173 } 174 175 public void setMinSwipeProgress(float minSwipeProgress) { 176 mMinSwipeProgress = minSwipeProgress; 177 } 178 179 public void setMaxSwipeProgress(float maxSwipeProgress) { 180 mMaxSwipeProgress = maxSwipeProgress; 181 } 182 183 private float getSwipeProgressForOffset(View view, float translation) { 184 float viewSize = getSize(view); 185 float result = Math.abs(translation / viewSize); 186 return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress); 187 } 188 189 private float getSwipeAlpha(float progress) { 190 return Math.min(0, Math.max(1, progress / SWIPE_PROGRESS_FADE_END)); 191 } 192 193 private void updateSwipeProgressFromOffset(View animView, boolean dismissable) { 194 updateSwipeProgressFromOffset(animView, dismissable, getTranslation(animView)); 195 } 196 197 private void updateSwipeProgressFromOffset(View animView, boolean dismissable, 198 float translation) { 199 float swipeProgress = getSwipeProgressForOffset(animView, translation); 200 if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) { 201 if (FADE_OUT_DURING_SWIPE && dismissable) { 202 float alpha = swipeProgress; 203 if (!mDisableHwLayers) { 204 if (alpha != 0f && alpha != 1f) { 205 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 206 } else { 207 animView.setLayerType(View.LAYER_TYPE_NONE, null); 208 } 209 } 210 animView.setAlpha(getSwipeAlpha(swipeProgress)); 211 } 212 } 213 invalidateGlobalRegion(animView); 214 } 215 216 // invalidate the view's own bounds all the way up the view hierarchy 217 public static void invalidateGlobalRegion(View view) { 218 invalidateGlobalRegion( 219 view, 220 new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); 221 } 222 223 // invalidate a rectangle relative to the view's coordinate system all the way up the view 224 // hierarchy 225 public static void invalidateGlobalRegion(View view, RectF childBounds) { 226 //childBounds.offset(view.getTranslationX(), view.getTranslationY()); 227 if (DEBUG_INVALIDATE) 228 Log.v(TAG, "-------------"); 229 while (view.getParent() != null && view.getParent() instanceof View) { 230 view = (View) view.getParent(); 231 view.getMatrix().mapRect(childBounds); 232 view.invalidate((int) Math.floor(childBounds.left), 233 (int) Math.floor(childBounds.top), 234 (int) Math.ceil(childBounds.right), 235 (int) Math.ceil(childBounds.bottom)); 236 if (DEBUG_INVALIDATE) { 237 Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left) 238 + "," + (int) Math.floor(childBounds.top) 239 + "," + (int) Math.ceil(childBounds.right) 240 + "," + (int) Math.ceil(childBounds.bottom)); 241 } 242 } 243 } 244 245 public void removeLongPressCallback() { 246 if (mWatchLongPress != null) { 247 mHandler.removeCallbacks(mWatchLongPress); 248 mWatchLongPress = null; 249 } 250 } 251 252 public boolean onInterceptTouchEvent(final MotionEvent ev) { 253 final int action = ev.getAction(); 254 255 switch (action) { 256 case MotionEvent.ACTION_DOWN: 257 mTouchAboveFalsingThreshold = false; 258 mDragging = false; 259 mSnappingChild = false; 260 mLongPressSent = false; 261 mVelocityTracker.clear(); 262 mCurrView = mCallback.getChildAtPosition(ev); 263 264 if (mCurrView != null) { 265 onDownUpdate(mCurrView); 266 mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView); 267 mVelocityTracker.addMovement(ev); 268 mInitialTouchPos = getPos(ev); 269 mPerpendicularInitialTouchPos = getPerpendicularPos(ev); 270 mTranslation = getTranslation(mCurrView); 271 if (mLongPressListener != null) { 272 if (mWatchLongPress == null) { 273 mWatchLongPress = new Runnable() { 274 @Override 275 public void run() { 276 if (mCurrView != null && !mLongPressSent) { 277 mLongPressSent = true; 278 mCurrView.sendAccessibilityEvent( 279 AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 280 mCurrView.getLocationOnScreen(mTmpPos); 281 final int x = (int) ev.getRawX() - mTmpPos[0]; 282 final int y = (int) ev.getRawY() - mTmpPos[1]; 283 mLongPressListener.onLongPress(mCurrView, x, y); 284 } 285 } 286 }; 287 } 288 mHandler.postDelayed(mWatchLongPress, mLongPressTimeout); 289 } 290 } 291 break; 292 293 case MotionEvent.ACTION_MOVE: 294 if (mCurrView != null && !mLongPressSent) { 295 mVelocityTracker.addMovement(ev); 296 float pos = getPos(ev); 297 float perpendicularPos = getPerpendicularPos(ev); 298 float delta = pos - mInitialTouchPos; 299 float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos; 300 if (Math.abs(delta) > mPagingTouchSlop 301 && Math.abs(delta) > Math.abs(deltaPerpendicular)) { 302 mCallback.onBeginDrag(mCurrView); 303 mDragging = true; 304 mInitialTouchPos = getPos(ev); 305 mTranslation = getTranslation(mCurrView); 306 removeLongPressCallback(); 307 } 308 } 309 break; 310 311 case MotionEvent.ACTION_UP: 312 case MotionEvent.ACTION_CANCEL: 313 final boolean captured = (mDragging || mLongPressSent); 314 mDragging = false; 315 mCurrView = null; 316 mLongPressSent = false; 317 removeLongPressCallback(); 318 if (captured) return true; 319 break; 320 } 321 return mDragging || mLongPressSent; 322 } 323 324 /** 325 * @param view The view to be dismissed 326 * @param velocity The desired pixels/second speed at which the view should move 327 * @param useAccelerateInterpolator Should an accelerating Interpolator be used 328 */ 329 public void dismissChild(final View view, float velocity, boolean useAccelerateInterpolator) { 330 dismissChild(view, velocity, null /* endAction */, 0 /* delay */, 331 useAccelerateInterpolator, 0 /* fixedDuration */, false /* isDismissAll */); 332 } 333 334 /** 335 * @param view The view to be dismissed 336 * @param velocity The desired pixels/second speed at which the view should move 337 * @param endAction The action to perform at the end 338 * @param delay The delay after which we should start 339 * @param useAccelerateInterpolator Should an accelerating Interpolator be used 340 * @param fixedDuration If not 0, this exact duration will be taken 341 */ 342 public void dismissChild(final View animView, float velocity, final Runnable endAction, 343 long delay, boolean useAccelerateInterpolator, long fixedDuration, 344 boolean isDismissAll) { 345 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView); 346 float newPos; 347 boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 348 349 // if we use the Menu to dismiss an item in landscape, animate up 350 boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll) 351 && mSwipeDirection == Y; 352 // if the language is rtl we prefer swiping to the left 353 boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll) 354 && isLayoutRtl; 355 boolean animateLeft = velocity < 0 356 || (velocity == 0 && getTranslation(animView) < 0 && !isDismissAll); 357 358 if (animateLeft || animateLeftForRtl || animateUpForMenu) { 359 newPos = -getSize(animView); 360 } else { 361 newPos = getSize(animView); 362 } 363 long duration; 364 if (fixedDuration == 0) { 365 duration = MAX_ESCAPE_ANIMATION_DURATION; 366 if (velocity != 0) { 367 duration = Math.min(duration, 368 (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math 369 .abs(velocity)) 370 ); 371 } else { 372 duration = DEFAULT_ESCAPE_ANIMATION_DURATION; 373 } 374 } else { 375 duration = fixedDuration; 376 } 377 378 if (!mDisableHwLayers) { 379 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 380 } 381 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { 382 public void onAnimationUpdate(ValueAnimator animation) { 383 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed); 384 } 385 }; 386 387 Animator anim = getViewTranslationAnimator(animView, newPos, updateListener); 388 if (anim == null) { 389 return; 390 } 391 if (useAccelerateInterpolator) { 392 anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); 393 anim.setDuration(duration); 394 } else { 395 mFlingAnimationUtils.applyDismissing(anim, getTranslation(animView), 396 newPos, velocity, getSize(animView)); 397 } 398 if (delay > 0) { 399 anim.setStartDelay(delay); 400 } 401 anim.addListener(new AnimatorListenerAdapter() { 402 private boolean mCancelled; 403 404 public void onAnimationCancel(Animator animation) { 405 mCancelled = true; 406 } 407 408 public void onAnimationEnd(Animator animation) { 409 updateSwipeProgressFromOffset(animView, canBeDismissed); 410 mDismissPendingMap.remove(animView); 411 if (!mCancelled) { 412 mCallback.onChildDismissed(animView); 413 } 414 if (endAction != null) { 415 endAction.run(); 416 } 417 if (!mDisableHwLayers) { 418 animView.setLayerType(View.LAYER_TYPE_NONE, null); 419 } 420 } 421 }); 422 423 prepareDismissAnimation(animView, anim); 424 mDismissPendingMap.put(animView, anim); 425 anim.start(); 426 } 427 428 /** 429 * Called to update the dismiss animation. 430 */ 431 protected void prepareDismissAnimation(View view, Animator anim) { 432 // Do nothing 433 } 434 435 public void snapChild(final View animView, final float targetLeft, float velocity) { 436 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView); 437 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { 438 public void onAnimationUpdate(ValueAnimator animation) { 439 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed); 440 } 441 }; 442 443 Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener); 444 if (anim == null) { 445 return; 446 } 447 int duration = SNAP_ANIM_LEN; 448 anim.setDuration(duration); 449 anim.addListener(new AnimatorListenerAdapter() { 450 public void onAnimationEnd(Animator animator) { 451 mSnappingChild = false; 452 updateSwipeProgressFromOffset(animView, canBeDismissed); 453 mCallback.onChildSnappedBack(animView, targetLeft); 454 } 455 }); 456 prepareSnapBackAnimation(animView, anim); 457 mSnappingChild = true; 458 anim.start(); 459 } 460 461 /** 462 * Called to update the snap back animation. 463 */ 464 protected void prepareSnapBackAnimation(View view, Animator anim) { 465 // Do nothing 466 } 467 468 /** 469 * Called when there's a down event. 470 */ 471 public void onDownUpdate(View currView) { 472 // Do nothing 473 } 474 475 /** 476 * Called on a move event. 477 */ 478 protected void onMoveUpdate(View view, float totalTranslation, float delta) { 479 // Do nothing 480 } 481 482 /** 483 * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current 484 * view is being animated to dismiss or snap. 485 */ 486 public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) { 487 updateSwipeProgressFromOffset(animView, canBeDismissed, value); 488 } 489 490 private void snapChildInstantly(final View view) { 491 final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view); 492 setTranslation(view, 0); 493 updateSwipeProgressFromOffset(view, canAnimViewBeDismissed); 494 } 495 496 /** 497 * Called when a view is updated to be non-dismissable, if the view was being dismissed before 498 * the update this will handle snapping it back into place. 499 * 500 * @param view the view to snap if necessary. 501 * @param animate whether to animate the snap or not. 502 * @param targetLeft the target to snap to. 503 */ 504 public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) { 505 if ((mDragging && mCurrView == view) || mSnappingChild) { 506 return; 507 } 508 boolean needToSnap = false; 509 Animator dismissPendingAnim = mDismissPendingMap.get(view); 510 if (dismissPendingAnim != null) { 511 needToSnap = true; 512 dismissPendingAnim.cancel(); 513 } else if (getTranslation(view) != 0) { 514 needToSnap = true; 515 } 516 if (needToSnap) { 517 if (animate) { 518 snapChild(view, targetLeft, 0.0f /* velocity */); 519 } else { 520 snapChildInstantly(view); 521 } 522 } 523 } 524 525 public boolean onTouchEvent(MotionEvent ev) { 526 if (mLongPressSent) { 527 return true; 528 } 529 530 if (!mDragging) { 531 if (mCallback.getChildAtPosition(ev) != null) { 532 533 // We are dragging directly over a card, make sure that we also catch the gesture 534 // even if nobody else wants the touch event. 535 onInterceptTouchEvent(ev); 536 return true; 537 } else { 538 539 // We are not doing anything, make sure the long press callback 540 // is not still ticking like a bomb waiting to go off. 541 removeLongPressCallback(); 542 return false; 543 } 544 } 545 546 mVelocityTracker.addMovement(ev); 547 final int action = ev.getAction(); 548 switch (action) { 549 case MotionEvent.ACTION_OUTSIDE: 550 case MotionEvent.ACTION_MOVE: 551 if (mCurrView != null) { 552 float delta = getPos(ev) - mInitialTouchPos; 553 float absDelta = Math.abs(delta); 554 if (absDelta >= getFalsingThreshold()) { 555 mTouchAboveFalsingThreshold = true; 556 } 557 // don't let items that can't be dismissed be dragged more than 558 // maxScrollDistance 559 if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) { 560 float size = getSize(mCurrView); 561 float maxScrollDistance = 0.25f * size; 562 if (absDelta >= size) { 563 delta = delta > 0 ? maxScrollDistance : -maxScrollDistance; 564 } else { 565 delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2)); 566 } 567 } 568 569 setTranslation(mCurrView, mTranslation + delta); 570 updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed); 571 onMoveUpdate(mCurrView, mTranslation + delta, delta); 572 } 573 break; 574 case MotionEvent.ACTION_UP: 575 case MotionEvent.ACTION_CANCEL: 576 if (mCurrView == null) { 577 break; 578 } 579 mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity()); 580 float velocity = getVelocity(mVelocityTracker); 581 582 if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) { 583 if (isDismissGesture(ev)) { 584 // flingadingy 585 dismissChild(mCurrView, velocity, 586 !swipedFastEnough() /* useAccelerateInterpolator */); 587 } else { 588 // snappity 589 mCallback.onDragCancelled(mCurrView); 590 snapChild(mCurrView, 0 /* leftTarget */, velocity); 591 } 592 mCurrView = null; 593 } 594 mDragging = false; 595 break; 596 } 597 return true; 598 } 599 600 private int getFalsingThreshold() { 601 float factor = mCallback.getFalsingThresholdFactor(); 602 return (int) (mFalsingThreshold * factor); 603 } 604 605 private float getMaxVelocity() { 606 return MAX_DISMISS_VELOCITY * mDensityScale; 607 } 608 609 protected float getEscapeVelocity() { 610 return getUnscaledEscapeVelocity() * mDensityScale; 611 } 612 613 protected float getUnscaledEscapeVelocity() { 614 return SWIPE_ESCAPE_VELOCITY; 615 } 616 617 protected long getMaxEscapeAnimDuration() { 618 return MAX_ESCAPE_ANIMATION_DURATION; 619 } 620 621 protected boolean swipedFarEnough() { 622 float translation = getTranslation(mCurrView); 623 return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView); 624 } 625 626 protected boolean isDismissGesture(MotionEvent ev) { 627 boolean falsingDetected = mCallback.isAntiFalsingNeeded(); 628 if (mFalsingManager.isClassiferEnabled()) { 629 falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(); 630 } else { 631 falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold; 632 } 633 return !falsingDetected && (swipedFastEnough() || swipedFarEnough()) 634 && ev.getActionMasked() == MotionEvent.ACTION_UP 635 && mCallback.canChildBeDismissed(mCurrView); 636 } 637 638 protected boolean swipedFastEnough() { 639 float velocity = getVelocity(mVelocityTracker); 640 float translation = getTranslation(mCurrView); 641 boolean ret = (Math.abs(velocity) > getEscapeVelocity()) 642 && (velocity > 0) == (translation > 0); 643 return ret; 644 } 645 646 protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity, 647 float translation) { 648 return false; 649 } 650 651 public interface Callback { 652 View getChildAtPosition(MotionEvent ev); 653 654 boolean canChildBeDismissed(View v); 655 656 boolean isAntiFalsingNeeded(); 657 658 void onBeginDrag(View v); 659 660 void onChildDismissed(View v); 661 662 void onDragCancelled(View v); 663 664 /** 665 * Called when the child is snapped to a position. 666 * 667 * @param animView the view that was snapped. 668 * @param targetLeft the left position the view was snapped to. 669 */ 670 void onChildSnappedBack(View animView, float targetLeft); 671 672 /** 673 * Updates the swipe progress on a child. 674 * 675 * @return if true, prevents the default alpha fading. 676 */ 677 boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress); 678 679 /** 680 * @return The factor the falsing threshold should be multiplied with 681 */ 682 float getFalsingThresholdFactor(); 683 } 684 685 /** 686 * Equivalent to View.OnLongClickListener with coordinates 687 */ 688 public interface LongPressListener { 689 /** 690 * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates 691 * @return whether the longpress was handled 692 */ 693 boolean onLongPress(View v, int x, int y); 694 } 695 } 696