Kotlin Android: Как реализовать поэлементную прокрутку RecyclerView — прокрутка к середине ближайшего элемента?

Имеется горизонтальный RecyclerView с CardView-элементами. При прокрутке пользователем список должен сам прокрутиться к ближайшему элементу при свайпе и остановиться так, чтобы элемент оказался по центру экрана и по бокам было видно 2 соседних элемента.

Идеальный пример того, что я хочу получить — ВК Клипы (уж извините, ничего другого не нашел ¯\_(ツ)_/¯): https://www.youtube.com/watch?v=FDAHxyUbt74


На данный момент получилось сделать что-то похожее, но если прокрутить быстро, то скролл не остановится на соседнем элементе, а прокрутит его.

Логика такая:

В onScrolled(), при условии, что сейчас выполняется прокрутка пользователем, получаем направление прокрутки scrollDirection (dx > 0 — вправо, dx < 0 — влево )

Затем, при остановке прокрутки срабатывает onScrollStateChanged() и если в данный момент список не прокручивается (newState == 1) и его нужно прокрутить (scrollDirection != 0), то получаем первый и последний видимые элементы и прокручиваем список к одному из них, в зависимости от нужного направления

val scrollListener = object : RecyclerView.OnScrollListener() {
    var scrollDirection = 0

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)

        if (newState > 0 || scrollDirection == 0)
            return

        val layoutManager = recyclerView.layoutManager as LinearLayoutManager
        val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition()
        val lastVisiblePosition = layoutManager.findLastVisibleItemPosition()
        val firstCompletelyVisiblePosition = layoutManager.findFirstCompletelyVisibleItemPosition()
        val lastCompletelyVisiblePosition = layoutManager.findLastCompletelyVisibleItemPosition()

        if (lastVisiblePosition >= 0 && firstVisiblePosition >= 0){
            var elementPositionToScroll = 0

            if (scrollDirection > 0){
                elementPositionToScroll = if (lastCompletelyVisiblePosition < 0)
                    lastVisiblePosition else lastCompletelyVisiblePosition
            }
            if (scrollDirection < 0){
                elementPositionToScroll = if (firstCompletelyVisiblePosition < 0)
                    firstVisiblePosition else firstCompletelyVisiblePosition
            }

            val elementToScroll = layoutManager.findViewByPosition(elementPositionToScroll)
            if (elementToScroll != null)
                recyclerView.smoothScrollBy((elementToScroll.x - dpToPx(40)).toInt(), 0)
            scrollDirection = 0
        }
    }

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        if (recyclerView.scrollState == RecyclerView.SCROLL_STATE_DRAGGING)
            scrollDirection = dx
    }

}

Может быть есть какая-то встроенная реализация для прокрутки свайпами?


Ответы (1 шт):

Автор решения: denisnumb

Чтобы получить от списка элементов желаемое поведение, нужно всего лишь заменить RecyclerView на ViewPager2.

Так как ViewPager2 наследуется от RecyclerView, в коде ничего не нужно изменять (по крайней мере, в моем случае).

Для отображения по краям соседних элементов использовал вот это решение с англоязычной версии сайта.

→ Ссылка