RecyclerView 应该与 stackFromEnd 保持在底部

RecyclerView should stay at bottom with stackFromEnd

所以网络上基本上已经存在这个问题:如何让 RecyclerView 滚动到带有新项目的底部?几乎总能找到答案:添加 stackFromEnd=truereverseLayout=true。就我而言,我尝试了两者的所有可能组合,但都没有用。我也看到了这样的解决方案:

    viewAdapter.registerAdapterDataObserver(object : AdapterDataObserver() {
        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
            //(layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(viewAdapter.itemCount - 1, 0) or
            //(layoutManager as? LinearLayoutManager)?.smoothScrollToPosition(this@ChatMessagesList, null, viewAdapter.itemCount - 1) or
            //this@ChatMessagesList.smoothScrollToPosition(viewAdapter.itemCount - 1)
        }
    })

这对我也没有帮助。首先,我将描述我的设置,然后是预期行为和当前行为:

我有一个 xml 看起来像这样:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">

    <ImageView
        android:id="@+id/backgroundImage"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:contentDescription="@string/image_image_description"
        android:scaleType="centerCrop"
        android:src="@drawable/chat_background"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/overlay"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#CCFFFFFF"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <app.jooy.messenger.ui.components.generic.chat.chat_header.ChatHeader
        android:id="@+id/chat_header"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <app.jooy.messenger.ui.components.generic.chat.chat_message_list.ChatMessagesList
        android:id="@+id/chat_messages_list"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/chat_options_chooser"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/chat_header" />

    <app.jooy.messenger.ui.components.generic.chat.chat_options_chooser.ChatOptionsChooser
        android:id="@+id/chat_options_chooser"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="@id/chat_emoji_bar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <app.jooy.messenger.ui.components.generic.chat.chat_emoji_bar.ChatEmojiBar
        android:id="@+id/chat_emoji_bar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="@id/chat_action_bar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <app.jooy.messenger.ui.components.generic.chat.chat_action_bar.ChatActionBar
        android:id="@+id/chat_action_bar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/colorActionBarBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</merge>

重要的观点是 chat_messages_list RecyclerView 这是我的 RecyclerView 其实现如下(为简单起见删除了一些东西):

@ExperimentalCoroutinesApi
@ReactiveComponent
class ChatMessagesList @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ReactiveRecyclerView<ChatMessagesList.ViewState, ChatMessagesList.ViewAction, ChatMessagesList.ViewStructure>(
    ViewState(),
    context,
    attrs,
    defStyleAttr
) {

    private val viewAdapter = ChatMessagesListAdapter()

    init {

        setHasFixedSize(true)
        layoutManager = object : LinearLayoutManager(context) {
            init {
                stackFromEnd = true
            }
        }
        viewAdapter.registerAdapterDataObserver(object : AdapterDataObserver() {
            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
                //(layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(0, 0)
                //(layoutManager as? LinearLayoutManager)?.smoothScrollToPosition(this@ChatMessagesList, null, viewAdapter.itemCount - 1)
                //this@ChatMessagesList.smoothScrollToPosition(0)
            }
        })

        registerTypes(viewAdapter)
        adapter = viewAdapter

        addItemDecoration(getDecoration())

        setPadding(0, 6.toPx(), 0, 6.toPx())
        clipToPadding = false

    }

}

此外,我可以告诉您,我的适配器肯定会调用正确的 notifyItemRangeInserted,...调用。

预期行为: RecyclerView 应该从屏幕底部的第一个项目开始,如果添加了一个新项目,它应该总是添加到底部并滚动到。最后底部的新项目应该看起来像是从底部推入的。

当前行为: 项目从底部开始堆叠,直到 RecyclerView 内的可见 space 未满,一切都以精美的动画呈现,但一旦可见 space 已满,项目就会添加到底部但没有滚动到。正如某些人所期望的那样,我想要实现的功能是聊天之一。

进一步说明: 如果我将 this@ChatMessagesList.smoothScrollToPosition(viewAdapter.itemCount - 1) 添加到 onItemRangeInserted 回调,它会转到底部但没有动画,它只是跳到那里。

所以我自己想通了。我仍然不知道为什么某些策略有效而有些策略根本无效。

我在 LinearLayoutManager 上设置了 reverseLayout=true(这意味着我必须在列表中反转我的项目)然后我添加了一个 onItemRangeInserted 侦听器,如下所示:

    layoutManager = object : LinearLayoutManager(context) {
        init {
            reverseLayout = true
        }
    }
    viewAdapter.registerAdapterDataObserver(object : AdapterDataObserver() {
        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
            if (positionStart == 0) {
                layoutManager?.scrollToPosition(0)
            }
        }
    })