在Android的RecyclerView中检测动画完成

Rom*_*nko 32 animation android android-recyclerview

RecyclerView,不像到ListView,没有一个简单的方法来设置空视图,所以人们必须手动管理它,使空视图在适配器的项目数的情况下,可见是0.

实现这一点,起初我尝试在修改底层结构(ArrayList在我的情况下)后立即调用空视图逻辑,例如:

btnRemoveFirst.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        devices.remove(0); // remove item from ArrayList
        adapter.notifyItemRemoved(0); // notify RecyclerView's adapter
        updateEmptyView();
    }
});
Run Code Online (Sandbox Code Playgroud)

它做了这件事,但有一个缺点:当删除最后一个元素时,在删除动画完成之前立即显示空视图.所以我决定等到动画结束然后更新UI.

令我惊讶的是,我找不到在RecyclerView中监听动画事件的好方法.首先想到的是使用这样的isRunning方法:

btnRemoveFirst.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        devices.remove(0); // remove item from ArrayList
        adapter.notifyItemRemoved(0); // notify RecyclerView's adapter
        recyclerView.getItemAnimator().isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
            @Override
            public void onAnimationsFinished() {
                updateEmptyView();
            }
        });
    }
});
Run Code Online (Sandbox Code Playgroud)

不幸的是,在这种情况下回调立即运行,因为在那一刻内部ItemAnimator仍然没有处于"运行"状态.所以,问题是:如何正确使用ItemAnimator.isRunning()方法,是否有更好的方法来实现所需的结果,即在单个元素的删除动画完成后显示空视图

Rom*_*nko 19

目前我发现解决这个问题的唯一工作方式是扩展ItemAnimator并传递给它RecyclerView:

recyclerView.setItemAnimator(new DefaultItemAnimator() {
    @Override
    public void onAnimationFinished(RecyclerView.ViewHolder viewHolder) {
        updateEmptyView();
    }
});
Run Code Online (Sandbox Code Playgroud)

但是这种技术并不普遍,因为我必须从ItemAnimator正在使用的具体实现扩展RecyclerView.在CoolItemAnimator内部私有的情况下CoolRecyclerView,我的方法根本不起作用.


PS:我的同事建议包裹ItemAnimator装饰以下列方式:

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

这将是很好,尽管看起来矫枉过正这样一个简单的任务,但在这种情况下产生的装饰是不可能的,无论如何,是因为ItemAnimator有一个方法setListener()是包保护的,所以我显然不能把它包装,还有一些最后的方法.


nib*_*ius 12

我有一个更通用的案例,我想要检测回收器视图何时完全动画完成,同时删除或添加一个或多个项目.

我尝试过Roman Petrenko的答案,但在这种情况下它不起作用.问题是onAnimationFinished在回收器视图中为每个条目调用.大多数条目没有改变,因此onAnimationFinished或多或少地被称为瞬时.但是对于添加和删除动画需要一点时间,因此稍后会调用它.

这导致至少两个问题.假设您有一个doStuff()在动画完成时要运行的方法.

  1. 如果您只是打电话doStuff(),onAnimationFinished您将为回收站视图中的每个项目调用一次,这可能不是您想要做的.

  2. 如果你doStuff()第一次onAnimationFinished打电话,你可能会在最后一个动画完成之前很久就打电话.

如果您可以知道要动画的项目数量,您可以确保doStuff()在上一个动画结束时调用.但我还没有找到任何方法知道有多少剩余的动画排队等候.

我对此问题的解决方案是让回收器视图首先通过使用启动动画new Handler().post(),然后设置一个侦听器isRunning(),并在动画准备好时调用该侦听器.之后,它重复该过程,直到所有视图都被动画化.

void changeAdapterData() {
    // ...
    // Changes are made to the data held by the adapter
    recyclerView.getAdapter().notifyDataSetChanged();

    // The recycler view have not started animating yet, so post a message to the
    // message queue that will be run after the recycler view have started animating.
    new Handler().post(waitForAnimationsToFinishRunnable);
}

private Runnable waitForAnimationsToFinishRunnable = new Runnable() {
    @Override
    public void run() {
        waitForAnimationsToFinish();
    }
};

// When the data in the recycler view is changed all views are animated. If the
// recycler view is animating, this method sets up a listener that is called when the
// current animation finishes. The listener will call this method again once the
// animation is done.
private void waitForAnimationsToFinish() {
    if (recyclerView.isAnimating()) {
        // The recycler view is still animating, try again when the animation has finished.
        recyclerView.getItemAnimator().isRunning(animationFinishedListener);
        return;
    }

    // The recycler view have animated all it's views
    onRecyclerViewAnimationsFinished();
}

// Listener that is called whenever the recycler view have finished animating one view.
private RecyclerView.ItemAnimator.ItemAnimatorFinishedListener animationFinishedListener =
        new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
    @Override
    public void onAnimationsFinished() {
        // The current animation have finished and there is currently no animation running,
        // but there might still be more items that will be animated after this method returns.
        // Post a message to the message queue for checking if there are any more
        // animations running.
        new Handler().post(waitForAnimationsToFinishRunnable);
    }
};

// The recycler view is done animating, it's now time to doStuff().
private void onRecyclerViewAnimationsFinished() {
    doStuff();
}
Run Code Online (Sandbox Code Playgroud)