f9c25b350e
Minimal implementation using new Compose SnapFlingBehavior
183 lines
5.7 KiB
Kotlin
183 lines
5.7 KiB
Kotlin
package eu.kanade.presentation.components
|
|
|
|
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.BoxScope
|
|
import androidx.compose.foundation.layout.PaddingValues
|
|
import androidx.compose.foundation.layout.wrapContentSize
|
|
import androidx.compose.foundation.lazy.LazyListItemInfo
|
|
import androidx.compose.foundation.lazy.LazyListState
|
|
import androidx.compose.foundation.lazy.LazyRow
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.runtime.Stable
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.saveable.Saver
|
|
import androidx.compose.runtime.saveable.listSaver
|
|
import androidx.compose.runtime.saveable.rememberSaveable
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.runtime.snapshotFlow
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.util.fastMaxBy
|
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
|
|
@Composable
|
|
fun HorizontalPager(
|
|
count: Int,
|
|
modifier: Modifier = Modifier,
|
|
state: PagerState = rememberPagerState(),
|
|
key: ((page: Int) -> Any)? = null,
|
|
contentPadding: PaddingValues = PaddingValues(),
|
|
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
|
|
userScrollEnabled: Boolean = true,
|
|
content: @Composable BoxScope.(page: Int) -> Unit,
|
|
) {
|
|
Pager(
|
|
count = count,
|
|
modifier = modifier,
|
|
state = state,
|
|
isVertical = false,
|
|
key = key,
|
|
contentPadding = contentPadding,
|
|
verticalAlignment = verticalAlignment,
|
|
userScrollEnabled = userScrollEnabled,
|
|
content = content,
|
|
)
|
|
}
|
|
|
|
@Composable
|
|
private fun Pager(
|
|
count: Int,
|
|
modifier: Modifier,
|
|
state: PagerState,
|
|
isVertical: Boolean,
|
|
key: ((page: Int) -> Any)?,
|
|
contentPadding: PaddingValues,
|
|
userScrollEnabled: Boolean,
|
|
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
|
|
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
|
|
content: @Composable BoxScope.(page: Int) -> Unit,
|
|
) {
|
|
LaunchedEffect(count) {
|
|
state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0)
|
|
}
|
|
|
|
LaunchedEffect(state) {
|
|
snapshotFlow { state.mostVisiblePageLayoutInfo?.index }
|
|
.distinctUntilChanged()
|
|
.collect { state.updateCurrentPageBasedOnLazyListState() }
|
|
}
|
|
|
|
if (isVertical) {
|
|
LazyColumn(
|
|
modifier = modifier,
|
|
state = state.lazyListState,
|
|
contentPadding = contentPadding,
|
|
horizontalAlignment = horizontalAlignment,
|
|
verticalArrangement = Arrangement.aligned(verticalAlignment),
|
|
userScrollEnabled = userScrollEnabled,
|
|
flingBehavior = rememberSnapFlingBehavior(lazyListState = state.lazyListState),
|
|
) {
|
|
items(
|
|
count = count,
|
|
key = key,
|
|
) { page ->
|
|
Box(
|
|
modifier = Modifier
|
|
.fillParentMaxHeight()
|
|
.wrapContentSize(),
|
|
) {
|
|
content(this, page)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
LazyRow(
|
|
modifier = modifier,
|
|
state = state.lazyListState,
|
|
contentPadding = contentPadding,
|
|
verticalAlignment = verticalAlignment,
|
|
horizontalArrangement = Arrangement.aligned(horizontalAlignment),
|
|
userScrollEnabled = userScrollEnabled,
|
|
flingBehavior = rememberSnapFlingBehavior(lazyListState = state.lazyListState),
|
|
) {
|
|
items(
|
|
count = count,
|
|
key = key,
|
|
) { page ->
|
|
Box(
|
|
modifier = Modifier
|
|
.fillParentMaxWidth()
|
|
.wrapContentSize(),
|
|
) {
|
|
content(this, page)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun rememberPagerState(
|
|
initialPage: Int = 0,
|
|
) = rememberSaveable(saver = PagerState.Saver) {
|
|
PagerState(currentPage = initialPage)
|
|
}
|
|
|
|
@Stable
|
|
class PagerState(
|
|
currentPage: Int = 0,
|
|
) {
|
|
init { check(currentPage >= 0) { "currentPage cannot be less than zero" } }
|
|
|
|
val lazyListState = LazyListState(firstVisibleItemIndex = currentPage)
|
|
|
|
private var _currentPage by mutableStateOf(currentPage)
|
|
|
|
var currentPage: Int
|
|
get() = _currentPage
|
|
set(value) {
|
|
if (value != _currentPage) {
|
|
_currentPage = value
|
|
}
|
|
}
|
|
|
|
val mostVisiblePageLayoutInfo: LazyListItemInfo?
|
|
get() {
|
|
val layoutInfo = lazyListState.layoutInfo
|
|
return layoutInfo.visibleItemsInfo.fastMaxBy {
|
|
val start = maxOf(it.offset, 0)
|
|
val end = minOf(
|
|
it.offset + it.size,
|
|
layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding,
|
|
)
|
|
end - start
|
|
}
|
|
}
|
|
|
|
fun updateCurrentPageBasedOnLazyListState() {
|
|
mostVisiblePageLayoutInfo?.let {
|
|
currentPage = it.index
|
|
}
|
|
}
|
|
|
|
suspend fun animateScrollToPage(page: Int) {
|
|
lazyListState.animateScrollToItem(index = page)
|
|
}
|
|
|
|
suspend fun scrollToPage(page: Int) {
|
|
lazyListState.scrollToItem(index = page)
|
|
updateCurrentPageBasedOnLazyListState()
|
|
}
|
|
|
|
companion object {
|
|
val Saver: Saver<PagerState, *> = listSaver(
|
|
save = { listOf(it.currentPage) },
|
|
restore = { PagerState(it[0]) },
|
|
)
|
|
}
|
|
}
|