正确覆盖RecyclerView动画 [英] Properly overwriting RecyclerView animations

查看:169
本文介绍了正确覆盖RecyclerView动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个RecycleView,其中显示项目列表.我为RecyclerView指定默认的动画制作器,如下所示:

I have a RecycleView in which a display a list of items. I specify the default animator to the RecyclerView like this:

recyclerView.setItemAnimator( new DefaultItemAnimator() );

一切正常,但是我想使用自己的自定义动画来添加/删除/更新列表中的元素.

Everything works great, but I want to use my own custom animations for adding/removing/updating the elements in the list.

我定义了一个自定义动画类,如下所示:

I defined a custom animator class like this:

    public class MyAnimator extends RecyclerView.ItemAnimator {

    @Override
    public  boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
        return false;
    }

    @Override
    public  boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
        return false;
    }

    @Override
    public  boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
        return false;
    }

    @Override
    public  boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
        return false;
    }

    @Override
    public  void runPendingAnimations() {

    }

    @Override
    public  void endAnimation(RecyclerView.ViewHolder item) {

    }

    @Override
    public  void endAnimations() {

    }

    @Override
    public  boolean isRunning() {
        return false;
    }
}

并以与对DefaultItemAnimator相同的方式进行设置.动画不再播放了,所以我猜想它起作用了,但是问题是项目有时会相互堆叠,而当我删除所有项目时,仍然剩下一些,所以我想我丢失了一些东西.

And set it the same way as I did for DefaultItemAnimator. The animations are not playing anymore, so I guess it worked, but the problem is that items get stacked sometimes on top of each other, and when I remove all the items, some are still left, so I guess I'm missing something.

据我了解,animateDisappearance是从列表中删除该项目时调用的方法.如果我返回false,就应该跳过动画,据我理解,对吗?

As far as I understood animateDisappearance is a method that gets called, when the item is removed from the list. If I return false, it should simply skip the animation as far as I understood, correct?

我甚至在正确的轨道上吗?当我在github上查找此示例时,结果很少,总的来说,我似乎找不到如何执行此操作的基本代码示例,而我发现的全部都是数千行代码.

Am I even on the right track? when I look for examples of this on github, there are very few results and overall I can't seem to find any basic code example how to do this, and the ones I found are all thousands of lines of codes.

如何在不使用任何外部库的情况下简单地用自己的默认覆盖添加/删除动画?谢谢!

How can I simply overwrite the default add/remove animations with my own without using any external libraries? Thanks!

我能够通过以下方式覆盖默认动画:

I was able to override the default animation the following way:

        recyclerView.setItemAnimator(new DefaultItemAnimator() {
            @Override
            public boolean animateRemove(RecyclerView.ViewHolder holder) {
                holder.itemView.clearAnimation();
                final RecyclerView.ViewHolder h = holder;
                holder.itemView.animate()
                        .alpha(0)
                        .setInterpolator(new AccelerateInterpolator(2.f))
                        .setDuration(1350)
                        .setListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(Animator animation) {
                                dispatchRemoveFinished(h);
                            }
                        })
                        .start();
                //
                return false;
            }
        } );

动画效果很好,但是由于某种原因,似乎立即触发了"dispatchRemoveFinished",因此,在移除视图之后,它们会立即执行此操作,而不是在动画之后调整其余元素.有什么办法可以解决这个问题?

The animation works perfectly, but for some reason it seems like 'dispatchRemoveFinished' is triggered instantly, so instead of the remaining elements adjusting AFTER the animation, they do it instantly as soon as the view is removed. Is there any way to fix this?

推荐答案

实现 RecyclerView.ItemAnimator 时,您必须遵循一些规则,否则RecyclerView状态会变得混乱:

When implementing your RecyclerView.ItemAnimator you have to follow few rules or RecyclerView state will get messed up:

  1. 所有返回false的空方法必须至少调用 dispatchAnimationFinished(viewHolder)并清除动画状态.

如果这些方法要开始动画,则应 dispatchAnimationStarted(viewHolder),存储动画请求并返回true,以调用 runPendingAnimations()应该真正开始.

If those methods are to start animation, You should dispatchAnimationStarted(viewHolder), store animation request and return true to get call to runPendingAnimations() where animations should actually begin.

您需要跟踪正在进行的动画才能正确取消它们.您还将收到对已经进行动画处理的项目的请求.

You need to keep track of ongoing animations to be able to properly cancel them, You will get requests for items that are already being animated as well.

这是一个示例 ItemAnimator ,该示例仅对移除和移动进行动画处理.请注意充当动画数据持有者和动画状态侦听器的内部类:

Here's a sample ItemAnimator that animates only removal and movement. Note the inner class that acts as data-holder of animations and listener of animation states:

