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