正确覆盖 RecyclerView 动画

0x2*_*29a 4 android android-recyclerview

我有一个 RecycleView,其中显示了一个项目列表。我为 RecyclerView 指定默认动画师,如下所示:

recyclerView.setItemAnimator( new DefaultItemAnimator() );
Run Code Online (Sandbox Code Playgroud)

一切都很好,但我想使用我自己的自定义动画来添加/删除/更新列表中的元素。

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

    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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并像我为 DefaultItemAnimator 所做的那样设置它。动画不再播放,所以我想它有效,但问题是有时项目会堆叠在一起,当我删除所有项目时,有些仍然存在,所以我想我错过了一些东西。

据我了解 animateDisappearance 是一种在从列表中删除项目时被调用的方法。如果我返回false,就我的理解,它应该简单地跳过动画,对吗?

我是否在正确的轨道上?当我在 github 上寻找这样的例子时,结果很少,总的来说我似乎找不到任何基本的代码示例如何做到这一点,而我找到的都是数千行代码。

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

编辑:

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

        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;
            }
        } );
Run Code Online (Sandbox Code Playgroud)

动画完美运行,但由于某种原因,“dispatchRemoveFinished”似乎是立即触发的,因此,不是在动画之后调整剩余元素,而是在视图移除后立即执行。有没有什么办法解决这一问题?

Paw*_*wel 5

在实现你的时候,RecyclerView.ItemAnimator你必须遵循一些规则,否则 RecyclerView 状态会变得一团糟:

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

  2. 如果这些方法要开始动画,您应该dispatchAnimationStarted(viewHolder)存储动画请求并返回 true 以调用runPendingAnimations()动画实际开始的位置。

  3. 您需要跟踪正在进行的动画才能正确取消它们,您也会收到对已经动画的项目的请求。

这是一个ItemAnimator仅对移除和移动进行动画处理的示例。注意作为动画数据持有者和动画状态监听器的内部类:

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) {

    }
}
}
Run Code Online (Sandbox Code Playgroud)

您还可以覆盖SimpleItemAnimator来解析类的animate...方法为animateMoveanimateRemove等你。