如何让RecyclerView始终滚动到底部

42

我使用Recyclerview替换了ListView,但我希望保持Recyclerview始终滚动到底部。

ListView可以使用此方法setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL)

对于RecyclerView, 我使用方法smoothScrollToPosition(myAdapter.getItemCount() - 1)

但当软键盘弹出时,它会替换掉RecyclerView的内容。


你最终解决了这个问题吗?我发现将setReverseLayout设置为true会导致项目从上到下显示,这对于聊天来说不太合适。 - CodeAlien
你可以尝试我的答案:https://dev59.com/Cofca4cB1Zd3GeqPhE8p#43647835 - DysaniazzZ
7个回答

50
如果你想将滚动位置固定在RecyclerView底部,这在聊天应用程序中非常有用,只需在LinearLayoutManager上调用setStackFromEnd(true),就可以使键盘将列表项固定在底部(键盘)而不是顶部。

2
只对我第一次有效...是的,我正在开发一个聊天应用。 - prateek31
这个有效。在 XML 文件中设置它没有起作用。 - Samuel Robert
1
@larrytech 从答案中不清楚应该在LinearLayoutManager上调用吗? - Kirill Kulakov
它不起作用。它被反转了,但是它没有滚动到底部。 - User

37
这是因为RV认为其参考点是TOP,当键盘出现时,RV的大小会被父级更新,而RV会保持其参考点稳定(从而使顶部位置保持在相同位置)。您可以将LayoutManager#ReverseLayout设置为true,这样RV将从适配器末尾布局项目。例如:适配器位置0位于底部,1位于其上方等等... 当然,这需要您反转适配器的顺序。我不确定,但设置堆栈从结尾开始也可能会给您相同的结果,而无需重新排序适配器。

谢谢,你解决了我的问题。我使用了reverseLayout参数。 - lj wang
17
布局内容被反转,但列表没有滚动到末尾。即使使用stackFromBottom也没有帮助。 - Codevalley
只是补充一点:有时在XML中设置stackFromEnd不起作用,而在代码中调用该方法可以。 - guness
1
如果您同时设置stackFromEnd=true和setReverseLayout=true,它将无法正常工作。有人知道任何解决方法吗? - Luccas Correa
1
mLinearLayoutManager.setStackFromEnd(true); 对于解决键盘打开时的recyclerView问题真的很有帮助。谢谢! :) - Meet Vora
显示剩余3条评论

6
recyclerView.scrollToPosition(getAdapter().getItemCount()-1);

1
当用户向上滚动一点并希望保持相同的最后可见项时,这种情况效果不佳。 - John Pang

5
我曾经遇到过同样的问题,我使用了这里提到的方法来解决它。该方法用于检测软键盘是否已打开,如果已打开,则只需调用smoothScrollToPosition()方法即可。

一个更简单的解决方案是为您的活动根视图添加一个已知ID,比如“@+id/activityRoot”,将GlobalLayoutListener钩入ViewTreeObserver,从那里计算您的活动视图根与窗口大小之间的大小差异:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
    int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
    if (heightDiff > 100) { 
        recyclerView.smoothScrollToPosition(myAdapter.getItemCount() - 1);
    }
 }
});

简单易懂!


1
这是本页面上所有答案中最好的解决方案,但是在实现代码时,请确保检查 myAdapter.getItemCount() > 0。否则,当列表中没有项目时启动应用程序时,它将崩溃,因为它尝试滚动到位置-1。 - Someone Somewhere

1

我也遇到了同样的问题。但是以下代码帮助了我。希望这对你有用。

在这里,状态是arraylist。

 recyclerView.scrollToPosition(staus.size()-1);

下一个是: 在这个中,你可以使用适配器类。
   recyclerView.scrollToPosition(showAdapter.getItemCount()-1);

0

我自己也遇到了这个问题,最终我创建了自己的LayoutManager来解决它。这是一个相当简单的解决方案,可以分为三个步骤:

  1. stackFromEnd设置为true。

  2. 确定是否应该在每次调用onItemsChanged时将forceTranscriptScroll设置为true。根据文档,当适配器的内容发生更改时,会调用onItemsChanged。如果transcriptMode设置为Disabled,则forceTranscriptScroll始终为false;如果设置为AlwaysScroll,则始终为true;如果设置为Normal,则仅在适配器中的最后一个项目完全可见时才为true。

  3. onLayoutCompleted中,如果forceTranscriptScroll设置为true且列表中的最后一个项目尚未完全可见,则滚动到列表中的最后一个项目。

以下是实现这三个步骤的代码:

import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class TranscriptEnabledLinearLayoutManager(context: Context, transcriptMode: TranscriptMode = TranscriptMode.Normal) :
    LinearLayoutManager(context) {

    enum class TranscriptMode {
        Disabled, Normal, AlwaysScroll
    }

    private var transcriptMode: TranscriptMode = TranscriptMode.Disabled
        set(value) {
            field = value
            // Step 1
            stackFromEnd = field != TranscriptMode.Disabled
        }

    private var forceTranscriptScroll = false

    init {
        this.transcriptMode = transcriptMode
    }

    // Step 2
    override fun onItemsChanged(recyclerView: RecyclerView) {
        super.onItemsChanged(recyclerView)
        forceTranscriptScroll = when (transcriptMode) {
            TranscriptMode.Disabled -> false
            TranscriptMode.Normal -> {
                findLastCompletelyVisibleItemPosition() == itemCount - 1
            }
            TranscriptMode.AlwaysScroll -> true
        }
    }

    // Step 3
    override fun onLayoutCompleted(state: RecyclerView.State?) {
        super.onLayoutCompleted(state)
        val recyclerViewState = state ?: return

        if (!recyclerViewState.isPreLayout && forceTranscriptScroll) {
            // gets the position of the last item in the list. returns if list is empty
            val lastAdapterItemPosition = recyclerViewState.itemCount.takeIf { it > 0 }
                ?.minus(1) ?: return
            val lastCompletelyVisibleItem = findLastCompletelyVisibleItemPosition()
            if (lastCompletelyVisibleItem != lastAdapterItemPosition ||
                recyclerViewState.targetScrollPosition != lastAdapterItemPosition) {
                scrollToPositionWithOffset(lastAdapterItemPosition, 0)
            }
            forceTranscriptScroll = false
        }
    }
}

0

虽然这个问题有点老了,似乎没有一个正确的答案。我认为你在这里需要做的是监听适配器的变化,然后在每次检测到变化时滚动到底部。

这是一个可行的示例

yourAdapter?.registerAdapterDataObserver(object : AdapterDataObserver() {
    override fun onChanged() {
        yourRecyclerView?.smoothScrollToPosition(yourAdapter?.itemCount ?: 0)
    }
})

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