Android 的 RecyclerView SnapHelper 介紹

本文為 Nick Rout 發(fā)布于 Medium 的文章譯文
原文鏈接為 Detecting snap changes with Android’s RecyclerView SnapHelper
本文僅作為個(gè)人學(xué)習(xí)記錄所用。如有涉及侵權(quán),請(qǐng)相關(guān)人士盡快聯(lián)系譯文作者。

SnapHelper 是 AndroidX RecyclerView 軟件包的重要補(bǔ)充。 簡(jiǎn)而言之,它可用于更改 RecyclerView 的行為,用于輔助 RecyclerView 在滾動(dòng)結(jié)束時(shí)將 Item 對(duì)齊到某個(gè)位置。

目前,基本 SnapHelper 類有兩種標(biāo)準(zhǔn)實(shí)現(xiàn); LinearSnapHelper 和 PagerSnapHelper,它們各自提供的功能略有不同。 兩者都支持水平和垂直方向。

LinearSnapHelper 適用于較小的項(xiàng)目,并將目標(biāo)子視圖的中心對(duì)齊到 RecyclerView 的中心:


LinearSnapHelper

PagerSnapHelper適用于全屏項(xiàng)目,其行為類似于ViewPager:


PagerSnapHelper

使用這些類的API非常簡(jiǎn)單:
val snapHelper = LinearSnapHelper() // Or PagerSnapHelper
snapHelper.attachToRecyclerView(recyclerView)

缺少API的情況????♀?

如果我們想知道捕捉位置何時(shí)更改該怎么辦? 例如,也許我們正在使用 PagerSnapHelper,并且想要顯示一個(gè)頁(yè)面指示器。
不幸的是,在撰寫本文時(shí),尚不存在此類 API 。 對(duì)于這樣的回調(diào),甚至存在一個(gè)開放的問(wèn)題,已經(jīng)存在了一段時(shí)間。
我們將如何實(shí)施呢? SnapHelper 類很復(fù)雜且不是非常模塊化,因此擴(kuò)展它們(或編寫新的子類)將很痛苦。 幸運(yùn)的是,我們可以利用現(xiàn)有的 RecyclerView 類和一些 Kotlin 魔術(shù)來(lái)實(shí)現(xiàn)這一目標(biāo)。

查找當(dāng)前的捕捉位置??

我們需要的第一件事是確定當(dāng)前捕捉位置的方法。 同樣,目前尚不存在此類 SnapHelper 函數(shù),我們將必須自行實(shí)現(xiàn)此功能。
SnapHelper 提供的功能是一種查找當(dāng)前快照視圖的方法。 我們必須傳遞 SnapHelper 附加到的 RecyclerView 使用的 LayoutManager:

val layoutManager = recyclerView.layoutManager
val snapView = snapHelper.findSnapView(layoutManager)

然后,我們可以使用此 LayoutManager 來(lái)確定此 View 的位置:

val snapPosition = layoutManager.getPosition(snapView)

我們可以為 Kotlin 擴(kuò)展函數(shù)中的可重用性而巧妙地將其包裝起來(lái),同時(shí)還要考慮到一些可為空性方面:

package com.nickrout.snaphelperlistener

import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper

fun SnapHelper.getSnapPosition(recyclerView: RecyclerView): Int {
    val layoutManager = recyclerView.layoutManager ?: return RecyclerView.NO_POSITION
    val snapView = findSnapView(layoutManager) ?: return RecyclerView.NO_POSITION
    return layoutManager.getPosition(snapView)
}

監(jiān)聽(tīng)捕捉位置變化??

在深入研究如何確定捕捉位置變化之前,我們先定義一個(gè)簡(jiǎn)單的回調(diào)接口:

package com.nickrout.snaphelperlistener

interface OnSnapPositionChangeListener {

    fun onSnapPositionChange(position: Int)
}
確定捕捉位置變化

我們知道,捕捉位置只會(huì)在滾動(dòng)過(guò)程中改變。 因此,為了確定更改,我們將結(jié)合先前定義的getSnapPosition 函數(shù)和 OnScrollListener 的自定義子類。 重要的是要注意,我們僅想知道捕捉位置何時(shí)發(fā)生變化,因此我們的類需要保留對(duì)最后一個(gè)已知位置的引用,以便僅在此位置不同時(shí)才觸發(fā)回調(diào)。 關(guān)鍵功能:

private var snapPosition = RecyclerView.NO_POSITION

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
    maybeNotifySnapPositionChange(recyclerView)
}

