Skip to content

Instantly share code, notes, and snippets.

@matsushitak
Created June 9, 2020 04:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matsushitak/98be509fb1fa74dcc73e278085c87964 to your computer and use it in GitHub Desktop.
Save matsushitak/98be509fb1fa74dcc73e278085c87964 to your computer and use it in GitHub Desktop.
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
/**
* RecyclerViewのページングリスナー
*/
// 参考:https://thetopsites.net/article/50618951.shtml
abstract class RecyclerViewPagingListener(
layoutManager: RecyclerView.LayoutManager,
direction: LoadOnScrollDirection
) : RecyclerView.OnScrollListener() {
/**
* 現在読み込んだページ数
*/
private var pendingCurrentPage = 0
/**
* 現在読み込んだアイテム数
*/
private var pendingTotalItemCount = 0
/**
* ロード中フラグ
*/
private var loading = true
/**
* 検知する方向
*/
private val pendingDirection: LoadOnScrollDirection
/**
* LinearLayoutManager
*/
private var pendingLinearLayoutManager: LinearLayoutManager? = null
/**
* GridLayoutManager
*/
private var pendingGridLayoutManager: GridLayoutManager? = null
/**
* StaggeredGridLayoutManager
*/
private var pendingStaggeredGridLayoutManager: StaggeredGridLayoutManager? = null
init {
// パフォーマンスのためonScrolledでキャストしない
when (layoutManager) {
is LinearLayoutManager -> {
pendingLinearLayoutManager = layoutManager
}
is GridLayoutManager -> {
pendingGridLayoutManager = layoutManager
}
is StaggeredGridLayoutManager -> {
pendingStaggeredGridLayoutManager = layoutManager
}
else -> {
throw IllegalArgumentException("unsupported this layout manager.")
}
}
pendingDirection = direction
}
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
var lastVisibleItemPosition = 0
var firstVisibleItemPosition = 0
var totalItemCount = 0
when {
pendingLinearLayoutManager != null -> {
val layoutManager = requireNotNull(pendingLinearLayoutManager)
totalItemCount = layoutManager.itemCount
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
}
pendingGridLayoutManager != null -> {
val layoutManager = requireNotNull(pendingGridLayoutManager)
totalItemCount = layoutManager.itemCount
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
}
pendingStaggeredGridLayoutManager != null -> {
val layoutManager = requireNotNull(pendingStaggeredGridLayoutManager)
val lastVisibleItemPositions = layoutManager.findLastVisibleItemPositions(null)
val firstVisibleItemPositions = layoutManager.findFirstVisibleItemPositions(null)
totalItemCount = layoutManager.itemCount
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions)
firstVisibleItemPosition = getFirstVisibleItem(firstVisibleItemPositions)
}
}
when (pendingDirection) {
LoadOnScrollDirection.BOTTOM -> {
if (totalItemCount < pendingTotalItemCount) {
pendingCurrentPage = STARTING_PAGE_INDEX
pendingTotalItemCount = totalItemCount
if (totalItemCount == 0) {
loading = true
}
}
if (loading && totalItemCount > pendingTotalItemCount) {
loading = false
pendingTotalItemCount = totalItemCount
}
if (!loading && lastVisibleItemPosition + PREFETCH_ITEM_POSITION > totalItemCount) {
pendingCurrentPage++
onLoadMore(pendingCurrentPage, totalItemCount)
loading = true
}
}
LoadOnScrollDirection.TOP -> {
if (totalItemCount < pendingTotalItemCount) {
pendingCurrentPage = STARTING_PAGE_INDEX
pendingTotalItemCount = totalItemCount
if (totalItemCount == 0) {
loading = true
}
}
if (loading && totalItemCount > pendingTotalItemCount) {
loading = false
pendingTotalItemCount = totalItemCount
}
if (!loading && firstVisibleItemPosition < PREFETCH_ITEM_POSITION) {
pendingCurrentPage++
onLoadMore(pendingCurrentPage, totalItemCount)
loading = true
}
}
}
}
private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
var maxSize = 0
for (i in lastVisibleItemPositions.indices) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i]
} else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i]
}
}
return maxSize
}
private fun getFirstVisibleItem(firstVisibleItemPositions: IntArray): Int {
var maxSize = 0
for (i in firstVisibleItemPositions.indices) {
if (i == 0) {
maxSize = firstVisibleItemPositions[i]
} else if (firstVisibleItemPositions[i] > maxSize) {
maxSize = firstVisibleItemPositions[i]
}
}
return maxSize
}
/**
* 追加読み込み
*
* @param page 現在のページ数
* @param totalItemsCount 現在のアイテム数
*/
abstract fun onLoadMore(page: Int, totalItemsCount: Int)
/**
* スクロール検知方向
*/
enum class LoadOnScrollDirection {
TOP, BOTTOM
}
companion object {
/**
* 開始ページ
*/
private const val STARTING_PAGE_INDEX = 0
/**
* プリフェッチする位置
*
* "5"に設定した場合
* "100"アイテムあると"95"が見えた時にロード開始する
*/
private const val PREFETCH_ITEM_POSITION = 10
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment