Overdrive优化RecyclerView布局



所以我有一个RecyclerView,它有多个视图类型,它们都有不同的渲染背景。当然,我想避免对所有这些组件进行过度透支,所以我将RecyclerView和层次结构中的所有视图都没有背景。

这可以正常工作,直到我开始设置项目的动画。当然,DefaultItemAnimator很好地融合了项目的输入和输出,因此在RecyclerView中打开了一个"洞",它的背景很快就会变得可见。

好吧,我想,让我们尝试一下——让我们在动画实际运行时只给RecyclerView一个背景,但在其他情况下删除背景,这样滚动在高FPS速率下可以顺利进行。然而,这实际上比我最初想象的要困难,因为在RecyclerViewItemAnimator或相关类中都没有特定的"动画将开始"和相应的"动画结束"信号。

我最近尝试将AdapterDataObserverItemAnimatorFinishedListener像这样结合起来,但没有成功:

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);
        }
    }
}

最新更新