所以我有一个RecyclerView
,它有多个视图类型,它们都有不同的渲染背景。当然,我想避免对所有这些组件进行过度透支,所以我将RecyclerView
和层次结构中的所有视图都没有背景。
这可以正常工作,直到我开始设置项目的动画。当然,DefaultItemAnimator
很好地融合了项目的输入和输出,因此在RecyclerView
中打开了一个"洞",它的背景很快就会变得可见。
好吧,我想,让我们尝试一下——让我们在动画实际运行时只给RecyclerView
一个背景,但在其他情况下删除背景,这样滚动在高FPS速率下可以顺利进行。然而,这实际上比我最初想象的要困难,因为在RecyclerView
和ItemAnimator
或相关类中都没有特定的"动画将开始"和相应的"动画结束"信号。
我最近尝试将AdapterDataObserver
和ItemAnimatorFinishedListener
像这样结合起来,但没有成功:
RecyclerView.ItemAnimator.ItemAnimatorFinishedListener finishListener =
new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
@Override
public void onAnimationsFinished() {
recycler.setBackgroundResource(0);
}
};
recycler.getAdapter().registerAdapterDataObserver(
new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
start();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
start();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
start();
}
private void start() {
recycler.setBackgroundResource(R.color.white);
if (!recycler.getItemAnimator().isRunning()) {
return;
}
recycler.getItemAnimator().isRunning(finishListener);
}
}
);
这里的问题是,适配器的范围回调运行得比实际动画运行得早得多,因为在RecyclerView
内部发生下一个requestLayout()
之前不会调度动画,即我的start()
方法中的recycler.getItemAnimator().isRunning()
总是返回false
,因此永远不会删除白色背景。
因此,在我开始试验额外的ViewTreeObserver.OnGlobalLayoutListener
并将其纳入混合之前,有人找到了解决这个问题的合适、有效(更容易?!)的解决方案吗?
好的,我走得更远,包括了一个ViewTreeObserver.OnGlobalLayoutListener
——这似乎很有效:
/**
* This is a utility class that monitors a {@link RecyclerView} for changes and temporarily
* gives the view a background so we do not see any artifacts while items are animated in or
* out of the view, and, at the same time prevent the overdraw that would occur when we'd
* give the {@link RecyclerView} a permanent opaque background color.
* <p>
* Created by Thomas Keller <me@thomaskeller.biz> on 12.05.16.
*/
public class RecyclerBackgroundSaver {
private RecyclerView mRecyclerView;
@ColorRes
private int mBackgroundColor;
private boolean mAdapterChanged = false;
private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener
= new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// ignore layout changes until something actually changed in the adapter
if (!mAdapterChanged) {
return;
}
mRecyclerView.setBackgroundResource(mBackgroundColor);
// if no animation is running (which should actually only be the case if
// we change the adapter without animating anything, like complete dataset changes),
// do not do anything either
if (!mRecyclerView.getItemAnimator().isRunning()) {
return;
}
// remove this view tree observer, i.e. do not react on further layout changes for
// one and the same dataset change and give control to the ItemAnimatorFinishedListener
mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
mRecyclerView.getItemAnimator().isRunning(finishListener);
}
};
RecyclerView.ItemAnimator.ItemAnimatorFinishedListener finishListener
= new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
@Override
public void onAnimationsFinished() {
// the animation ended, reset the adapter changed flag so the next change kicks off
// the cycle again and add the layout change listener back
mRecyclerView.setBackgroundResource(0);
mAdapterChanged = false;
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
};
RecyclerView.AdapterDataObserver mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mAdapterChanged = true;
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mAdapterChanged = true;
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
mAdapterChanged = true;
}
};
public RecyclerBackgroundSaver(RecyclerView recyclerView, @ColorRes int backgroundColor) {
mRecyclerView = recyclerView;
mBackgroundColor = backgroundColor;
}
/**
* Enables the background saver, i.e for the next item change, the RecyclerView's background
* will be temporarily set to the configured background color.
*/
public void enable() {
checkNotNull(mRecyclerView.getAdapter(), "RecyclerView has no adapter set, yet");
mRecyclerView.getAdapter().registerAdapterDataObserver(mAdapterDataObserver);
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
/**
* Disables the background saver, i.e. for the next animation,
* the RecyclerView's parent background will again shine through.
*/
public void disable() {
mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
if (mRecyclerView.getAdapter() != null) {
mRecyclerView.getAdapter().unregisterAdapterDataObserver(mAdapterDataObserver);
}
}
}