所以我有一个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);
}
}
);
这里的问题是适配器的范围回调比实际动画运行的方式早,因为动画不会在requestLayout()
内部发生下一个RecyclerView
之前安排,即{{我的recycler.getItemAnimator().isRunning()
方法中的1}}始终返回start()
,因此永远不会删除白色背景。
所以,在我开始尝试使用其他false
并将其纳入混合之前 - 是否有人找到了解决此问题的正确,有效(更容易?)的解决方案?
答案 0 :(得分:2)
好的,我走得更远,包括一个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);
}
}
}