Android Overdraw优化回收视图布局

Android Overdraw优化回收视图布局,android,android-recyclerview,Android,Android Recyclerview,因此,我有一个RecyclerView,它有多种视图类型,所有视图都具有不同的渲染背景。当然,我希望避免对所有这些组件进行透支,因此我在层次结构中提供了我的RecyclerView和所有视图,而没有任何背景 这一切都很好,直到我开始为输入和输出的项目设置动画为止。当然,DefaultItemAnimator很好地混合了项目的输入和输出,因此在RecyclerView中打开了一个“洞”,它的背景很快就可见了 好吧,我想,让我们试试——让我们在动画实际运行时只给RecyclerView一个背景,否则

因此,我有一个
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
内部执行下一个
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);
        }
    }
}
/**
*这是一个实用程序类,用于监视{@link recyclererview}的更改和临时更改
*为视图提供背景,以便在或中设置项目动画时不会看到任何瑕疵
*在视野之外,同时防止我们在
*给{@link recyclererview}一个永久的不透明背景色。
*
*托马斯·凯勒于2016年5月12日创作。
*/
公共类回收包地面保护器{
私人回收视图mRecyclerView;
@彩色
私人国际mBackgroundColor;
私有布尔值mAdapterChanged=false;
私有ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener
=新建ViewTreeObserver.OnGlobalLayoutListener(){
@凌驾
公共图书馆{
//忽略布局更改,直到适配器中发生实际更改
如果(!mAdapterChanged){
返回;
}
mRecyclerView.setBackgroundResource(mBackgroundColor);
//如果没有动画正在运行(实际上只有在
//我们在不设置任何动画的情况下更改适配器,如完整的数据集更改),
//也不要做任何事
如果(!mRecyclerView.getItemAnimator().isRunning()){
返回;
}
//删除此视图树观察者,即不要对视图的进一步布局更改作出反应
//同一个数据集发生更改,并将控制权授予ItemAnimatorFinishedListener
mrecycleView.getViewTreeObserver().removeOnGlobalLayoutListener(此);
mRecyclerView.getItemAnimator().isRunning(finishListener);
}
};
RecyclerView.ItemAnimator.ItemAnimatorFinishedListener finishListener
=新的RecyclerView.ItemAnimator.ItemAnimatorFinishedListener(){
@凌驾
AnimationsFinished()上的公共无效{
//动画结束后,重置适配器更改标志,以便开始下一次更改
//再次循环并重新添加布局更改侦听器
mRecyclerView.setBackgroundResource(0);
mAdapterChanged=false;
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
};
RecyclerView.AdapterDataObserver mAdapterDataObserver=新的RecyclerView.AdapterDataObserver(){
@凌驾
已插入的公用项目(int positionStart、int itemCount){
mAdapterChanged=true;
}
@凌驾
已删除的公共void(int positionStart,int itemCount){
mAdapterChanged=true;
}
@凌驾
已移动的公用文件夹(int-fromPosition、int-toPosition、int-itemCount){
mAdapterChanged=true;
}
};
公共回收站背景保护程序(回收站视图回收站视图,@ColorRes int backgroundColor){
mRecyclerView=回收视图;
mBackgroundColor=背景色;
}
/**
*启用背景保护程序,即对于下一项更改,RecyclerView的背景
*将临时设置为配置的背景色。
*/
公共作废启用(){
checkNotNull(mRecyclerView.getAdapter(),“RecyclerView尚未设置适配器”);
mRecyclerView.getAdapter().registerAdapterDataObserver(mAdapterDataObserver);
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
/**
*禁用背景保护程序,即用于下一个动画,
*回收视图的父背景将再次闪耀。
*/
公共无效禁用(){
mrecycleView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
if(mRecyclerView.getAdapter()!=null){
mRecyclerView.getAdapter().unregisterAdapterDataObserver(mAdapterDataObserver);
}
}
}