Добавьте пространство в RecyclerView с помощью PagerSnapHelper

Я ищу способ добавить пробел между моими элементами, используя RecyclerView и PagerSnapHelper,

Чтобы продемонстрировать, что я имею в виду, вот gif:

Как вы можете видеть выше между двумя изображениями, есть небольшое черное пространство. Но эти места доступны только на свитке. Если scrollState находится в SCROLL_STATE_IDLE изображение будет отцентрировано и будет "полной шириной".

Это то, чего я хочу достичь.

Моя идея заключалась в том, чтобы добавить новый "заполнитель" viewType к моему RecyclerView.Adapter и настроить PagerSnapHelper "перепрыгнуть" заполнитель viewType (перепрыгивать каждую секунду View было бы также возможно). Но почему-то это не работает. Я не могу изменить PagerSnaperHelper (или SnapHelper с большим количеством дублированного кода из PagerSnapHelper) работать так, как я хочу. Еще дальше при реализации это чувствовало себя как-то не так. Мне казалось, что "это не так, как должно быть".

Итак - какие-либо рекомендации или идеи, как я могу решить это? Я полностью открыт для всех предложений.

Юридическая информация:
Я знаю, что мог бы использовать старый ViewPager за это. Но по особым причинам я прошу о реализации для RecyclerView, Поэтому, пожалуйста, не предлагайте использовать ViewPager здесь, спасибо

2 ответа

Решение

Вы можете попробовать это: создать два viewTypeПервый - это ваш образ, второй - черный отступ. Тогда в SCROLL_STATE_IDLE а также SCROLL_STATE_SETTLING вызов smoothScrollToPosition(position) в желаемое положение.

val pagerSnapHelper = PagerSnapHelper()
val spacePagerSnapHelper = SpacePagerSnapHelper(pagerSnapHelper)
pagerSnapHelper.attachToRecyclerView(recyclerView)
spacePagerSnapHelper.attachToRecyclerView(recyclerView)

И это SpacePagerSnapHelper:

/**
 * This is an [RecyclerView.OnScrollListener] which will scrolls
 * the [RecyclerView] to the next closes position if the
 * position of the snapped View (calculated by the given [snapHelper] in [SnapHelper.findSnapView])
 * is odd. Because each odd position is a space/placeholder where we want to jump over.
 *
 * See also [this SO question](https://stackru.com/q/51747104).
 */
class SpacePagerSnapHelper(
        private val snapHelper: SnapHelper
) : RecyclerView.OnScrollListener() {

    private enum class ScrollDirection {
        UNKNOWN, LEFT, RIGHT
    }

    private var scrollDirection: ScrollDirection = UNKNOWN

    fun attachToRecyclerView(recyclerView: RecyclerView) {
        recyclerView.addOnScrollListener(this)
    }

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        scrollDirection = if (dx > 0) RIGHT else LEFT
    }

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        when (newState) {
            RecyclerView.SCROLL_STATE_IDLE -> onPageChanged(recyclerView)
            RecyclerView.SCROLL_STATE_SETTLING -> onPageChanged(recyclerView)
        }
    }

    private fun onPageChanged(recyclerView: RecyclerView) {
        val layoutManager = recyclerView.layoutManager
        val viewToSnap = snapHelper.findSnapView(layoutManager)
        viewToSnap?.let {
            val position = layoutManager.getPosition(it)
            // Only "jump over" each second item because it is a space/placeholder
            // see also MediaContentAdapter.
            if (position % 2 != 0) {
                when (scrollDirection) {
                    LEFT -> {
                        recyclerView.smoothScrollToPosition(position - 1)
                    }
                    RIGHT -> {
                        recyclerView.smoothScrollToPosition(position + 1)
                    }
                    UNKNOWN -> {
                        Timber.i("Unknown scrollDirection... Don't know where to go")
                    }
                }
            }
        }
    }
}

Решение UgAr0FF в целом работает, но оно также приводит к нежелательному поведению прокрутки:

как это не должно выглядеть

Как видите, PagerSnapHelper сначала пытается сфокусироваться на разделителе, а затем решает перейти к следующему элементу. Я исправил поведение прокрутки, чтобы оно выглядело так:

как это должно выглядеть

Вот код:

import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_SETTLING


class PagerSnapAndSkipDividerHelper : PagerSnapHelper() {

    private var dx = 0

    override fun attachToRecyclerView(recyclerView: RecyclerView?) {
        super.attachToRecyclerView(recyclerView)
        recyclerView?.addOnScrollListener(onScrollListener)
    }

    override fun findTargetSnapPosition(
        layoutManager: RecyclerView.LayoutManager?,
        velocityX: Int,
        velocityY: Int
    ): Int = getNextNonDividerPosition(
        super.findTargetSnapPosition(layoutManager, velocityX, velocityY)
    )

    private fun getNextNonDividerPosition(position: Int) =
        if (position % 2 == 0) position else if (dx > 0) position + 1 else position - 1

    private val onScrollListener = object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            this@PagerSnapAndSkipDividerHelper.dx = dx
        }

        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            if (listOf(SCROLL_STATE_IDLE, SCROLL_STATE_SETTLING).contains(newState)) {
                val viewToSnapTo = findSnapView(recyclerView.layoutManager) ?: return
                val position = recyclerView.layoutManager?.getPosition(viewToSnapTo) ?: return
                val nonDividerPosition = getNextNonDividerPosition(position)
                if (position != nonDividerPosition) {
                    recyclerView.smoothScrollToPosition(nonDividerPosition)
                }
            }
        }
    }
}

Установите это так:

PagerSnapAndSkipDividerHelper().attachToRecyclerView(recyclerView)
Другие вопросы по тегам