private fun maybeNotifySnapPositionChange(recyclerView: RecyclerView) {
    val snapPosition = snapHelper.getSnapPosition(recyclerView)
    val snapPositionChanged = this.snapPosition != snapPosition
    if (snapPositionChanged) {
        onSnapPositionChangeListener
            .onSnapPositionChange(snapPosition)
        this.snapPosition = snapPosition
    }
}
監(jiān)聽(tīng)滾動(dòng)位置變化
添加選項(xiàng)以在滾動(dòng)完成時(shí)通知

上面的實(shí)現(xiàn)將在滾動(dòng)事件期間將所有捕捉位置更改通知我們,特別是在使用 LinearSnapHelper 時(shí)。 也許我們只想知道最終的捕捉位置是什么(即,當(dāng)滾動(dòng)狀態(tài)變?yōu)榭臻e狀態(tài)時(shí))?
首先,讓我們定義一個(gè)枚舉類來(lái)指定以下兩個(gè)選項(xiàng):

enum class Behavior {
    NOTIFY_ON_SCROLL,
    NOTIFY_ON_SCROLL_STATE_IDLE
}

然后,我們使用第二個(gè) OnScrollListener 回調(diào)來(lái)實(shí)現(xiàn)此目的:

override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
        maybeNotifySnapPositionChange(recyclerView)
    }
}
在滾動(dòng)狀態(tài)空閑時(shí)監(jiān)聽(tīng)快照位置變化
最終課程

我們的最終課程融合了上述所有功能,同時(shí)還使用了可空性和默認(rèn)參數(shù):

package com.nickrout.snaphelperlistener

import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper

class SnapOnScrollListener(
        private val snapHelper: SnapHelper,
        var behavior: Behavior = Behavior.NOTIFY_ON_SCROLL,
        var onSnapPositionChangeListener: OnSnapPositionChangeListener? = null
) : RecyclerView.OnScrollListener() {

    enum class Behavior {
        NOTIFY_ON_SCROLL,
        NOTIFY_ON_SCROLL_STATE_IDLE
    }

    private var snapPosition = RecyclerView.NO_POSITION

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        if (behavior == Behavior.NOTIFY_ON_SCROLL) {
            maybeNotifySnapPositionChange(recyclerView)
        }
    }

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        if (behavior == Behavior.NOTIFY_ON_SCROLL_STATE_IDLE
                && newState == RecyclerView.SCROLL_STATE_IDLE) {
            maybeNotifySnapPositionChange(recyclerView)
        }
    }

    private fun maybeNotifySnapPositionChange(recyclerView: RecyclerView) {
        val snapPosition = snapHelper.getSnapPosition(recyclerView)
        val snapPositionChanged = this.snapPosition != snapPosition
        if (snapPositionChanged) {
            onSnapPositionChangeListener?.onSnapPositionChange(snapPosition)
            this.snapPosition = snapPosition
        }
    }
}

我們將新類與現(xiàn)有的 RecyclerView 和 SnapHelper 連接起來(lái),如下所示:

val snapOnScrollListener = SnapOnScrollListener(snapHelper, behavior, onSnapPositionChangeListener)
recyclerView.addOnScrollListener(snapOnScrollListener)
添加便捷的擴(kuò)展功能??

我們當(dāng)前的實(shí)現(xiàn)效果很好,但是我們可以通過(guò)使用另一個(gè)擴(kuò)展功能來(lái)減少設(shè)置所需的樣板代碼。
我們要確保新的 OnSnapPositionChangeListener 和 SnapOnScrollListener 類的設(shè)置一致。 我們還希望保持“行為”選項(xiàng)可用:

package com.nickrout.snaphelperlistener

import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper

fun RecyclerView.attachSnapHelperWithListener(
        snapHelper: SnapHelper,
        behavior: SnapOnScrollListener.Behavior = SnapOnScrollListener.Behavior.NOTIFY_ON_SCROLL,
        onSnapPositionChangeListener: OnSnapPositionChangeListener) {
    snapHelper.attachToRecyclerView(this)
    val snapOnScrollListener = SnapOnScrollListener(snapHelper, onSnapPositionChangeListener, behavior)
    addOnScrollListener(snapOnScrollListener)
}

現(xiàn)在,我們有一種簡(jiǎn)單的方法可以將 RecyclerView 與 SnapHelper 聯(lián)系在一起,同時(shí)還可以監(jiān)聽(tīng)捕捉位置的變化:

recyclerView.attachSnapHelperWithListener(snapHelper, behavior, onSnapPositionChangeListener)

我希望這篇文章,可以幫助你對(duì) SnapHelper 有更深入的了解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容