mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Fix CFv2 initial scrolling bugs.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package androidx.recyclerview.widget
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
/**
|
||||
* Variation of a vertical, reversed [LinearLayoutManager] that makes specific assumptions in how it will
|
||||
* be used by Conversation view to support easier scrolling to the initial start position.
|
||||
*
|
||||
* Primarily, it assumes that an initial scroll to position call will always happen and that the implementation
|
||||
* of [LinearLayoutManager] remains unchanged with respect to how it assigns [mPendingScrollPosition] and
|
||||
* [mPendingScrollPositionOffset] in [LinearLayoutManager.scrollToPositionWithOffset] and how it always clears
|
||||
* the pending state variables in every call to [LinearLayoutManager.onLayoutCompleted].
|
||||
*
|
||||
* The assumptions are necessary to force the requested scroll position/layout to occur even if the request
|
||||
* happens prior to the data source populating the recycler view/adapter.
|
||||
*/
|
||||
class ConversationLayoutManager(context: Context) : LinearLayoutManager(context, RecyclerView.VERTICAL, true) {
|
||||
|
||||
private var afterScroll: (() -> Unit)? = null
|
||||
|
||||
override fun supportsPredictiveItemAnimations(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to the desired position and be notified when the layout manager has completed the request
|
||||
* via [afterScroll] callback.
|
||||
*/
|
||||
fun scrollToPositionWithOffset(position: Int, offset: Int, afterScroll: () -> Unit) {
|
||||
this.afterScroll = afterScroll
|
||||
super.scrollToPositionWithOffset(position, offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* If a scroll to position request is made and a layout pass occurs prior to the list being populated with via the data source,
|
||||
* the base implementation clears the request as if it was never made.
|
||||
*
|
||||
* This override will capture the pending scroll position and offset, determine if the scroll request was satisfied, and
|
||||
* re-request the scroll to position to force another attempt if not satisfied.
|
||||
*
|
||||
* A pending scroll request will be re-requested if the pending scroll position is outside the bounds of the current known size of
|
||||
* items in the list.
|
||||
*/
|
||||
override fun onLayoutCompleted(state: RecyclerView.State?) {
|
||||
val pendingScrollPosition = mPendingScrollPosition
|
||||
val pendingScrollOffset = mPendingScrollPositionOffset
|
||||
|
||||
val reRequestPendingPosition = pendingScrollPosition >= (state?.mItemCount ?: 0)
|
||||
|
||||
// Base implementation always clears mPendingScrollPosition+mPendingScrollPositionOffset
|
||||
super.onLayoutCompleted(state)
|
||||
|
||||
// Re-request scroll to position request if necessary thus forcing mPendingScrollPosition+mPendingScrollPositionOffset to be re-assigned
|
||||
if (reRequestPendingPosition) {
|
||||
Log.d(TAG, "Re-requesting pending scroll position: $pendingScrollPosition offset: $pendingScrollOffset")
|
||||
if (pendingScrollOffset != INVALID_OFFSET) {
|
||||
scrollToPositionWithOffset(pendingScrollPosition, pendingScrollOffset)
|
||||
} else {
|
||||
scrollToPosition(pendingScrollPosition)
|
||||
}
|
||||
} else {
|
||||
afterScroll?.invoke()
|
||||
afterScroll = null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ConversationLayoutManager::class.java)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user