当我用适配器设置 RecyclerView 完成显示其项目时,是否有回调?

Is there a callback for when RecyclerView has finished showing its items after I've set it with an adapter?

背景

我制作了一个显示 RecyclerView 快速滚动条的库(here,以防万一),我想决定何时显示以及何时隐藏快速滚动条。

我认为一个不错的决定是,如果有项目没有显示在屏幕上(或者有很多项目没有出现),在 RecyclerView 完成其布局过程后,我将设置快速滚动条可见,如果所有项目都已显示,则无需显示。

问题

我找不到 RecyclerView 的 listener/callback 来告诉我它何时显示完项目,以便我可以检查显示的项目数与项目数相比。

当键盘出现和隐藏时,recyclerView 也可能改变它的大小。

我试过的

滚动侦听器可能无济于事,因为它发生了 "all the time",我只需要检查 RecyclerView 何时更改其大小或项目计数(或数据)何时更改。

我可以用一个布局来包装 RecyclerView,通知我尺寸的变化,比如我所做的 this one,但我认为它不会起作用,因为 RecyclerView 可能还没有准备好还不知道有多少项目是可见的。

检查显示的项目数量的方法可以这样使用:

    final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
    mRecyclerView.setLayoutManager(layoutManager);
    ...
    Log.d("AppLog", "visible items count:" + (layoutManager.findLastVisibleItemPosition() -layoutManager.findFirstVisibleItemPosition()+1));

问题

我如何在 recyclerView 显示完其子视图时收到通知,以便我可以根据当前显示的内容来决定 show/hide 快速滚动器?

我找到了解决这个问题的方法(感谢用户 pskink),方法是使用 LayoutManager 的回调:

final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false) {
            @Override
            public void onLayoutChildren(final Recycler recycler, final State state) {
                super.onLayoutChildren(recycler, state);
                //TODO if the items are filtered, considered hiding the fast scroller here
                final int firstVisibleItemPosition = findFirstVisibleItemPosition();
                if (firstVisibleItemPosition != 0) {
                    // this avoids trying to handle un-needed calls
                    if (firstVisibleItemPosition == -1)
                        //not initialized, or no items shown, so hide fast-scroller
                        mFastScroller.setVisibility(View.GONE);
                    return;
                }
                final int lastVisibleItemPosition = findLastVisibleItemPosition();
                int itemsShown = lastVisibleItemPosition - firstVisibleItemPosition + 1;
                //if all items are shown, hide the fast-scroller
                mFastScroller.setVisibility(mAdapter.getItemCount() > itemsShown ? View.VISIBLE : View.GONE);
            }
        };

这里的好处是它运行良好,甚至可以处理键盘 shown/hidden。

不好的是它会在不感兴趣的情况下被调用(意味着它有误报),但它不像滚动事件那么频繁,所以对我来说已经足够了。


编辑:后来添加了一个更好的回调,它不会被多次调用。这是新代码,而不是我上面写的代码:

        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false) {
            @Override
            public void onLayoutCompleted(final State state) {
                super.onLayoutCompleted(state);
                final int firstVisibleItemPosition = findFirstVisibleItemPosition();
                final int lastVisibleItemPosition = findLastVisibleItemPosition();
                int itemsShown = lastVisibleItemPosition - firstVisibleItemPosition + 1;
                //if all items are shown, hide the fast-scroller
                fastScroller.setVisibility(adapter.getItemCount() > itemsShown ? View.VISIBLE : View.GONE);
            }
        });

我为此使用 'addOnGlobalLayoutListener'。这是我的例子:

定义一个接口来执行加载后所需的操作:

public interface RecyclerViewReadyCallback {
  void onLayoutReady();
}

在 RecyclerView 上,我在加载准备就绪时触发了 onLayoutReady 方法:

mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
  if (recyclerViewReadyCallback != null) {
    recyclerViewReadyCallback.onLayoutReady();
  }
  recyclerViewReadyCallback = null;
});

注意:设置为null是为了防止方法被多次调用。

将此留在这里作为替代方法。在某些情况下可能有用。您还可以使用 LinearLayoutManagers onScrollStateChanged() 并检查滚动条何时空闲。

有一点要记住,当你第一次加载你的视图时,它不会被调用,只有当用户开始滚动并且滚动完成时,才会被触发。

LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(),
                RecyclerView.HORIZONTAL, false) {

            @Override
            public void onScrollStateChanged(int state) {
                super.onScrollStateChanged(state);

                if (state == RecyclerView.SCROLL_STATE_IDLE) {
                        // your logic goes here
                    }
                }
            }
        };

适合我的解决方案。在使用项目初始化 RecyclerView 后需要做一些事情。

adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
    override fun onChanged() {
        viewModel.onListReady()
        adapter.unregisterAdapterDataObserver(this)
    }
})