Skip to content

Instantly share code, notes, and snippets.

@mayojava
Last active October 5, 2020 13:19

Revisions

  1. mayojava revised this gist Oct 5, 2020. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions Pager.kt
    Original file line number Diff line number Diff line change
    @@ -70,7 +70,7 @@ fun Pager(
    }

    fun Modifier.scrollOffset(orientation: Orientation, offset: Dp) = offset(
    x = if (orientation ==Horizontal) offset else 0.dp,
    x = if (orientation == Horizontal) offset else 0.dp,
    y = if (orientation == Vertical) offset else 0.dp
    )

    @@ -99,7 +99,7 @@ private data class PagerState(
    val scrollController = ScrollableController(
    consumeScrollDelta = { scrollDelta ->
    val oldOffset = scrollOffset
    scrollOffset = (oldOffset + scrollDelta).coerceIn(maxPageFraction * -pageDimen, 0f)
    scrollOffset = (oldOffset + scrollDelta).coerceIn(-1 * maxPageFraction * pageDimen, 0f)
    scrollOffset - oldOffset
    },
    flingConfig = FlingConfig(AndroidFlingDecaySpec(density)) { target ->
    @@ -108,7 +108,7 @@ private data class PagerState(
    floor(currentPageFraction) * pageDimen
    } else {
    ceil(currentPageFraction) * pageDimen
    }.coerceIn(maxPageFraction * -pageDimen, 0f)
    }.coerceIn(-1 * maxPageFraction * pageDimen, 0f)

    TargetAnimation(newTarget, tween())
    },
  2. mayojava created this gist Oct 5, 2020.
    117 changes: 117 additions & 0 deletions Pager.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,117 @@
    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(maxPageFraction * -pageDimen, 0f)
    scrollOffset - oldOffset
    },
    flingConfig = FlingConfig(AndroidFlingDecaySpec(density)) { target ->

    val newTarget = if (target < scrollOffset) {
    floor(currentPageFraction) * pageDimen
    } else {
    ceil(currentPageFraction) * pageDimen
    }.coerceIn(maxPageFraction * -pageDimen, 0f)

    TargetAnimation(newTarget, tween())
    },
    animationClock = clock
    )
    }