h6ah4i / android-advancedrecyclerview

RecyclerView extension library which provides advanced features. (ex. Google's Inbox app like swiping, Play Music app like drag and drop sorting)
https://advancedrecyclerview.h6ah4i.com/
Apache License 2.0
5.31k stars 862 forks source link

How can I start drag item animation programmatically? #384

Open liuwei-tapatalk opened 7 years ago

liuwei-tapatalk commented 7 years ago

Hi, I'm working on such behavior : long click item -> show a dialog -> select 'move' on the dialog -> the item should floats up, then user can drag it and move.

I know I can use dragManager.setDraggingItemScale(1.2f) to make item floats up. But it only work when user start to drag. What I want is making the item floats up before user start to drag. Is there anyway to do it?

liuwei-tapatalk commented 7 years ago

@h6ah4i Thanks for your sample! I tried your solution and it works very well. But I still have two question about it.

Q1. Why we need PAYLOAD_DRAGGABLE_ITEM_CHANGED ? I think we can simplified the code as below :

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        final AbstractDataProvider.Data item = mProvider.getItem(position);
        holder.mTextView.setText(item.getText());
        final int dragState = holder.getDragStateFlags();
// Just remove it.        if (draggableChanged || ((dragState & Draggable.STATE_FLAG_IS_UPDATED) != 0)) {
            int bgResId;

            if ((dragState & Draggable.STATE_FLAG_IS_ACTIVE) != 0) {
                bgResId = R.drawable.bg_item_dragging_active_state;
                DrawableUtils.clearState(holder.mContainer.getForeground());
            } else if ((dragState & Draggable.STATE_FLAG_DRAGGING) != 0) {
                bgResId = R.drawable.bg_item_dragging_state;
            } else {
                bgResId = R.drawable.bg_item_normal_state;
            }

            holder.mContainer.setBackgroundResource(bgResId);
// Remove it        }

        if ((((dragState & Draggable.STATE_FLAG_IS_ACTIVE) != 0) || (dragState & Draggable.STATE_FLAG_DRAGGING) == 0)
                && (position == mDraggableItemPosition)) {
            holder.mContainer.setBackgroundResource(R.drawable.bg_item_dragging_active_state);
            ViewCompat.setScaleX(holder.itemView, 1.2f);
            ViewCompat.setScaleY(holder.itemView, 1.2f);
            ViewCompat.setTranslationZ(holder.itemView, 2.0f);
        } else {
            ViewCompat.setScaleX(holder.itemView, 1.0f);
            ViewCompat.setScaleY(holder.itemView, 1.0f);
            ViewCompat.setTranslationZ(holder.itemView, 0.0f);
        }
    }

Q2. How can I add animation when it scale? I want add animation when it enter 'drag state' from 'normal state'. I tried some code as below, but it works very weird. Firstly it will scale up with animation as expect. But when onAnimationEnd, it will scale down to normal size then scale up to large size immediately.

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    //....Some code....
    if ((((dragState & Draggable.STATE_FLAG_IS_ACTIVE) != 0) || (dragState & Draggable.STATE_FLAG_DRAGGING) == 0)
            && (position == mDraggableItemPosition)) {
        holder.mContainer.setBackgroundResource(R.drawable.bg_item_dragging_active_state);

        Animation anim1 = new ScaleAnimation(1f, 1.2f, 1f, 1.2f, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        Animation anim2 = new AlphaAnimation(1f, 0.95f);
        final AnimationSet as = new AnimationSet(true);
        as.addAnimation(anim1);
        as.addAnimation(anim2);
        as.setFillAfter(false);
        as.setDuration(250);
        final View v = holder.itemView;
        as.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                // nothing
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                v.clearAnimation();
                ViewCompat.setScaleX(v, 1.2f);
                ViewCompat.setScaleY(v, 1.2f);
                ViewCompat.setTranslationZ(v, 2.0f);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
                // nothing
            }
        });
        holder.itemView.startAnimation(as);
    } else {
        ViewCompat.setScaleX(holder.itemView, 1.0f);
        ViewCompat.setScaleY(holder.itemView, 1.0f);
        ViewCompat.setTranslationZ(holder.itemView, 0.0f);
    }

Thanks for your help again and waiting for your reply!

liuwei-tapatalk commented 7 years ago

By the way, for the original question, my solution as below:

private void showMoveItemAnim(final View view) {
    float scale = 1.2f;
    Animation anim1 = new ScaleAnimation(1f, scale, 1f, scale, Animation.RELATIVE_TO_SELF,
            0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    Animation anim2 = new AlphaAnimation(1f, 0.95f);
    final AnimationSet as = new AnimationSet(true);
    as.addAnimation(anim1);
    as.addAnimation(anim2);
    as.setFillAfter(true);
    as.setDuration(250);
    view.startAnimation(as);

    view.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_MOVE
                    || event.getAction() == MotionEvent.ACTION_UP) {
                (new Handler()).postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        v.clearAnimation();
                    }
                }, 200);
                view.setOnTouchListener(null);
            }
            return false;
        }
    });
}

My solution can provide animation when it enter draggable state, but I'm not sure when is the best time to clearAnimation, so I clearAnimation after 200ms. I think my code is very ugly 👎

h6ah4i commented 7 years ago

@liuwei-tapatalk

Q1. Why we need PAYLOAD_DRAGGABLE_ITEM_CHANGED ? I think we can simplified the code as below:

That's okay. No problem at all.

Q2. How can I add animation when it scale? I want add animation when it enter 'drag state' from 'normal state'. I tried some code as below, but it works very weird. Firstly it will scale up with animation as expect. But when onAnimationEnd, it will scale down to normal size then scale up to large size immediately.

Manipulating animations for itemView directly is not safe. Animations should always be handled through an ItemAnimator, without the manner it will lead fatal inconsistency inside of the RecyclerView (and ending in weird exceptions).

ref.: https://medium.com/master-of-code-global/recyclerview-tips-and-recipes-476410fa12fd related issues: #336, #26, #315


EDIT: Found a good article creating a custom ItemAnimator.

http://frogermcs.github.io/recyclerview-animations-androiddevsummit-write-up/

FYI, DefaultItemAnimator implementation looks a bit complicated so this library provides RefactoredDefaultItemAnimator which provides the same behavior with more cleaned up code base.

liuwei-tapatalk commented 7 years ago

Sorry for reply so late. I still can not implement this animation perfectly. And I don't know how to describe the problem.

After many attempts, I think the best solution is your implement in https://github.com/h6ah4i/android-advancedrecyclerview/commit/fcd2f31fc565d877d736276c440e384f91238986.