Android 的 RecyclerView SnapHelper 介紹

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

SnapHelper 是 AndroidX RecyclerView 軟件包的重要補充列粪。 簡而言之,它可用于更改 RecyclerView 的行為,用于輔助 RecyclerView 在滾動結(jié)束時將 Item 對齊到某個位置。

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

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


LinearSnapHelper

PagerSnapHelper適用于全屏項目剪验,其行為類似于ViewPager:


PagerSnapHelper

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

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

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

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

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

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

然后真屯,我們可以使用此 LayoutManager 來確定此 View 的位置:

val snapPosition = layoutManager.getPosition(snapView)

我們可以為 Kotlin 擴展函數(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)聽捕捉位置變化??

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

package com.nickrout.snaphelperlistener

interface OnSnapPositionChangeListener {

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

我們知道绑蔫,捕捉位置只會在滾動過程中改變纪他。 因此,為了確定更改晾匠,我們將結(jié)合先前定義的getSnapPosition 函數(shù)和 OnScrollListener 的自定義子類。 重要的是要注意梯刚,我們僅想知道捕捉位置何時發(fā)生變化凉馆,因此我們的類需要保留對最后一個已知位置的引用,以便僅在此位置不同時才觸發(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)聽滾動位置變化
添加選項以在滾動完成時通知

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

enum class Behavior {
    NOTIFY_ON_SCROLL,
    NOTIFY_ON_SCROLL_STATE_IDLE
}

然后瘦黑,我們使用第二個 OnScrollListener 回調(diào)來實現(xiàn)此目的:

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

我們的最終課程融合了上述所有功能京革,同時還使用了可空性和默認(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 連接起來,如下所示:

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

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

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)在甲葬,我們有一種簡單的方法可以將 RecyclerView 與 SnapHelper 聯(lián)系在一起廊勃,同時還可以監(jiān)聽捕捉位置的變化:

recyclerView.attachSnapHelperWithListener(snapHelper, behavior, onSnapPositionChangeListener)

我希望這篇文章,可以幫助你對 SnapHelper 有更深入的了解经窖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坡垫,一起剝皮案震驚了整個濱河市梭灿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冰悠,老刑警劉巖堡妒,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異屿脐,居然都是意外死亡涕蚤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門的诵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來万栅,“玉大人,你說我怎么就攤上這事西疤》沉#” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵代赁,是天一觀的道長扰她。 經(jīng)常有香客問我,道長芭碍,這世上最難降的妖魔是什么徒役? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮窖壕,結(jié)果婚禮上忧勿,老公的妹妹穿的比我還像新娘。我一直安慰自己瞻讽,他們只是感情好鸳吸,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著速勇,像睡著了一般晌砾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烦磁,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天养匈,我揣著相機與錄音,去河邊找鬼都伪。 笑死乖寒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的院溺。 我是一名探鬼主播护戳,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼恭陡,長吁一口氣:“原來是場噩夢啊……” “哼绢片!你這毒婦竟也來了嵌屎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撮躁,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年买雾,在試婚紗的時候發(fā)現(xiàn)自己被綠了把曼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡漓穿,死狀恐怖嗤军,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晃危,我是刑警寧澤叙赚,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站僚饭,受9級特大地震影響震叮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鳍鸵,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一苇瓣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧偿乖,春花似錦钓简、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撤蚊。三九已至古掏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侦啸,已是汗流浹背槽唾。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留光涂,地道東北人庞萍。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像忘闻,于是被迫代替她去往敵國和親钝计。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348