Implementing “Bounce Back” RecyclerView Animation with ItemTouchHelper.Callback

别等时光非礼了梦想. 提交于 2019-12-13 03:59:08

问题


I am attempting to create an animation in my RecyclerView rows with a Helper class that extends ItemTouchHelper.Callback.

The default "swipe" behaviour with ItemTouchHelper is that if a row is swiped with sufficient velocity, it disappears from view (i.e. swipe-to-dimiss). In my case, I want to create an animation that causes the row to bounce straight back if such an event takes place.

I have been able to create a ValueAnimator that animates the row back into view if it is swiped off screen. However, the problem is that I cannot get the parent ItemTouchHelper class to recognise that the x values it has stored have changed from the width of the row (so that it is completely offscreen) to 0 (so that it is back on screen).

This means that whenever I go to interact with the row after it has completed my custom animation, it behaves as if the row is still off screen, even though it is fully visible. This usually means that pressing it means the row jumps from an X position of 0 to an X position of the row width, and so animates back in again from the screen's edge. It does this a couple of times before it fully resets and behaves normally again.

This video will help show the problem. As the first two swipes are not flings off-screen, the default behaviour works. After the row is swiped off screen however and bounces back, tapping the row shows it pinging back in from the right edge:

Here is the code I use to conduct the animation:

public class CustomItemTouchHelper extends ItemTouchHelper.Callback {

    private RecoverAnimation ra;
    private boolean animatorRunning;

//...

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        final float per = (dX/viewHolder.itemView.getWidth());
        if (per > 0.8F && !animatorRunning && !isCurrentlyActive) {
            ra = new RecoverAnimation(c, recyclerView, viewHolder, ItemTouchHelper.ANIMATION_TYPE_SWIPE_CANCEL, actionState, dX, dY, 0, 0);
            ra.start();
        } else {
            getDefaultUIUtil().onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState, isCurrentlyActive);
        }
    }

//...

Note that this inner class, RecoverAnimation, is pulled from the ItemTouchHelper source code:

private class RecoverAnimation implements AnimatorListenerCompat {

    final float mStartDx;

    final float mStartDy;

    final float mTargetX;

    final float mTargetY;

    final Canvas mCanvas;

    final RecyclerView mRecyclerView;

    final RecyclerView.ViewHolder mViewHolder;

    final int mActionState;

    private final ValueAnimatorCompat mValueAnimator;

    float mX;

    float mY;

    // if user starts touching a recovering view, we put it into interaction mode again,
    // instantly.
    boolean mOverridden = false;

    private boolean mEnded = false;

    private float mFraction;

    public RecoverAnimation(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                            int actionState, float startDx, float startDy, float targetX, float targetY) {

        mRecyclerView = recyclerView;
        mCanvas = c;

        mActionState = actionState;
        mViewHolder = viewHolder;
        mStartDx = startDx;
        mStartDy = startDy;
        mTargetX = targetX;
        mTargetY = targetY;
        mValueAnimator = AnimatorCompatHelper.emptyValueAnimator();
        mValueAnimator.addUpdateListener(
                new AnimatorUpdateListenerCompat() {
                    @Override
                    public void onAnimationUpdate(ValueAnimatorCompat animation) {
                        setFraction(animation.getAnimatedFraction());
                        update();
                    }
                });
        mValueAnimator.setTarget(viewHolder.itemView);
        mValueAnimator.addListener(this);
        setFraction(0f);
    }

    public void setDuration(long duration) {
        mValueAnimator.setDuration(duration);
    }

    public void start() {
        animatorRunning = true;
        mViewHolder.setIsRecyclable(false);
        mValueAnimator.start();
    }

    public void cancel() {
        mValueAnimator.cancel();
    }

    public void setFraction(float fraction) {
        mFraction = fraction;
    }

    /**
     * We run updates on onDraw method but use the fraction from animator callback.
     * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
     */
    public void update() {
        if (mStartDx == mTargetX) {
            mX = ViewCompat.getTranslationX(mViewHolder.itemView);
        } else {
            mX = mStartDx + mFraction * (mTargetX - mStartDx);
        }
        if (mStartDy == mTargetY) {
            mY = ViewCompat.getTranslationY(mViewHolder.itemView);
        } else {
            mY = mStartDy + mFraction * (mTargetY - mStartDy);
        }
        getDefaultUIUtil().onDraw(mCanvas, mRecyclerView, mViewHolder.itemView, mX, mY, mActionState, false);
    }

    @Override
    public void onAnimationStart(ValueAnimatorCompat animation) {

    }

    @Override
    public void onAnimationEnd(ValueAnimatorCompat animation) {
        animatorRunning = false;
        mEnded = true;
        getDefaultUIUtil().onDraw(mCanvas, mRecyclerView, mViewHolder.itemView, 0, 0, ItemTouchHelper.ACTION_STATE_IDLE, false);
        getDefaultUIUtil().clearView(mViewHolder.itemView);
    }

    @Override
    public void onAnimationCancel(ValueAnimatorCompat animation) {
        setFraction(1f); //make sure we recover the view's state.
    }

    @Override
    public void onAnimationRepeat(ValueAnimatorCompat animation) {

    }
}

Is there anything I can do to counteract this issue, or is it simply not possible (yet)?


回答1:


Following solution will bounce back recyclerview item without implementing your custom RecoverAnimation class.

    public class CustomItemTouchHelper extends ItemTouchHelper.Callback {

        private RecoverAnimation ra;
        private boolean animatorRunning;
        private boolean swipeBack = false;

    //...

        @Override
        public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
            recyclerView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event)
            {
                if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL)
                {
                    swipeBack = true;
                }
                else
                {
                    swipeBack = false;
                }
                return false;
            }
        });
        }

        @Override
        public int convertToAbsoluteDirection(int flags, int layoutDirection) {
             return swipeBack ? 0 : super.convertToAbsoluteDirection(flags, layoutDirection);
        }

    //...


来源:https://stackoverflow.com/questions/32784798/implementing-bounce-back-recyclerview-animation-with-itemtouchhelper-callback

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!