RecyclerView设置适配器后,是否有回调可以在其完成显示所有项时使用?

64

背景

我做了一个可以在RecyclerView上展示快速滚动条的库,(这里,如果有人需要的话),我想要决定何时显示和隐藏快速滚动条。

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

问题

我找不到RecyclerView的监听器/回调函数来告诉我它何时完成显示项目,以便我可以检查显示的项目数与项目总数相比有多少。

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

我尝试过的

滚动监听器可能没有用,因为它会一直发生,并且我只需要在RecyclerView更改其大小或项计数(或数据)更改时进行检查。

我可以使用一个通知我大小更改的布局来包装RecyclerView,例如这个,但我认为它不会起作用,因为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显示其子视图完成后得到通知,以便根据当前显示的内容决定是否显示/隐藏快速滚动条?


@KrupalShah 我认为它只有在数据发生更改时才会被调用,而不是在视图显示后告诉我。不幸的是,我没有看到相关文档,所以我现在已经测试过了,但它并没有帮助(显示第一项和最后一项都是“-1”),可能是因为RecyclerView是ViewPager中的一个片段的一部分。然而,当当前页面使用此功能时,我首先得到一个错误的结果,然后得到一个正确的结果(因为我确实需要在页面显示后很快更改数据)。也许我可以在更改页面时检查项目,并使用你写的内容。 - android developer
3
RecyclerView.LayoutManager#onLayoutChildren:RecyclerView的布局管理器方法,用于排列和定位视图中的子项。 - pskink
@pskink 这几乎是完美的。每次需要时它都会被调用,但在滚动时它也会被调用(但出于某种原因,在滚动时停止被调用)。也许这已经足够好了。现在给你一个+1。 - android developer
@pskink 好的,既然我找不到更好的解决方案,那我就展示如何使用你的方法。虽然它被调用的次数比我需要的多,但它并不像滚动那样频繁发生,而且即使键盘显示时也会发生(这很棒)。 - android developer
1
RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State state): - ANemati
显示剩余4条评论
4个回答

59

我已经找到了解决方法(感谢用户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);
    }
};

这里的好处是它运行良好,即使键盘显示/隐藏也可以处理。

坏处是它在不感兴趣的情况下被调用(意味着它有假阳性),但这种情况不如滚动事件那么频繁,所以对我来说已经足够好了。


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

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

1
基本假设 mAdapter.getItemCount() > itemsShown 对我来说似乎是错误的。如果屏幕可以显示更多的项目怎么办?例如,mAdapter.getItemCount() 是40,最大可显示的项目数为10。因此,mAdapter.getItemCount() > itemsShown 总是成立的。如果我错了,请纠正我。 - Phate P
@PhateeP 这就是我们的想法。如果有太多的项目需要显示,我会显示快速滚动条,否则我会隐藏它。 - android developer
这正是我需要的,当用户更改项目的排序顺序时,自然而然地回到列表顶部会感觉很自然。使用这种解决方案,滚动是无缝的,比在类似问题的答案中建议的数据提交后延迟滚动的短定时器要好得多。 - Ronan
1
@androiddeveloper,使用onLayoutCompleted(State state))不是更好的选择吗?因为它只会被调用一次,而不像你使用的那个方法会被多次调用。 - Sufian
@Sufian,我想这个问题是在23.1.1版本时提出的。你提供的是24.1.0版本。当我提问时它还没有可用。不管怎样,谢谢。我已经更新了存储库的代码,看起来运行良好。同时也更新了答案。 - android developer

1
我将使用 'addOnGlobalLayoutListener',以下是我的示例:
定义一个接口来执行加载后所需的操作:
public interface RecyclerViewReadyCallback {
  void onLayoutReady();
}

在RecyclerView上,当加载完成时,我会触发onLayoutReady方法:
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
  if (recyclerViewReadyCallback != null) {
    recyclerViewReadyCallback.onLayoutReady();
  }
  recyclerViewReadyCallback = null;
});

注意:将其设置为null是必要的,以防止方法被多次调用。

我认为这不会起作用。首先,在第一次运行后,它会设置空值。即使您修复了这个问题,当RecyclerView更改其项目数量时,甚至降至零时,快速滚动条应该消失的下一次调用也不会发生。 - android developer
1
它不起作用,布局可能在适配器填充之前就完成了。此外,使用removeOnGlobalLayoutListener而不是hack recyclerViewReadyCallback = null - RedGlyph

0

将此作为另一种方法留在这里。在某些情况下可能会有用。您还可以利用LinearLayoutManageronScrollStateChanged(),并在滚动空闲时进行检查。

记住一件事,当您首次加载视图时,不会调用此函数,只有当用户开始滚动并且滚动完成时,才会触发此函数。

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

那么它什么时候有用呢?它将被调用多次,但第一次需要对其进行调整时,它不会被调用... - android developer
例如,当您有一个仅显示一个项目的回收站视图,并且您想要滚动完成后的位置时,可以使用 yourRecyclerView.getLayoutManager().findFirstVisibleItemPosition(); - Francois
我猜你已经满足了这个需求。 :) - android developer

0
我的解决方案。在RecyclerView初始化项目后需要执行一些操作。
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
    override fun onChanged() {
        viewModel.onListReady()
        adapter.unregisterAdapterDataObserver(this)
    }
})

1
对我来说它不起作用。 - mazend

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接