summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java')
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java462
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