Reputation: 356
I am looking for a way to programmatically slowly scroll a RecyclerView so as to bring a certain element targetPosition exactly in the middle of the screen. Note that all my items in the RecylerView have the same height by design. The RecyclerView is vertical. I am programming in Kotlin and AndroidX
I have tried:
smoothScrollToPosition(targetPosition)
It does the scrolling slowly (I can also control the speed by extending the LinearLayoutManager and overriding calculateSpeedPerPixel() - see How can i control the scrolling speed of recyclerView.smoothScrollToPosition(position)), however I can't control the exact location of the list item on the screen after the scrolling. All I know is it will be fully visible on the screen.
I have tried:
scrollToX(0, targetPosition * itemHeight - screenHeight / 2)
It gives me the error message: "W/RecyclerView: RecyclerView does not support scrolling to an absolute position. Use scrollToPosition instead"
I have also tried replacing the RecyclerView by a LinearLayoutManager which contains all the items as children, and translate the LinearLayoutManager in the view, but I don't get the children initially outside of the screen to draw.
Is there a solution to this issue?
Upvotes: 0
Views: 2157
Reputation: 356
Thank you @Bob. I missed scrollBy(). Following your advice, here is the code that worked for me.
class RecyclerViewFixedItemSize : RecyclerView {
var itemFixedSize : Int = 0
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
}
constructor(context: Context) : super(context) {
}
fun smoothScrollToPositionCentered(position: Int) {
// Find the fixed item size - once for all
if (itemFixedSize == 0) {
val ll = layoutManager as LinearLayoutManager
val fp = ll.findFirstCompletelyVisibleItemPosition()
val fv = ll.getChildAt(fp)
itemFixedSize = fv!!.height
}
// find the recycler view position and screen coordinates of the first item (partially) visible on screen
val ll = layoutManager as LinearLayoutManagerFixedItemSize
val fp = ll.findFirstVisibleItemPosition()
val fv = ll.getChildAt(0)
// find the middle of the recycler view
val dyrv = (top - bottom ) / 2
// compute the number of pixels to scroll to get the view position in the center
val dy = (position - fp) * itemFixedSize + dyrv + fv!!.top + itemFixedSize / 2
smoothScrollBy(0, dy)
}
}
Upvotes: 0
Reputation: 13865
You can calculate the smoothScrollToPosition
's targetPosition based on your actual target position and the number of visible items.
A quick POC on how to do that:
val scrollToPosition = 50
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val firstPosition = layoutManager.findFirstVisibleItemPosition()
val lastPosition = layoutManager.findLastVisibleItemPosition()
val visibleItems = lastPosition - firstPosition + 1
if (firstPosition < scrollToPosition) {
recyclerView.smoothScrollToPosition(scrollToPosition + (visibleItems / 2))
} else {
recyclerView.smoothScrollToPosition(scrollToPosition - (visibleItems / 2))
}
If you want more precise results that the item should be exactly at the middle of the screen, you can use the heights of the item (since its fixed) and the height of the RecyclerView, then calculate the offset to scroll. And call:
recyclerView.scrollBy(dx, dy)
Or:
recyclerView.smoothScrollBy(dx, dy)
Upvotes: 2