public class RecAnimator extends RecyclerView.ItemAnimator {

private final static String TAG = "RecAnimator";

private final static int ANIMATION_TYPE_DISAPPEAR = 1;
private final static int ANIMATION_TYPE_MOVE = 2;

// must keep track of all pending/ongoing animations.
private final ArrayList<AnimInfo> pending = new ArrayList<>();
private final HashMap<RecyclerView.ViewHolder, AnimInfo> disappearances = new HashMap<>();
private final HashMap<RecyclerView.ViewHolder, AnimInfo> persistences = new HashMap<>();

@Override
public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
    pending.add(new AnimInfo(viewHolder, ANIMATION_TYPE_DISAPPEAR, 0));
    dispatchAnimationStarted(viewHolder);
    // new pending animation added, return true to indicate we want a call to runPendingAnimations()
    return true;
}

@Override
public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
    dispatchAnimationFinished(viewHolder);
    return false;
}

@Override
public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
    if (preLayoutInfo.top != postLayoutInfo.top) {
        // required movement
        int topDiff = preLayoutInfo.top - postLayoutInfo.top;
        AnimInfo per = persistences.get(viewHolder);
        if(per != null && per.isRunning) {
            // there is already an ongoing animation - update it instead
            per.top = per.holder.itemView.getTranslationY() + topDiff;
            per.start();
            // discard this animatePersistence call
            dispatchAnimationFinished(viewHolder);
            return false;
        }
        pending.add(new AnimInfo(viewHolder, ANIMATION_TYPE_MOVE, topDiff));
        dispatchAnimationStarted(viewHolder);
        // new pending animation added, return true to indicate we want a call to runPendingAnimations()
        return true;
    }
    dispatchAnimationFinished(viewHolder);
    return false;
}

@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
    dispatchAnimationFinished(oldHolder);
    dispatchAnimationFinished(newHolder);
    return false;
}

@Override
public void runPendingAnimations() {
    for (AnimInfo ai: pending) {
        ai.start();
    }
    pending.clear();
}

@Override
public void endAnimation(RecyclerView.ViewHolder item) {
    AnimInfo ai = disappearances.get(item);
    if (ai != null && ai.isRunning) {
        ai.holder.itemView.animate().cancel();
    }
    ai = persistences.get(item);
    if (ai != null && ai.isRunning) {
        ai.holder.itemView.animate().cancel();
    }
}

@Override
public void endAnimations() {
    for (AnimInfo ai: disappearances.values())
        if (ai.isRunning)
            ai.holder.itemView.animate().cancel();

    for (AnimInfo ai: persistences.values())
        if (ai.isRunning)
            ai.holder.itemView.animate().cancel();
}

@Override
public boolean isRunning() {
    return !pending.isEmpty() &&
            !disappearances.isEmpty() &&
            !persistences.isEmpty();
}

/** 
 * This is container for each animation. It's also cancel/end listener for them.
 * */
private final class AnimInfo implements Animator.AnimatorListener {
    private final RecyclerView.ViewHolder holder;
    private final int animationType;
    private float top;
    private boolean isRunning = false;

    private AnimInfo(RecyclerView.ViewHolder holder, int animationType, float top) {
        this.holder = holder;
        this.animationType = animationType;
        this.top = top;
    }

    void start(){
        View itemView = holder.itemView;
        itemView.animate().setListener(this);
        switch (animationType) {
            case ANIMATION_TYPE_DISAPPEAR:
                itemView.setPivotY(0f);
                itemView.animate().scaleX(0f).scaleY(0f).setDuration(getRemoveDuration());
                disappearances.put(holder, this);   // must keep track of all animations
                break;
            case ANIMATION_TYPE_MOVE:
                itemView.setTranslationY(top);
                itemView.animate().translationY(0f).setDuration(getMoveDuration());
                persistences.put(holder, this);     // must keep track of all animations
                break;
        }
        isRunning = true;
    }

    private void resetViewHolderState(){
        // reset state as if no animation was ran
        switch (animationType) {
            case ANIMATION_TYPE_DISAPPEAR:
                holder.itemView.setScaleX(1f);
                holder.itemView.setScaleY(1f);
                break;
            case ANIMATION_TYPE_MOVE:
                holder.itemView.setTranslationY(0f);
                break;
        }
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        switch (animationType) {
            case ANIMATION_TYPE_DISAPPEAR:
                disappearances.remove(holder);
                break;
            case ANIMATION_TYPE_MOVE:
                persistences.remove(holder);
                break;
        }
        resetViewHolderState();
        holder.itemView.animate().setListener(null); // clear listener
        dispatchAnimationFinished(holder);
        if (!isRunning())
            dispatchAnimationsFinished();
        isRunning = false;
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        // jump to end state
        switch (animationType) {
            case ANIMATION_TYPE_DISAPPEAR:
                holder.itemView.setScaleX(0f);
                holder.itemView.setScaleY(0f);
                break;
            case ANIMATION_TYPE_MOVE:
                holder.itemView.setTranslationY(0f);
                break;
        }
    }

    @Override
    public void onAnimationStart(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }
}
}

您还可以覆盖 SimpleItemAnimator 解析<将code> animate ... 方法转换为 animateMove animateRemove 等.

You can also override SimpleItemAnimator class that parses animate... methods into animateMove, animateRemove etc. for you.

这篇关于正确覆盖RecyclerView动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