Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f75101df authored by cketti's avatar cketti
Browse files

Add support for swipe actions not animating the view all the way off the screen

parent 944595f9
Loading
Loading
Loading
Loading
+9 −9
Original line number Diff line number Diff line
package com.fsck.k9

enum class SwipeAction {
    None,
    ToggleSelection,
    ToggleRead,
    ToggleStar,
    Archive,
    Delete,
    Spam,
    Move
enum class SwipeAction(val removesItem: Boolean) {
    None(removesItem = false),
    ToggleSelection(removesItem = false),
    ToggleRead(removesItem = false),
    ToggleStar(removesItem = false),
    Archive(removesItem = true),
    Delete(removesItem = true),
    Spam(removesItem = true),
    Move(removesItem = true)
}
+23 −1
Original line number Diff line number Diff line
@@ -101,7 +101,7 @@ class MessageListSwipeCallback(
        val viewWidth = view.width
        val viewHeight = view.height

        val isViewAnimatingBack = !isCurrentlyActive && abs(dX).toInt() >= viewWidth
        val isViewAnimatingBack = !isCurrentlyActive

        canvas.withTranslation(x = view.left.toFloat(), y = view.top.toFloat()) {
            if (isViewAnimatingBack) {
@@ -170,6 +170,28 @@ class MessageListSwipeCallback(

        swipeLayout.draw(this)
    }

    override fun getMaxSwipeDistance(recyclerView: RecyclerView, direction: Int): Int {
        return recyclerView.width / 2
    }

    override fun shouldAnimateOut(direction: Int): Boolean {
        return when (direction) {
            ItemTouchHelper.RIGHT -> swipeRightAction.removesItem
            ItemTouchHelper.LEFT -> swipeLeftAction.removesItem
            else -> error("Unsupported direction")
        }
    }

    override fun getAnimationDuration(
        recyclerView: RecyclerView,
        animationType: Int,
        animateDx: Float,
        animateDy: Float
    ): Long {
        val percentage = abs(animateDx) / recyclerView.width
        return (super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy) * percentage).toLong()
    }
}

fun interface SwipeActionSupportProvider {
+81 −39
Original line number Diff line number Diff line
@@ -47,27 +47,10 @@ import java.util.ArrayList;
import java.util.List;

/**
 * This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
 * <p>
 * It works with a RecyclerView and a Callback class, which configures what type of interactions
 * are enabled and also receives events when user performs these actions.
 * <p>
 * Depending on which functionality you support, you should override
 * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or
 * {@link Callback#onSwiped(ViewHolder, int)}.
 * <p>
 * This class is designed to work with any LayoutManager but for certain situations, it can be
 * optimized for your custom LayoutManager by extending methods in the
 * {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler}
 * interface in your LayoutManager.
 * <p>
 * By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. You can
 * customize these behaviors by overriding {@link Callback#onChildDraw(Canvas, RecyclerView,
 * ViewHolder, float, float, int, boolean)}
 * or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
 * boolean)}.
 * <p/>
 * Most of the time you only need to override <code>onChildDraw</code>.
 * Fork of {@link androidx.recyclerview.widget.ItemTouchHelper} that supports horizontal swipe actions that don't
 * remove the list item. In that case item views are not animated all the way off the screen.
 * <br>
 * See {@link Callback#shouldAnimateOut(int)} and {@link Callback#getMaxSwipeDistance(RecyclerView, int)}.
 */
public class ItemTouchHelper extends RecyclerView.ItemDecoration
        implements RecyclerView.OnChildAttachStateChangeListener {
@@ -106,6 +89,11 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
     */
    public static final int END = RIGHT << 2;

    /**
     * Flag that indicates a swipe was performed using a fling.
     */
    private static final int FLING = 1 << 20;

    /**
     * ItemTouchHelper is in idle state. At this state, either there is no related motion event by
     * the user or latest motion events have not yet triggered a swipe or drag.
@@ -562,11 +550,26 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
            getSelectedDxDy(mTmpPosition);
            dx = mTmpPosition[0];
            dy = mTmpPosition[1];

            if ((mSelectedFlags & (LEFT | RIGHT)) != 0 && dx != 0) {
                dx = limitDeltaX(parent, dx);
            }
        }

        mCallback.onDraw(c, parent, mSelected,
                mRecoverAnimations, mActionState, dx, dy);
    }

    private float limitDeltaX(RecyclerView recyclerView, float deltaX) {
        int swipeDirection = deltaX > 0 ? RIGHT : LEFT;
        if (!mCallback.shouldAnimateOut(swipeDirection)) {
            int maxWidth = mCallback.getMaxSwipeDistance(recyclerView, swipeDirection);
            deltaX = Math.abs(deltaX) > maxWidth ? Math.signum(deltaX) * maxWidth : deltaX;
        }

        return deltaX;
    }

    /**
     * Starts dragging or swiping the given View. Call with null if you want to clear it.
     *
@@ -602,9 +605,16 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
        if (mSelected != null) {
            final ViewHolder prevSelected = mSelected;
            if (prevSelected.itemView.getParent() != null) {
                final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
                final int swipeFlags = prevActionState == ACTION_STATE_DRAG ? 0
                        : swipeIfNecessary(prevSelected);
                final boolean wasFling = (swipeFlags & FLING) != 0;
                final int swipeDir = swipeFlags & ~FLING;
                releaseVelocityTracker();

                getSelectedDxDy(mTmpPosition);
                final float currentTranslateX = limitDeltaX(mRecyclerView, mTmpPosition[0]);
                final float currentTranslateY = mTmpPosition[1];

                // find where we should animate to
                final float targetTranslateX, targetTranslateY;
                int animationType;
@@ -613,8 +623,15 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
                    case RIGHT:
                    case START:
                    case END:
                        targetTranslateY = 0;
                        if (mCallback.shouldAnimateOut(swipeDir)) {
                            targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
                        } else if (wasFling) {
                            int maxSwipeDistance = mCallback.getMaxSwipeDistance(mRecyclerView, swipeDir);
                            targetTranslateX = Math.signum(mDx) * maxSwipeDistance;
                        } else {
                            targetTranslateX = currentTranslateX;
                        }
                        targetTranslateY = 0;
                        break;
                    case UP:
                    case DOWN:
@@ -632,9 +649,7 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
                } else {
                    animationType = ANIMATION_TYPE_SWIPE_CANCEL;
                }
                getSelectedDxDy(mTmpPosition);
                final float currentTranslateX = mTmpPosition[0];
                final float currentTranslateY = mTmpPosition[1];

                final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
                        prevActionState, currentTranslateX, currentTranslateY,
                        targetTranslateX, targetTranslateY) {
@@ -1208,32 +1223,36 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
        }
        final int originalFlags = (originalMovementFlags
                & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
        int swipeDir;
        int swipeFlags;
        if (Math.abs(mDx) > Math.abs(mDy)) {
            if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
            if ((swipeFlags = checkHorizontalSwipe(viewHolder, flags)) > 0) {
                int fling = swipeFlags & FLING;
                int swipeDir = swipeFlags & ~FLING;
                // if swipe dir is not in original flags, it should be the relative direction
                if ((originalFlags & swipeDir) == 0) {
                    // convert to relative
                    return Callback.convertToRelativeDirection(swipeDir,
                            ViewCompat.getLayoutDirection(mRecyclerView));
                            ViewCompat.getLayoutDirection(mRecyclerView)) | fling;
                }
                return swipeDir;
                return swipeFlags;
            }
            if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
                return swipeDir;
            if ((swipeFlags = checkVerticalSwipe(viewHolder, flags)) > 0) {
                return swipeFlags;
            }
        } else {
            if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
                return swipeDir;
            if ((swipeFlags = checkVerticalSwipe(viewHolder, flags)) > 0) {
                return swipeFlags;
            }
            if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
            if ((swipeFlags = checkHorizontalSwipe(viewHolder, flags)) > 0) {
                int fling = swipeFlags & FLING;
                int swipeDir = swipeFlags & ~FLING;
                // if swipe dir is not in original flags, it should be the relative direction
                if ((originalFlags & swipeDir) == 0) {
                    // convert to relative
                    return Callback.convertToRelativeDirection(swipeDir,
                            ViewCompat.getLayoutDirection(mRecyclerView));
                            ViewCompat.getLayoutDirection(mRecyclerView)) | fling;
                }
                return swipeDir;
                return swipeFlags;
            }
        }
        return 0;
@@ -1252,7 +1271,7 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
                if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag
                        && absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity)
                        && absXVelocity > Math.abs(yVelocity)) {
                    return velDirFlag;
                    return velDirFlag | FLING;
                }
            }

@@ -1987,7 +2006,7 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
                anim.update();
                final int count = c.save();
                onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
                        false);
                        anim.mAnimationType == ANIMATION_TYPE_SWIPE_SUCCESS && !anim.mIsPendingCleanup);
                c.restoreToCount(count);
            }
            if (selected != null) {
@@ -2189,6 +2208,29 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
            }
            return value;
        }

        /**
         * Called to find out whether or not views should be animated out when the swipe action was successfully
         * triggered.
         *
         * @param direction The swipe direction.
         * @return {@code true} if the swipe action removes the item from the list. {@code false} otherwise.
         */
        public boolean shouldAnimateOut(int direction) {
            return true;
        }

        /**
         * Called to find out how far a view can be moved during a swipe when the swipe action doesn't remove the item
         * from the list. See {@link #shouldAnimateOut(int)}.
         *
         * @param recyclerView The RecyclerView instance to which ItemTouchHelper is attached to.
         * @param direction The swipe direction.
         * @return The maximum distance in pixels that a view can be moved during a swipe.
         */
        public int getMaxSwipeDistance(RecyclerView recyclerView, int direction) {
            return recyclerView.getWidth();
        }
    }

    /**