本文為 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 的中心:
PagerSnapHelper適用于全屏項目剪验,其行為類似于ViewPager:
使用這些類的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
}
}
添加選項以在滾動完成時通知
上面的實現(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)
}
}
最終課程
我們的最終課程融合了上述所有功能京革,同時還使用了可空性和默認(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 有更深入的了解经窖。