Skip to content

Instantly share code, notes, and snippets.

@mayojava
Last active October 5, 2020 13:19
Show Gist options
  • Save mayojava/7d6ae7a142d48c9809620e1dd0b9e6a9 to your computer and use it in GitHub Desktop.
Save mayojava/7d6ae7a142d48c9809620e1dd0b9e6a9 to your computer and use it in GitHub Desktop.
package dev.efemoney.orchestra.ui
import androidx.compose.animation.asDisposableClock
import androidx.compose.animation.core.AnimationClockObservable
import androidx.compose.animation.core.TargetAnimation
import androidx.compose.animation.core.tween
import androidx.compose.foundation.animation.AndroidFlingDecaySpec
import androidx.compose.foundation.animation.FlingConfig
import androidx.compose.foundation.gestures.ScrollableController
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.*
import androidx.compose.ui.Layout
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.AnimationClockAmbient
import androidx.compose.ui.platform.DensityAmbient
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import androidx.compose.ui.util.fastMap
import kotlin.math.ceil
import kotlin.math.floor
import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
import androidx.compose.ui.gesture.scrollorientationlocking.Orientation.Horizontal
import androidx.compose.ui.gesture.scrollorientationlocking.Orientation.Vertical
@Composable
fun Pager(
orientation: Orientation,
modifier: Modifier = Modifier,
children: @Composable () -> Unit = emptyContent()
) {
val pagerState = rememberPagerState(orientation)
Layout(
children,
modifier
.scrollable(pagerState.orientation, pagerState.scrollController)
.scrollOffset(orientation, pagerState.scrollOffset.inDp)
) { measurables, constraints ->
val placeables = measurables.fastMap { it.measure(constraints) }
val measuredSize = placeables.fold(IntSize.Zero) { currentMax, placeable ->
IntSize(
width = maxOf(currentMax.width, placeable.width),
height = maxOf(currentMax.height, placeable.height)
)
}
pagerState.pageCount = placeables.size
pagerState.pageDimen = if (orientation == Horizontal) measuredSize.width else measuredSize.height
layout(measuredSize.width, measuredSize.height) {
placeables.fastForEachIndexed { index, placeable ->
val pageOffset = pagerState.pageDimen * index
val pageDx = if (orientation == Horizontal) pageOffset else 0
val pageDy = if (orientation == Vertical) pageOffset else 0
placeable.placeRelative(
pageDx + (measuredSize.width - placeable.width) / 2,
pageDy + (measuredSize.height - placeable.height) / 2,
)
}
}
}
}
fun Modifier.scrollOffset(orientation: Orientation, offset: Dp) = offset(
x = if (orientation == Horizontal) offset else 0.dp,
y = if (orientation == Vertical) offset else 0.dp
)
@Composable
private fun rememberPagerState(orientation: Orientation): PagerState {
val density = DensityAmbient.current
val clock = AnimationClockAmbient.current.asDisposableClock()
return remember(orientation, density, clock) { PagerState(orientation, density, clock) }
}
@Stable
private data class PagerState(
val orientation: Orientation,
val density: Density,
val clock: AnimationClockObservable
) {
var scrollOffset by mutableStateOf(0f)
var pageDimen by mutableStateOf(0)
var pageCount by mutableStateOf(0)
val maxPageFraction get() = pageCount - 1f
val currentPageFraction get() = scrollOffset / pageDimen
val scrollController = ScrollableController(
consumeScrollDelta = { scrollDelta ->
val oldOffset = scrollOffset
scrollOffset = (oldOffset + scrollDelta).coerceIn(-1 * maxPageFraction * pageDimen, 0f)
scrollOffset - oldOffset
},
flingConfig = FlingConfig(AndroidFlingDecaySpec(density)) { target ->
val newTarget = if (target < scrollOffset) {
floor(currentPageFraction) * pageDimen
} else {
ceil(currentPageFraction) * pageDimen
}.coerceIn(-1 * maxPageFraction * pageDimen, 0f)
TargetAnimation(newTarget, tween())
},
animationClock = clock
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment