Skip to content

Commit

Permalink
Try out using Modifier.scrollable
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbanes committed Mar 11, 2021
1 parent 5a42d25 commit d28be2c
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 59 deletions.
55 changes: 6 additions & 49 deletions pager/src/main/java/dev/chrisbanes/accompanist/pager/Pager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,24 @@ package dev.chrisbanes.accompanist.pager

import android.util.Log
import androidx.annotation.IntRange
import androidx.compose.animation.splineBasedDecay
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.semantics.ScrollAxisRange
import androidx.compose.ui.semantics.horizontalScrollAxisRange
import androidx.compose.ui.semantics.scrollBy
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import kotlinx.coroutines.launch
import kotlin.math.roundToInt

/**
Expand Down Expand Up @@ -102,48 +93,14 @@ fun Pager(
) {
require(offscreenLimit >= 1) { "offscreenLimit is required to be >= 1" }

val reverseScroll = LocalLayoutDirection.current == LayoutDirection.Rtl

val density = LocalDensity.current
val decay = remember(density) { splineBasedDecay<Float>(density) }

val semantics = Modifier.composed {
val coroutineScope = rememberCoroutineScope()
semantics {
if (state.pageCount > 0) {
horizontalScrollAxisRange = ScrollAxisRange(
value = { (state.currentPage + state.currentPageOffset) * state.pageSize },
maxValue = { state.lastPageIndex.toFloat() * state.pageSize },
reverseScrolling = reverseScroll
)
// Hook up scroll actions to our state
scrollBy { x: Float, _ ->
coroutineScope.launch {
state.draggableState.drag { dragBy(x) }
}
true
}
// Treat this as a selectable group
selectableGroup()
}
}
}

val draggable = Modifier.draggable(
state = state.draggableState,
startDragImmediately = true,
onDragStarted = {
state.selectionState = PagerState.SelectionState.Dragging
},
onDragStopped = { velocity ->
launch { state.performFling(velocity, decay) }
},
val scrollable = Modifier.scrollable(
orientation = Orientation.Horizontal,
reverseDirection = reverseScroll,
flingBehavior = state.flingBehavior,
state = state
)

Layout(
modifier = modifier.then(semantics).then(draggable),
modifier = modifier.then(scrollable).clipToBounds(),
content = {
val firstPage = (state.currentPage - offscreenLimit).coerceAtLeast(0)
val lastPage = (state.currentPage + offscreenLimit).coerceAtMost(state.lastPageIndex)
Expand Down
55 changes: 45 additions & 10 deletions pager/src/main/java/dev/chrisbanes/accompanist/pager/PagerState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ import androidx.compose.animation.core.DecayAnimationSpec
import androidx.compose.animation.core.animate
import androidx.compose.animation.core.animateDecay
import androidx.compose.animation.core.calculateTargetValue
import androidx.compose.animation.core.exponentialDecay
import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.DraggableState
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand Down Expand Up @@ -80,12 +84,18 @@ class PagerState(
pageCount: Int,
currentPage: Int = 0,
@FloatRange(from = 0.0, to = 1.0) currentPageOffset: Float = 0f,
) {
): ScrollableState {
private var _pageCount by mutableStateOf(pageCount)
private var _currentPage by mutableStateOf(currentPage)
private val _currentPageOffset = mutableStateOf(currentPageOffset)
internal var pageSize by mutableStateOf(0)

/**
* The ScrollableController instance. We keep it as we need to call stopAnimation on it once
* we reached the end of the list.
*/
private val scrollableState = ScrollableState { onScroll(it) }

/**
* The number of pages to display.
*/
Expand Down Expand Up @@ -174,7 +184,7 @@ class PagerState(

// We don't specifically use the DragScope's dragBy, but
// we do want to use it's mutex
draggableState.drag {
scroll {
selectionState = SelectionState.Settling
animateToPage(
page = page.coerceIn(0, lastPageIndex),
Expand All @@ -201,7 +211,7 @@ class PagerState(
) {
// We don't specifically use the DragScope's dragBy, but
// we do want to use it's mutex
draggableState.drag {
scroll {
currentPage = page
currentPageOffset = pageOffset
selectionState = SelectionState.Idle
Expand Down Expand Up @@ -248,8 +258,10 @@ class PagerState(
else -> 0f
}

internal val draggableState = DraggableState { delta ->
private fun onScroll(delta: Float): Float {
dragByOffset(delta / pageSize.coerceAtLeast(1))
// FIXME: should consume properly
return delta
}

private fun dragByOffset(deltaOffset: Float) {
Expand Down Expand Up @@ -289,13 +301,21 @@ class PagerState(
}
}

internal val flingBehavior: FlingBehavior = object : FlingBehavior {
override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
performFling2(initialVelocity)
// FIXME: calculate proper remaining velocity
return initialVelocity
}
}

/**
* TODO make this public?
*/
internal suspend fun performFling(
internal suspend fun ScrollScope.performFling2(
initialVelocity: Float,
animationSpec: DecayAnimationSpec<Float>,
) = draggableState.drag {
animationSpec: DecayAnimationSpec<Float> = exponentialDecay(),
) {
selectionState = SelectionState.Settling

// We calculate the target offset using pixels, rather than using the offset
Expand Down Expand Up @@ -337,7 +357,8 @@ class PagerState(
}

val coerced = value.coerceIn(0f, pageSize.toFloat())
dragBy((currentPageOffset * pageSize) - coerced)

scrollBy((currentPageOffset * pageSize) - coerced)

val pastLeftBound = initialVelocity > 0 &&
(currentPage < targetPage || (currentPage == targetPage && currentPageOffset == 0f))
Expand All @@ -364,13 +385,27 @@ class PagerState(
initialVelocity = initialVelocity,
animationSpec = spring()
) { value, _ ->
dragBy((currentPageOffset * pageSize) - value)
scrollBy((currentPageOffset * pageSize) - value)
}
}

snapToNearestPage()
}

override val isScrollInProgress: Boolean
get() = scrollableState.isScrollInProgress

override fun dispatchRawDelta(delta: Float): Float {
return scrollableState.dispatchRawDelta(delta)
}

override suspend fun scroll(
scrollPriority: MutatePriority,
block: suspend ScrollScope.() -> Unit
) {
scrollableState.scroll(scrollPriority, block)
}

override fun toString(): String = "PagerState(" +
"pageCount=$pageCount, " +
"currentPage=$currentPage, " +
Expand Down

0 comments on commit d28be2c

Please sign in to comment.