Reputation: 446
so basically this question already exists on the web: How do I keep my RecyclerView
scrolling to the bottom with new items? Almost always you will find the answer: add stackFromEnd=true
or reverseLayout=true
. And in my case I tried every possible combination of the two and it just didn't work. I also saw solutions like this:
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
//[email protected](viewAdapter.itemCount - 1)
}
})
which also didn't help me. First I will describe my setup and then the expected behaviour and current behaviour:
I have an xml that looks like this:
<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>
The important view is the chat_messages_list RecyclerView
which is my RecyclerView
whose implementation follows (removed some things for simplicity):
@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)
//[email protected](0)
}
})
registerTypes(viewAdapter)
adapter = viewAdapter
addItemDecoration(getDecoration())
setPadding(0, 6.toPx(), 0, 6.toPx())
clipToPadding = false
}
}
Further I can tell you that my adapter definitly calls the correct notifyItemRangeInserted
, ... calls.
Expected behaviour:
The RecyclerView
should start with the first item at the bottom of the screen and always if a new one is added it should be added at the bottom and scrolled to. In the end the new items at the bottom should look like they are pushed in from the bottom.
Current behaviour:
The items stack from the bottom and until the visible space inside the RecyclerView
is not full everything is animated beautifully, but as soon as the visible space is full, items are added at the bottom but not scrolled to. As some would expect the functionality I want to achive is the one of a chat.
Further notes:
If I add [email protected](viewAdapter.itemCount - 1)
to the onItemRangeInserted
callback it goes to the bottom but without a animation and it just jumps there.
Upvotes: 0
Views: 469
Reputation: 446
so I kind of figured it out myself. I still don't know why particular strategies work and some do not work at all.
I set reverseLayout=true
on the LinearLayoutManager
(which means I had to reverse my items in the list) and then I added an onItemRangeInserted
listener like this:
layoutManager = object : LinearLayoutManager(context) {
init {
reverseLayout = true
}
}
viewAdapter.registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0) {
layoutManager?.scrollToPosition(0)
}
}
})
Upvotes: 1