diff options
Diffstat (limited to 'src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java')
-rw-r--r-- | src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java | 462 |
1 files changed, 0 insertions, 462 deletions
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java b/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java deleted file mode 100644 index 4988259..0000000 --- a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationlist; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.Resources; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.OnItemTouchListener; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; -import com.android.messaging.util.UiUtils; - -/** - * Animation and touch helper class for Conversation List swipe. - */ -public class ConversationListSwipeHelper implements OnItemTouchListener { - private static final int UNIT_SECONDS = 1000; - private static final boolean ANIMATING = true; - - private static final float ERROR_FACTOR_MULTIPLIER = 1.2f; - private static final float PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.4f; - private static final float FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.05f; - - private static final int SWIPE_DIRECTION_NONE = 0; - private static final int SWIPE_DIRECTION_LEFT = 1; - private static final int SWIPE_DIRECTION_RIGHT = 2; - - private final RecyclerView mRecyclerView; - private final long mDefaultRestoreAnimationDuration; - private final long mDefaultDismissAnimationDuration; - private final long mMaxTranslationAnimationDuration; - private final int mTouchSlop; - private final int mMinimumFlingVelocity; - private final int mMaximumFlingVelocity; - - /* Valid throughout a single gesture. */ - private VelocityTracker mVelocityTracker; - private float mInitialX; - private float mInitialY; - private boolean mIsSwiping; - private ConversationListItemView mListItemView; - - public ConversationListSwipeHelper(final RecyclerView recyclerView) { - mRecyclerView = recyclerView; - - final Context context = mRecyclerView.getContext(); - final Resources res = context.getResources(); - mDefaultRestoreAnimationDuration = res.getInteger(R.integer.swipe_duration_ms); - mDefaultDismissAnimationDuration = res.getInteger(R.integer.swipe_duration_ms); - mMaxTranslationAnimationDuration = res.getInteger(R.integer.swipe_duration_ms); - - final ViewConfiguration viewConfiguration = ViewConfiguration.get(context); - mTouchSlop = viewConfiguration.getScaledPagingTouchSlop(); - mMaximumFlingVelocity = Math.min( - viewConfiguration.getScaledMaximumFlingVelocity(), - res.getInteger(R.integer.swipe_max_fling_velocity_px_per_s)); - mMinimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity(); - } - - @Override - public boolean onInterceptTouchEvent(final RecyclerView recyclerView, final MotionEvent event) { - if (event.getPointerCount() > 1) { - // Ignore subsequent pointers. - return false; - } - - // We are not yet tracking a swipe gesture. Begin detection by spying on - // touch events bubbling down to our children. - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - if (!hasGestureSwipeTarget()) { - onGestureStart(); - - mVelocityTracker.addMovement(event); - mInitialX = event.getX(); - mInitialY = event.getY(); - - final View viewAtPoint = mRecyclerView.findChildViewUnder(mInitialX, mInitialY); - final ConversationListItemView child = (ConversationListItemView) viewAtPoint; - if (viewAtPoint instanceof ConversationListItemView && - child != null && child.isSwipeAnimatable()) { - // Begin detecting swipe on the target for the rest of the gesture. - mListItemView = child; - if (mListItemView.isAnimating()) { - mListItemView = null; - } - } else { - mListItemView = null; - } - } - break; - case MotionEvent.ACTION_MOVE: - if (hasValidGestureSwipeTarget()) { - mVelocityTracker.addMovement(event); - - final int historicalCount = event.getHistorySize(); - // First consume the historical events, then consume the current ones. - for (int i = 0; i < historicalCount + 1; i++) { - float currX; - float currY; - if (i < historicalCount) { - currX = event.getHistoricalX(i); - currY = event.getHistoricalY(i); - } else { - currX = event.getX(); - currY = event.getY(); - } - final float deltaX = currX - mInitialX; - final float deltaY = currY - mInitialY; - final float absDeltaX = Math.abs(deltaX); - final float absDeltaY = Math.abs(deltaY); - - if (!mIsSwiping && absDeltaY > mTouchSlop - && absDeltaY > (ERROR_FACTOR_MULTIPLIER * absDeltaX)) { - // Stop detecting swipe for the remainder of this gesture. - onGestureEnd(); - return false; - } - - if (absDeltaX > mTouchSlop) { - // Swipe detected. Return true so we can handle the gesture in - // onTouchEvent. - mIsSwiping = true; - - // We don't want to suddenly jump the slop distance. - mInitialX = event.getX(); - mInitialY = event.getY(); - - onSwipeGestureStart(mListItemView); - return true; - } - } - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (hasGestureSwipeTarget()) { - onGestureEnd(); - } - break; - } - - // Start intercepting touch events from children if we detect a swipe. - return mIsSwiping; - } - - @Override - public void onTouchEvent(final RecyclerView recyclerView, final MotionEvent event) { - // We should only be here if we intercepted the touch due to swipe. - Assert.isTrue(mIsSwiping); - - // We are now tracking a swipe gesture. - mVelocityTracker.addMovement(event); - - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_OUTSIDE: - case MotionEvent.ACTION_MOVE: - if (hasValidGestureSwipeTarget()) { - mListItemView.setSwipeTranslationX(event.getX() - mInitialX); - } - break; - case MotionEvent.ACTION_UP: - if (hasValidGestureSwipeTarget()) { - final float maxVelocity = mMaximumFlingVelocity; - mVelocityTracker.computeCurrentVelocity(UNIT_SECONDS, maxVelocity); - final float velocityX = getLastComputedXVelocity(); - - final float translationX = mListItemView.getSwipeTranslationX(); - - int swipeDirection = SWIPE_DIRECTION_NONE; - if (translationX != 0) { - swipeDirection = - translationX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT; - } else if (velocityX != 0) { - swipeDirection = - velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT; - } - - final boolean fastEnough = isTargetSwipedFastEnough(); - final boolean farEnough = isTargetSwipedFarEnough(); - - final boolean shouldDismiss = (fastEnough || farEnough); - - if (shouldDismiss) { - if (fastEnough) { - animateDismiss(mListItemView, velocityX); - } else { - animateDismiss(mListItemView, swipeDirection); - } - } else { - animateRestore(mListItemView, velocityX); - } - - onSwipeGestureEnd(mListItemView, - shouldDismiss ? swipeDirection : SWIPE_DIRECTION_NONE); - } else { - onGestureEnd(); - } - break; - case MotionEvent.ACTION_CANCEL: - if (hasValidGestureSwipeTarget()) { - animateRestore(mListItemView, 0f); - onSwipeGestureEnd(mListItemView, SWIPE_DIRECTION_NONE); - } else { - onGestureEnd(); - } - break; - } - } - - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { - } - - /** - * We have started to intercept a series of touch events. - */ - private void onGestureStart() { - mIsSwiping = false; - // Work around bug in RecyclerView that sends two identical ACTION_DOWN - // events to #onInterceptTouchEvent. - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.clear(); - } - - /** - * The series of touch events has been detected as a swipe. - * - * Now that the gesture is a swipe, we will begin translating the view of the - * given viewHolder. - */ - private void onSwipeGestureStart(final ConversationListItemView itemView) { - mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true); - setHardwareAnimatingLayerType(itemView, ANIMATING); - itemView.setAnimating(true); - } - - /** - * The current swipe gesture is complete. - */ - private void onSwipeGestureEnd(final ConversationListItemView itemView, - final int swipeDirection) { - if (swipeDirection == SWIPE_DIRECTION_RIGHT || swipeDirection == SWIPE_DIRECTION_LEFT) { - itemView.onSwipeComplete(); - } - - // Balances out onSwipeGestureStart. - itemView.setAnimating(false); - - onGestureEnd(); - } - - /** - * The series of touch events has ended in an {@link MotionEvent#ACTION_UP} - * or {@link MotionEvent#ACTION_CANCEL}. - */ - private void onGestureEnd() { - mVelocityTracker.recycle(); - mVelocityTracker = null; - mIsSwiping = false; - mListItemView = null; - } - - /** - * A swipe animation has started. - */ - private void onSwipeAnimationStart(final ConversationListItemView itemView) { - // Disallow interactions. - itemView.setAnimating(true); - ViewCompat.setHasTransientState(itemView, true); - setHardwareAnimatingLayerType(itemView, ANIMATING); - } - - /** - * The swipe animation has ended. - */ - private void onSwipeAnimationEnd(final ConversationListItemView itemView) { - // Restore interactions. - itemView.setAnimating(false); - ViewCompat.setHasTransientState(itemView, false); - setHardwareAnimatingLayerType(itemView, !ANIMATING); - } - - /** - * Animate the dismissal of the given item. The given velocityX is taken into consideration for - * the animation duration. Whether the item is dismissed to the left or right is dependent on - * the given velocityX. - */ - private void animateDismiss(final ConversationListItemView itemView, final float velocityX) { - Assert.isTrue(velocityX != 0); - final int direction = velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT; - animateDismiss(itemView, direction, velocityX); - } - - /** - * Animate the dismissal of the given item. The velocityX is assumed to be 0. - */ - private void animateDismiss(final ConversationListItemView itemView, final int swipeDirection) { - animateDismiss(itemView, swipeDirection, 0f); - } - - /** - * Animate the dismissal of the given item. - */ - private void animateDismiss(final ConversationListItemView itemView, - final int swipeDirection, final float velocityX) { - Assert.isTrue(swipeDirection != SWIPE_DIRECTION_NONE); - - onSwipeAnimationStart(itemView); - - final float animateTo = (swipeDirection == SWIPE_DIRECTION_RIGHT) ? - mRecyclerView.getWidth() : -mRecyclerView.getWidth(); - final long duration; - if (velocityX != 0) { - final float deltaX = animateTo - itemView.getSwipeTranslationX(); - duration = calculateTranslationDuration(deltaX, velocityX); - } else { - duration = mDefaultDismissAnimationDuration; - } - - final ObjectAnimator animator = getSwipeTranslationXAnimator( - itemView, animateTo, duration, UiUtils.DEFAULT_INTERPOLATOR); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final Animator animation) { - onSwipeAnimationEnd(itemView); - } - }); - animator.start(); - } - - /** - * Animate the bounce back of the given item. - */ - private void animateRestore(final ConversationListItemView itemView, - final float velocityX) { - onSwipeAnimationStart(itemView); - - final float translationX = itemView.getSwipeTranslationX(); - final long duration; - if (velocityX != 0 // Has velocity. - && velocityX > 0 != translationX > 0) { // Right direction. - duration = calculateTranslationDuration(translationX, velocityX); - } else { - duration = mDefaultRestoreAnimationDuration; - } - final ObjectAnimator animator = getSwipeTranslationXAnimator( - itemView, 0f, duration, UiUtils.DEFAULT_INTERPOLATOR); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final Animator animation) { - onSwipeAnimationEnd(itemView); - } - }); - animator.start(); - } - - /** - * Create and start an animator that animates the given view's translationX - * from its current value to the value given by animateTo. - */ - private ObjectAnimator getSwipeTranslationXAnimator(final ConversationListItemView itemView, - final float animateTo, final long duration, final TimeInterpolator interpolator) { - final ObjectAnimator animator = - ObjectAnimator.ofFloat(itemView, "swipeTranslationX", animateTo); - animator.setDuration(duration); - animator.setInterpolator(interpolator); - return animator; - } - - /** - * Determine if the swipe has enough velocity to be dismissed. - */ - private boolean isTargetSwipedFastEnough() { - final float velocityX = getLastComputedXVelocity(); - final float velocityY = mVelocityTracker.getYVelocity(); - final float minVelocity = mMinimumFlingVelocity; - final float translationX = mListItemView.getSwipeTranslationX(); - final float width = mListItemView.getWidth(); - return (Math.abs(velocityX) > minVelocity) // Fast enough. - && (Math.abs(velocityX) > Math.abs(velocityY)) // Not unintentional. - && (velocityX > 0) == (translationX > 0) // Right direction. - && Math.abs(translationX) > - FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement. - } - - /** - * Only used during a swipe gesture. Determine if the swipe has enough distance to be - * dismissed. - */ - private boolean isTargetSwipedFarEnough() { - final float velocityX = getLastComputedXVelocity(); - - final float translationX = mListItemView.getSwipeTranslationX(); - final float width = mListItemView.getWidth(); - - return (velocityX >= 0) == (translationX > 0) // Right direction. - && Math.abs(translationX) > - PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement. - } - - private long calculateTranslationDuration(final float deltaPosition, final float velocity) { - Assert.isTrue(velocity != 0); - final float durationInSeconds = Math.abs(deltaPosition / velocity); - return Math.min((int) (durationInSeconds * UNIT_SECONDS), mMaxTranslationAnimationDuration); - } - - private boolean hasGestureSwipeTarget() { - return mListItemView != null; - } - - private boolean hasValidGestureSwipeTarget() { - return hasGestureSwipeTarget() && mListItemView.getParent() == mRecyclerView; - } - - /** - * Enable a hardware layer for the it view and build that layer. - */ - private void setHardwareAnimatingLayerType(final ConversationListItemView itemView, - final boolean animating) { - if (animating) { - itemView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - if (itemView.getWindowToken() != null) { - itemView.buildLayer(); - } - } else { - itemView.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - - private float getLastComputedXVelocity() { - return mVelocityTracker.getXVelocity(); - } -}
\ No newline at end of file |