#1 Pagination ํด๋์ค
Pagination ํด๋์ค๋ ์ ์ฒด ๊ฒ์๊ธ ๊ฐฏ์, ํ ํ์ด์ง์ ํ์ํ ๊ฒ์๊ธ ๊ฐฏ์, ํ ํ์ด์ง ๋ธ๋ญ์ ํ์ํ ํ์ด์ง๋ค์ ๊ฐฏ์์ ๊ธฐ๋ฐํด ๊ฒ์ํ์ ๋ค๋น๊ฒ์ด์ ์ญํ ์ ํ๋ ํด๋์ค๋ค. ์์ ์ ์น ํ๋ก๊ทธ๋๋ฐ์์ ๊ฒ์ํ์ ๋ง๋ค ๋ ์ฒ์ ๋ง๋ค์๋ค. ๊ทธ ์ดํ๋ก๋ 2 ~ 3๋ฒ ์ ๋ ๋ค์ Pagination ํด๋์ค๋ฅผ ์ฌ์์ฑํ๋ ๊ธฐ์ต์ด ์๋ค. ์ผ๋ง๋ ๋นํจ์จ์ ์ธ๊ฐ! ๊ทธ๋์ ์ด์ฐธ์ ์๋ฒฝํ๊ฒ ๊ตฌํํ๊ณ ๊ธฐ๋กํด๋๋ ค ํ๋ค. ์์ผ๋ก Pagination ํด๋์ค๋ ๋ณธ ๊ฒ์๊ธ์ ์ฝ๋๋ฅผ ์ฌ์ฌ์ฉํ ๊ฒ์ด๋ค.
#2 ๊ธฐ๋ณธ ์ฝ๋
#2-1 ์ธํฐํ์ด์ค
// by interfacer_han (https://kenel.tistory.com/327)
/*
(example) goToFirstPageBlockButton: <<
(example) goToPreviousPageBlockButton: <
(example) goToNextPageBlockButton: >
(example) goToLastPageBlockButton: >>
(example) pageBlock 01: << < 01 02 03 04 05 06 07 08 09 10 > >>
(example) pageBlock 02: << < 11 12 13 14 15 16 17 18 19 20 > >>
(example) pageBlock 03: << < 21 22 23 24 25 26 27 28 29 30 > >>
...
*/
interface PaginatedList<T> : List<T> { // T๋ List๊ฐ ๋ณด์ ํ๋ item์ ๋ฐ์ดํฐํ
// public property - ์ธ์๋ก ๋ฐ์ ๊ฒ๋ค (๋ฐ๋ผ์ ์์ฐํ immutable)
val items: List<T>
val itemCountPerPage: Int
val pageCountPerPageBlock: Int
// public property - immutable
val totalItemCount: Int
val totalPageCount: Int
val totalPageBlockCount: Int
// public property - privateํ setter๋ฅผ _currentPageNumber ๋ฐ _currentPageBlockNumber์ ๊ตฌํ
val currentPageNumber: Int // 1-based indexing
val currentPageBlockNumber: Int // 1-based indexing
// getter
fun getCurrentPageItems(): List<T>
fun getCurrentPageItemNumbers(): List<Int> // 1-based indexing
fun getCurrentPageItemsWithNumbers(): List<Pair<Int, T>> // 1-based indexing
fun getCurrentPageBlockPages(): List<Int>
// page move method
fun goToPage(pageNumber: Int): Boolean // 1-based indexing
fun goToPreviousPage(): Boolean
fun goToNextPage(): Boolean
fun goToFirstPage(): Boolean
fun goToLastPage(): Boolean
// pageBlock move method
fun goToPageBlock(pageBlockNumber: Int): Boolean // 1-based indexing
fun goToPreviousPageBlock(): Boolean
fun goToNextPageBlock(): Boolean
fun goToFirstPageBlock(): Boolean
fun goToLastPageBlock(): Boolean
// method for cheking before page/pageBlock moving
fun canGoToPage(pageNumber: Int): Boolean // 1-based indexing
fun canGoToPageBlock(pageBlockNumber: Int): Boolean // 1-based indexing
// pageBlock move button visibility method
fun previousPageBlockButtonVisibility(): Boolean
fun nextPageBlockButtonVisibility(): Boolean
fun firstPageBlockButtonVisibility(): Boolean
fun lastPageBlockButtonVisibility(): Boolean
// parent interface implementation - List
override val size: Int
get() = items.size
override fun contains(element: T): Boolean {
return items.contains(element)
}
override fun containsAll(elements: Collection<T>): Boolean {
return items.containsAll(elements)
}
override fun get(index: Int): T {
return items[index]
}
override fun indexOf(element: T): Int {
return items.indexOf(element)
}
override fun isEmpty(): Boolean {
return items.isEmpty()
}
override fun iterator(): Iterator<T> {
return items.iterator()
}
override fun lastIndexOf(element: T): Int {
return items.lastIndexOf(element)
}
override fun listIterator(): ListIterator<T> {
return items.listIterator()
}
override fun listIterator(index: Int): ListIterator<T> {
return items.listIterator(index)
}
override fun subList(fromIndex: Int, toIndex: Int): List<T> {
return items.subList(fromIndex, toIndex)
}
}
์ ๋งํ ์ํฉ์ ๋ค ๋์ฒํ ์ ์๊ธฐ๋ฅผ ๋ฐ๋ผ๋ฉฐ ์งฐ๋ค.
#2-2 ์ธํฐํ์ด์ค ๊ตฌํ
// by interfacer_han (https://kenel.tistory.com/327)
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.setValue
import kotlin.math.min
data class PaginatedListImpl<T>(
// public property - ์ธ์๋ก ๋ฐ์ ๊ฒ๋ค (๋ฐ๋ผ์ ์์ฐํ immutable)
override val items: List<T>,
override val itemCountPerPage: Int,
override val pageCountPerPageBlock: Int
) : PaginatedList<T> {
// public property - immutable
override val totalItemCount: Int = items.size
override val totalPageCount: Int =
if (totalItemCount % itemCountPerPage == 0) totalItemCount / itemCountPerPage
else totalItemCount / itemCountPerPage + 1
override val totalPageBlockCount: Int =
if (totalPageCount % pageCountPerPageBlock == 0) totalPageCount / pageCountPerPageBlock
else totalPageCount / pageCountPerPageBlock + 1
// public property - privateํ setter๋ฅผ _currentPageNumber ๋ฐ _currentPageBlockNumber์ ๊ตฌํ
private var _currentPageNumber by mutableIntStateOf(1)
override val currentPageNumber: Int
get() = _currentPageNumber
private var _currentPageBlockNumber by mutableIntStateOf(1)
override val currentPageBlockNumber: Int
get() = _currentPageBlockNumber
// getter
override fun getCurrentPageItems(): List<T> {
val startIndex = (currentPageNumber - 1) * itemCountPerPage
val endIndex = min(startIndex + itemCountPerPage, totalItemCount)
return if (startIndex < endIndex) items.subList(startIndex, endIndex) else emptyList()
}
override fun getCurrentPageItemNumbers(): List<Int> {
val startIndex = (currentPageNumber - 1) * itemCountPerPage
val endIndex = min(startIndex + itemCountPerPage, totalItemCount)
return if (startIndex < endIndex) (startIndex + 1 until endIndex + 1).toList() else emptyList()
}
override fun getCurrentPageItemsWithNumbers(): List<Pair<Int, T>> {
val startIndex = (currentPageNumber - 1) * itemCountPerPage
val endIndex = min(startIndex + itemCountPerPage, totalItemCount)
return if (startIndex < endIndex) {
items.subList(startIndex, endIndex).mapIndexed { index, item ->
(startIndex + index + 1) to item
}
} else {
emptyList()
}
}
override fun getCurrentPageBlockPages(): List<Int> {
val firstPageInBlock = (currentPageBlockNumber - 1) * pageCountPerPageBlock + 1
val lastPageInBlock = min(currentPageBlockNumber * pageCountPerPageBlock, totalPageCount)
return (firstPageInBlock..lastPageInBlock).toList()
}
// page move method
override fun goToPage(pageNumber: Int): Boolean {
if (canGoToPage(pageNumber)) {
_currentPageNumber = pageNumber
_currentPageBlockNumber = ((pageNumber - 1) / pageCountPerPageBlock) + 1
return true
}
return false
}
override fun goToPreviousPage(): Boolean = goToPage(currentPageNumber - 1)
override fun goToNextPage(): Boolean = goToPage(currentPageNumber + 1)
override fun goToFirstPage(): Boolean = goToPage(1)
override fun goToLastPage(): Boolean = goToPage(totalPageCount)
// pageBlock move method
override fun goToPageBlock(pageBlockNumber: Int): Boolean {
return if (canGoToPageBlock(pageBlockNumber)) {
_currentPageBlockNumber = pageBlockNumber
val firstPageInBlock = (pageBlockNumber - 1) * pageCountPerPageBlock + 1
_currentPageNumber = firstPageInBlock
true
} else {
false
}
}
override fun goToPreviousPageBlock(): Boolean = goToPageBlock(currentPageBlockNumber - 1)
override fun goToNextPageBlock(): Boolean = goToPageBlock(currentPageBlockNumber + 1)
override fun goToFirstPageBlock(): Boolean = goToPageBlock(1)
override fun goToLastPageBlock(): Boolean = goToPageBlock(totalPageBlockCount)
// method for cheking before page/pageBlock moving
override fun canGoToPage(pageNumber: Int): Boolean {
return pageNumber in 1..totalPageCount
}
override fun canGoToPageBlock(pageBlockNumber: Int): Boolean {
return pageBlockNumber in 1..totalPageBlockCount
}
// pageBlock move button visibility method
override fun previousPageBlockButtonVisibility(): Boolean = currentPageBlockNumber > 1
override fun nextPageBlockButtonVisibility(): Boolean = currentPageBlockNumber < totalPageBlockCount
override fun firstPageBlockButtonVisibility(): Boolean = previousPageBlockButtonVisibility()
override fun lastPageBlockButtonVisibility(): Boolean = nextPageBlockButtonVisibility()
}
_currentPageNumber ๋ฐ _currentPageBlockNumber๋ฅผ State๋ก ๋๋ ๊ฒ์ด ์ค์ํ๋ค. State๊ฐ ์๋๋ผ ์ผ๋ฐ Int๋ก ๋๋ฉด, ๊ทธ ๊ฐ์ด ๋ณํด๋ Jetpack Compose์ Recomposition์ด ์ ๋ฐ๋์ง ์๋๋ค. ๋ฌผ๋ก , ์๋๋ก์ด๋์์ ์ฌ์ฉํ ๊ฒ์ด ์๋๋ผ๋ฉด Int๋ก ๋ฌ๋ ๋๊ฒ ์ง๋ง ๋ง์ด๋ค.
#3 ๊ฒ์ฆ์ ์ํ ์ํ ์ฑ
#3-1 ๊ฐ์
ํ ์ด ํ๋ก์ ํธ๋ก #2-2๋ฅผ ๊ฒ์ฆํ๊ธฐ ์ํ ์ฑ์ ๋๋ฑ ๋ง๋ค์๋ค.
#3-2 ๋ชฉํ
#2-2์ 3๊ฐ์ง ์ธ์ items: List<T>, itemCountPerPage: Int, pageCountPerPageBlock: Int์ ์ฌ์ฉ์๋ก๋ถํฐ ์ ๋ ฅ๋ฐ์ Pagination ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ณ , ๊ทธ ๊ฐ์ฒด์ ํ๋กํผํฐ๋ฅผ ์๊ฐ์ ์ผ๋ก ๋ณด์ธ๋ค. ๋, ๋ฉค๋ฒ ํจ์๋ค๋ ์ ์คํ๋๋์ง ํ์ธํ๋ค.
#3-3 ๊ตฌ์กฐ
Jetpack Compose์ MVVM์ ์ ์ฉํ๋ค. ์ ๋งํ๋ฉด ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์ง์ํ๋ ค๊ณ ํ์ง๋ง, ์ฝ๋๊ฐ ์๊ฐ๋ณด๋ค ๋ฐฉ๋ํด์ง๋ ๊ฒ ๊ฐ์ Hilt๊น์ง ์ด์ฉํด ์ฝ๋๋์ ์ฝ๊ฐ ์ค์๋ค. ์ ์ฒด ์์ค์ฝ๋๋ #3-6์ ์๋ค.
#3-4 ์คํฌ๋ฆฐ์ท
ํ ์คํธ ์ผ์ด์ค๋ฅผ ๋ค์ํ๊ฒ ์ณ๋ดค๋๋ฐ, ์์ง ๋ฐํ์ ์๋ฌ๊ฐ ๋์ง๋ ์์๋ค.
#3-5 ์ถ๊ฐ ์คํฌ๋ฆฐ์ท
๊ฐ๋จํ ์ฑ์ด์ง๋ง, ๊ฝค๋ ์์ฐจ๊ฒ ๋ง๋ ๊ฒ ๊ฐ๋ค. ์ด๋ฐ ๋ถ๋ถ์ ์์ํ์ง ์์๋ ์ต๊ด์ ์ผ๋ก ๊ตฌํํ๋ ํ๋ก๊ทธ๋๋จธ๊ฐ ๋๊ณ ์ถ๋ค.
#3-6 ์ ์ฒด ์์ค์ฝ๋
android-practice/playground/Pagination at master · Kanmanemone/android-practice
Contribute to Kanmanemone/android-practice development by creating an account on GitHub.
github.com
#3-7 APK
#2-2๋ฅผ ๊ฐ์ ธ๋ค๊ฐ ์ฐ๊ณ ์ถ์๋ฐ, ์ ์๋ํ๋์ง ๋ฏธ์ฌ์ฉ์ ์ฌ๋์ด ์์ ๊ฒ ๊ฐ๋ค. ๋์์ ์๋๋ก์ด๋ ์คํ๋์ค๊น์ง ํค๊ธฐ๋ ๊ท์ฐฎ์(?) ์ฌ๋ ๋ง์ด๋ค. ๊ทธ๋ฐ ์ฌ๋๋ค์ ์ํด, #3-6์ผ๋ก ๋ง๋ APK ํ์ผ์ด๋ค. ํ๋ฒ ์ค์นํด ์คํํด๋ณด์.