SwipeHelper.java revision ff8553f20964f4c31b0c503a9e1daff6ae08a9c7
17fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao/* 27fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * Copyright (C) 2012 Google Inc. 37fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * Licensed to The Android Open Source Project. 47fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * 57fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * Licensed under the Apache License, Version 2.0 (the "License"); 67fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * you may not use this file except in compliance with the License. 77fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * You may obtain a copy of the License at 87fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * 97fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * http://www.apache.org/licenses/LICENSE-2.0 107fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * 117fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * Unless required by applicable law or agreed to in writing, software 127fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * distributed under the License is distributed on an "AS IS" BASIS, 137fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 147fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * See the License for the specific language governing permissions and 157fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao * limitations under the License. 167fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao */ 177fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao 187fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaopackage com.android.mail.ui; 197fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao 207fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaoimport android.animation.Animator; 217fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaoimport android.animation.AnimatorListenerAdapter; 227fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaoimport android.animation.AnimatorSet; 237fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaoimport android.animation.ObjectAnimator; 246702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport android.animation.ValueAnimator; 256702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport android.animation.ValueAnimator.AnimatorUpdateListener; 266702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport android.content.Context; 276702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport android.content.res.Resources; 286702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport android.graphics.RectF; 297fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaoimport android.util.Log; 306702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport android.view.MotionEvent; 316702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport android.view.VelocityTracker; 327fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaoimport android.view.View; 336702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport android.view.animation.DecelerateInterpolator; 346702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier 356702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport com.android.mail.R; 366702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport com.android.mail.browse.ConversationItemView; 376702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartierimport com.android.mail.utils.Utils; 387fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao 397fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaoimport java.util.ArrayList; 407fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaoimport java.util.Collection; 4166f19258f9728d4ffe026074d8fd429d639802faMathieu Chartier 427fbee0731b14b5bf392a4254f5cd84685ab517dajeffhaopublic class SwipeHelper { 437fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao static final String TAG = "com.android.systemui.SwipeHelper"; 447fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static final boolean DEBUG_INVALIDATE = false; 457fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static final boolean CONSTRAIN_SWIPE = true; 467fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static final boolean FADE_OUT_DURING_SWIPE = true; 477fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true; 486702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier // Turn on for debugging only during development. 497fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static final boolean LOG_SWIPE_DISMISS_VELOCITY = false; 506702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier 517fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao public static final int X = 0; 526702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier public static final int Y = 1; 537fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao 547fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static DecelerateInterpolator sDecelerateInterpolator = 557fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao new DecelerateInterpolator(1.0f); 567fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao 577fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static int SWIPE_ESCAPE_VELOCITY = -1; 586702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private static int DEFAULT_ESCAPE_ANIMATION_DURATION; 597fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static int MAX_ESCAPE_ANIMATION_DURATION; 606702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private static int MAX_DISMISS_VELOCITY; 617fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static int SNAP_ANIM_LEN; 626702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private static int DISMISS_ANIMATION_DURATION; 637fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static float MIN_SWIPE; 647fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static float MIN_VERT; 657fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private static float MIN_LOCK; 667fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao 677fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao public static float ALPHA_FADE_START = 0f; // fraction of thumbnail width 687fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao // where fade starts 696702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier public static float ALPHA_TEXT_FADE_START = 0.4f; 706702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier static final float ALPHA_FADE_END = 0.7f; // fraction of thumbnail width 716702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier // beyond which alpha->0 726702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private static final float FACTOR = 1.2f; 736702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private float mMinAlpha = 0.5f; 746702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier 756702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier /* Dead region where swipe cannot be initiated. */ 767fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private final static int DEAD_REGION_FOR_SWIPE = 64; 776702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier 786702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private float mPagingTouchSlop; 796702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private Callback mCallback; 806702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private int mSwipeDirection; 816702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private VelocityTracker mVelocityTracker; 826702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier 837fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private float mInitialTouchPosX; 847fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private boolean mDragging; 857fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private SwipeableItemView mCurrView; 867fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private View mCurrAnimView; 877fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao private boolean mCanCurrViewBeDimissed; 886702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private float mDensityScale; 896702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private float mLastY; 906702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private float mInitialTouchPosY; 916702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier private LeaveBehindItem mPrevView; 926702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier 936702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier public SwipeHelper(Context context, int swipeDirection, Callback callback, float densityScale, 946702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier float pagingTouchSlop) { 956702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier mCallback = callback; 966702243ea2332b566d8e8b871cc9db0906d835adMathieu Chartier mSwipeDirection = swipeDirection; 977fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao mVelocityTracker = VelocityTracker.obtain(); 987fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao mDensityScale = densityScale; 997fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao mPagingTouchSlop = pagingTouchSlop; 1007fbee0731b14b5bf392a4254f5cd84685ab517dajeffhao if (SWIPE_ESCAPE_VELOCITY == -1) { 101 Resources res = context.getResources(); 102 SWIPE_ESCAPE_VELOCITY = res.getInteger(R.integer.swipe_escape_velocity); 103 DEFAULT_ESCAPE_ANIMATION_DURATION = res.getInteger(R.integer.escape_animation_duration); 104 MAX_ESCAPE_ANIMATION_DURATION = res.getInteger(R.integer.max_escape_animation_duration); 105 MAX_DISMISS_VELOCITY = res.getInteger(R.integer.max_dismiss_velocity); 106 SNAP_ANIM_LEN = res.getInteger(R.integer.snap_animation_duration); 107 DISMISS_ANIMATION_DURATION = res.getInteger(R.integer.dismiss_animation_duration); 108 MIN_SWIPE = res.getDimension(R.dimen.min_swipe); 109 MIN_VERT = res.getDimension(R.dimen.min_vert); 110 MIN_LOCK = res.getDimension(R.dimen.min_lock); 111 } 112 } 113 114 public void setDensityScale(float densityScale) { 115 mDensityScale = densityScale; 116 } 117 118 public void setPagingTouchSlop(float pagingTouchSlop) { 119 mPagingTouchSlop = pagingTouchSlop; 120 } 121 122 private float getVelocity(VelocityTracker vt) { 123 return mSwipeDirection == X ? vt.getXVelocity() : 124 vt.getYVelocity(); 125 } 126 127 private ObjectAnimator createTranslationAnimation(View v, float newPos) { 128 ObjectAnimator anim = ObjectAnimator.ofFloat(v, 129 mSwipeDirection == X ? "translationX" : "translationY", newPos); 130 return anim; 131 } 132 133 private ObjectAnimator createDismissAnimation(View v, float newPos, int duration) { 134 ObjectAnimator anim = createTranslationAnimation(v, newPos); 135 anim.setInterpolator(sDecelerateInterpolator); 136 anim.setDuration(duration); 137 return anim; 138 } 139 140 private float getPerpendicularVelocity(VelocityTracker vt) { 141 return mSwipeDirection == X ? vt.getYVelocity() : 142 vt.getXVelocity(); 143 } 144 145 private void setTranslation(View v, float translate) { 146 if (mSwipeDirection == X) { 147 v.setTranslationX(translate); 148 } else { 149 v.setTranslationY(translate); 150 } 151 } 152 153 private float getSize(View v) { 154 return mSwipeDirection == X ? v.getMeasuredWidth() : 155 v.getMeasuredHeight(); 156 } 157 158 public void setMinAlpha(float minAlpha) { 159 mMinAlpha = minAlpha; 160 } 161 162 private float getAlphaForOffset(View view) { 163 float viewSize = getSize(view); 164 final float fadeSize = ALPHA_FADE_END * viewSize; 165 float result = 1.0f; 166 float pos = view.getTranslationX(); 167 if (pos >= viewSize * ALPHA_FADE_START) { 168 result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize; 169 } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) { 170 result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize; 171 } 172 return Math.max(mMinAlpha, result); 173 } 174 175 private float getTextAlphaForOffset(View view) { 176 float viewSize = getSize(view); 177 final float fadeSize = ALPHA_TEXT_FADE_START * viewSize; 178 float result = 1.0f; 179 float pos = view.getTranslationX(); 180 if (pos >= 0) { 181 result = 1.0f - pos / fadeSize; 182 } else if (pos < 0) { 183 result = 1.0f + pos / fadeSize; 184 } 185 return Math.max(0, result); 186 } 187 188 // invalidate the view's own bounds all the way up the view hierarchy 189 public static void invalidateGlobalRegion(View view) { 190 invalidateGlobalRegion( 191 view, 192 new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); 193 } 194 195 // invalidate a rectangle relative to the view's coordinate system all the way up the view 196 // hierarchy 197 public static void invalidateGlobalRegion(View view, RectF childBounds) { 198 //childBounds.offset(view.getTranslationX(), view.getTranslationY()); 199 if (DEBUG_INVALIDATE) 200 Log.v(TAG, "-------------"); 201 while (view.getParent() != null && view.getParent() instanceof View) { 202 view = (View) view.getParent(); 203 view.getMatrix().mapRect(childBounds); 204 view.invalidate((int) Math.floor(childBounds.left), 205 (int) Math.floor(childBounds.top), 206 (int) Math.ceil(childBounds.right), 207 (int) Math.ceil(childBounds.bottom)); 208 if (DEBUG_INVALIDATE) { 209 Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left) 210 + "," + (int) Math.floor(childBounds.top) 211 + "," + (int) Math.ceil(childBounds.right) 212 + "," + (int) Math.ceil(childBounds.bottom)); 213 } 214 } 215 } 216 217 public boolean onInterceptTouchEvent(MotionEvent ev) { 218 final int action = ev.getAction(); 219 switch (action) { 220 case MotionEvent.ACTION_DOWN: 221 mLastY = ev.getY(); 222 mDragging = false; 223 View view = mCallback.getChildAtPosition(ev); 224 if (view instanceof SwipeableItemView) { 225 mCurrView = (SwipeableItemView) view; 226 } 227 mVelocityTracker.clear(); 228 if (mCurrView != null) { 229 mCurrAnimView = mCurrView.getSwipeableView(); 230 mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView); 231 mVelocityTracker.addMovement(ev); 232 mInitialTouchPosX = ev.getX(); 233 mInitialTouchPosY = ev.getY(); 234 } 235 mCallback.cancelDismissCounter(); 236 break; 237 case MotionEvent.ACTION_MOVE: 238 if (mCurrView != null) { 239 // Check the movement direction. 240 if (mLastY >= 0 && !mDragging) { 241 float currY = ev.getY(); 242 float currX = ev.getX(); 243 float deltaY = Math.abs(currY - mInitialTouchPosY); 244 float deltaX = Math.abs(currX - mInitialTouchPosX); 245 if (deltaY > mCurrView.getMinAllowScrollDistance() 246 && deltaY > (FACTOR * deltaX)) { 247 mLastY = ev.getY(); 248 mCallback.onScroll(); 249 return false; 250 } 251 } 252 mVelocityTracker.addMovement(ev); 253 float pos = ev.getX(); 254 float delta = pos - mInitialTouchPosX; 255 if (Math.abs(delta) > mPagingTouchSlop) { 256 mCallback.onBeginDrag(mCurrView.getSwipeableView()); 257 mPrevView = mCallback.getLastSwipedItem(); 258 mDragging = true; 259 mInitialTouchPosX = ev.getX() - mCurrAnimView.getTranslationX(); 260 mInitialTouchPosY = ev.getY(); 261 } 262 } 263 mLastY = ev.getY(); 264 break; 265 case MotionEvent.ACTION_UP: 266 case MotionEvent.ACTION_CANCEL: 267 mDragging = false; 268 mCurrView = null; 269 mCurrAnimView = null; 270 mLastY = -1; 271 break; 272 } 273 return mDragging; 274 } 275 276 /** 277 * @param view The view to be dismissed 278 * @param velocity The desired pixels/second speed at which the view should 279 * move 280 */ 281 private void dismissChild(final SwipeableItemView view, float velocity) { 282 final View animView = mCurrView.getSwipeableView(); 283 final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view); 284 float newPos = determinePos(animView, velocity); 285 int duration = determineDuration(animView, newPos, velocity); 286 287 Utils.enableHardwareLayer(animView); 288 ObjectAnimator anim = createDismissAnimation(animView, newPos, duration); 289 anim.addListener(new AnimatorListenerAdapter() { 290 @Override 291 public void onAnimationEnd(Animator animation) { 292 mCallback.onChildDismissed(mCurrView); 293 animView.setLayerType(View.LAYER_TYPE_NONE, null); 294 } 295 }); 296 anim.addUpdateListener(new AnimatorUpdateListener() { 297 @Override 298 public void onAnimationUpdate(ValueAnimator animation) { 299 if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) { 300 animView.setAlpha(getAlphaForOffset(animView)); 301 } 302 invalidateGlobalRegion(animView); 303 } 304 }); 305 anim.start(); 306 } 307 308 private void dismissChildren(final Collection<ConversationItemView> views, float velocity, 309 AnimatorListenerAdapter listener) { 310 final View animView = mCurrView.getSwipeableView(); 311 final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(mCurrView); 312 float newPos = determinePos(animView, velocity); 313 int duration = DISMISS_ANIMATION_DURATION; 314 ArrayList<Animator> animations = new ArrayList<Animator>(); 315 ObjectAnimator anim; 316 for (final ConversationItemView view : views) { 317 Utils.enableHardwareLayer(view); 318 anim = createDismissAnimation(view, newPos, duration); 319 anim.addUpdateListener(new AnimatorUpdateListener() { 320 @Override 321 public void onAnimationUpdate(ValueAnimator animation) { 322 if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) { 323 view.setAlpha(getAlphaForOffset(view)); 324 } 325 invalidateGlobalRegion(view); 326 } 327 }); 328 anim.addListener(new AnimatorListenerAdapter() { 329 @Override 330 public void onAnimationEnd(Animator animation) { 331 view.setLayerType(View.LAYER_TYPE_NONE, null); 332 } 333 }); 334 animations.add(anim); 335 } 336 AnimatorSet transitionSet = new AnimatorSet(); 337 transitionSet.playTogether(animations); 338 transitionSet.addListener(listener); 339 transitionSet.start(); 340 } 341 342 public void dismissChildren(ConversationItemView first, 343 final Collection<ConversationItemView> views, AnimatorListenerAdapter listener) { 344 mCurrView = first; 345 dismissChildren(views, 0f, listener); 346 } 347 348 private static int determineDuration(View animView, float newPos, float velocity) { 349 int duration = MAX_ESCAPE_ANIMATION_DURATION; 350 if (velocity != 0) { 351 duration = Math 352 .min(duration, 353 (int) (Math.abs(newPos - animView.getTranslationX()) * 1000f / Math 354 .abs(velocity))); 355 } else { 356 duration = DEFAULT_ESCAPE_ANIMATION_DURATION; 357 } 358 return duration; 359 } 360 361 private float determinePos(View animView, float velocity) { 362 final float newPos; 363 if (velocity < 0 || (velocity == 0 && animView.getTranslationX() < 0) 364 // if we use the Menu to dismiss an item in landscape, animate up 365 || (velocity == 0 && animView.getTranslationX() == 0 && mSwipeDirection == Y)) { 366 newPos = -getSize(animView); 367 } else { 368 newPos = getSize(animView); 369 } 370 return newPos; 371 } 372 373 public void snapChild(final SwipeableItemView view) { 374 final View animView = view.getSwipeableView(); 375 final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view); 376 final ObjectAnimator anim = createTranslationAnimation(animView, 0); 377 final int duration = SNAP_ANIM_LEN; 378 anim.setDuration(duration); 379 anim.addUpdateListener(new AnimatorUpdateListener() { 380 @Override 381 public void onAnimationUpdate(ValueAnimator animation) { 382 if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) { 383 animView.setAlpha(getAlphaForOffset(animView)); 384 } 385 invalidateGlobalRegion(animView); 386 } 387 }); 388 anim.addListener(new Animator.AnimatorListener() { 389 @Override 390 public void onAnimationStart(Animator animation) { 391 } 392 @Override 393 public void onAnimationEnd(Animator animation) { 394 animView.setAlpha(1.0f); 395 mCallback.onDragCancelled(mCurrView); 396 } 397 @Override 398 public void onAnimationCancel(Animator animation) { 399 } 400 @Override 401 public void onAnimationRepeat(Animator animation) { 402 } 403 }); 404 anim.start(); 405 } 406 407 public boolean onTouchEvent(MotionEvent ev) { 408 if (!mDragging) { 409 return false; 410 } 411 mVelocityTracker.addMovement(ev); 412 final int action = ev.getAction(); 413 switch (action) { 414 case MotionEvent.ACTION_OUTSIDE: 415 case MotionEvent.ACTION_MOVE: 416 if (mCurrView != null) { 417 float deltaX = ev.getX() - mInitialTouchPosX; 418 float deltaY = Math.abs(ev.getY() - mInitialTouchPosY); 419 // If the swipe started in the dead region, ignore it. 420 if (mInitialTouchPosX <= (DEAD_REGION_FOR_SWIPE * mDensityScale)){ 421 return true; 422 } 423 // If the user has gone vertical and not gone horizontalish AT 424 // LEAST minBeforeLock, switch to scroll. Otherwise, cancel 425 // the swipe. 426 if (!mDragging && deltaY > MIN_VERT && (Math.abs(deltaX)) < MIN_LOCK 427 && deltaY > (FACTOR * Math.abs(deltaX))) { 428 mCallback.onScroll(); 429 return false; 430 } 431 float minDistance = MIN_SWIPE; 432 if (Math.abs(deltaX) < minDistance) { 433 // Don't start the drag until at least X distance has 434 // occurred. 435 return true; 436 } 437 // don't let items that can't be dismissed be dragged more 438 // than maxScrollDistance 439 if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) { 440 float size = getSize(mCurrAnimView); 441 float maxScrollDistance = 0.15f * size; 442 if (Math.abs(deltaX) >= size) { 443 deltaX = deltaX > 0 ? maxScrollDistance : -maxScrollDistance; 444 } else { 445 deltaX = maxScrollDistance 446 * (float) Math.sin((deltaX / size) * (Math.PI / 2)); 447 } 448 } 449 setTranslation(mCurrAnimView, deltaX); 450 if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) { 451 mCurrAnimView.setAlpha(getAlphaForOffset(mCurrAnimView)); 452 if (mPrevView != null) { 453 // Base how much the text of the prev item is faded 454 // on how far the current item has moved. 455 mPrevView.setTextAlpha(getTextAlphaForOffset(mCurrAnimView)); 456 } 457 } 458 invalidateGlobalRegion(mCurrView.getSwipeableView()); 459 } 460 break; 461 case MotionEvent.ACTION_UP: 462 case MotionEvent.ACTION_CANCEL: 463 if (mCurrView != null) { 464 float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale; 465 mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity); 466 float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale; 467 float velocity = getVelocity(mVelocityTracker); 468 float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker); 469 470 // Decide whether to dismiss the current view 471 // Tweak constants below as required to prevent erroneous 472 // swipe/dismiss 473 float translation = Math.abs(mCurrAnimView.getTranslationX()); 474 float currAnimViewSize = getSize(mCurrAnimView); 475 // Long swipe = translation of .4 * width 476 boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH 477 && translation > 0.4 * currAnimViewSize; 478 // Fast swipe = > escapeVelocity and translation of .1 * 479 // width 480 boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) 481 && (Math.abs(velocity) > Math.abs(perpendicularVelocity)) 482 && (velocity > 0) == (mCurrAnimView.getTranslationX() > 0) 483 && translation > 0.05 * currAnimViewSize; 484 if (LOG_SWIPE_DISMISS_VELOCITY) { 485 Log.v(TAG, "Swipe/Dismiss: " + velocity + "/" + escapeVelocity + "/" 486 + perpendicularVelocity + ", x: " + translation + "/" 487 + currAnimViewSize); 488 } 489 490 boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) 491 && (childSwipedFastEnough || childSwipedFarEnough); 492 493 if (dismissChild) { 494 dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f); 495 } else { 496 snapChild(mCurrView); 497 } 498 } 499 break; 500 } 501 return true; 502 } 503 504 public interface Callback { 505 View getChildAtPosition(MotionEvent ev); 506 507 void cancelDismissCounter(); 508 509 void onScroll(); 510 511 boolean canChildBeDismissed(SwipeableItemView v); 512 513 void onBeginDrag(View v); 514 515 void onChildDismissed(SwipeableItemView v); 516 517 void onDragCancelled(SwipeableItemView v); 518 519 ConversationSelectionSet getSelectionSet(); 520 521 LeaveBehindItem getLastSwipedItem(); 522 } 523} 524