Overdraw-优化RecyclerView布局

时间:2016-05-12 10:25:21

标签: android android-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);
        }
    }
);

这里的问题是适配器的范围回调比实际动画运行的方式早,因为动画不会在requestLayout()内部发生下一个RecyclerView之前安排,即{{我的recycler.getItemAnimator().isRunning()方法中的1}}始终返回start(),因此永远不会删除白色背景。

所以,在我开始尝试使用其他false并将其纳入混合之前 - 是否有人找到了解决此问题的正确,有效(更容易?)的解决方案?

1 个答案:

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