mirror of
https://github.com/fankes/pagecurl-multiplatform.git
synced 2025-09-06 10:45:43 +08:00
Add next / prev animations
This commit is contained in:
@@ -48,14 +48,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
modifier = Modifier.overlayControls(
|
modifier = Modifier.overlayControls(
|
||||||
next = {
|
next = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val next = (state.current + 1).coerceAtMost(state.max - 1)
|
state.next()
|
||||||
state.snapTo(next)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prev = {
|
prev = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val prev = (state.current - 1).coerceAtLeast(0)
|
state.prev()
|
||||||
state.snapTo(prev)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
center = {
|
center = {
|
||||||
|
@@ -11,22 +11,20 @@ import eu.wewox.pagecurl.ExperimentalPageCurlApi
|
|||||||
@ExperimentalPageCurlApi
|
@ExperimentalPageCurlApi
|
||||||
public data class PageCurlConfig(
|
public data class PageCurlConfig(
|
||||||
val curl: CurlConfig = CurlConfig(),
|
val curl: CurlConfig = CurlConfig(),
|
||||||
val interaction: InteractionConfig = InteractionConfig()
|
val direction: PageCurlDirection = PageCurlDirection.StartToEnd,
|
||||||
|
val interaction: InteractionConfig = InteractionConfig(forward = direction.forward()),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ExperimentalPageCurlApi
|
@ExperimentalPageCurlApi
|
||||||
public data class InteractionConfig(
|
public data class InteractionConfig(
|
||||||
val forward: CurlDirection = CurlDirection(
|
val forward: DragDirection,
|
||||||
Rect(Offset(0.5f, 0.0f), Offset(1.0f, 1.0f)),
|
val backward: DragDirection = DragDirection(forward.end, forward.start),
|
||||||
Rect(Offset(0.0f, 0.0f), Offset(0.5f, 1.0f)),
|
|
||||||
),
|
|
||||||
val backward: CurlDirection = CurlDirection(forward.end, forward.start),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@ExperimentalPageCurlApi
|
@ExperimentalPageCurlApi
|
||||||
public data class CurlDirection(
|
public data class DragDirection(
|
||||||
val start: Rect,
|
val start: Rect,
|
||||||
val end: Rect
|
val end: Rect,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ExperimentalPageCurlApi
|
@ExperimentalPageCurlApi
|
||||||
@@ -48,3 +46,20 @@ public data class ShadowConfig(
|
|||||||
val radius: Dp = 15.dp,
|
val radius: Dp = 15.dp,
|
||||||
val offset: DpOffset = DpOffset((-5).dp, 0.dp),
|
val offset: DpOffset = DpOffset((-5).dp, 0.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ExperimentalPageCurlApi
|
||||||
|
public enum class PageCurlDirection {
|
||||||
|
StartToEnd,
|
||||||
|
// TODO (Alex) Add support for reversed end-to-start direction
|
||||||
|
// EndToStart,
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun left(): Rect = Rect(Offset(0.0f, 0.0f), Offset(0.5f, 1.0f))
|
||||||
|
|
||||||
|
private fun right(): Rect = Rect(Offset(0.5f, 0.0f), Offset(1.0f, 1.0f))
|
||||||
|
|
||||||
|
@ExperimentalPageCurlApi
|
||||||
|
private fun PageCurlDirection.forward(): DragDirection =
|
||||||
|
when (this) {
|
||||||
|
PageCurlDirection.StartToEnd -> DragDirection(right(), left())
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package eu.wewox.pagecurl.page
|
package eu.wewox.pagecurl.page
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.AnimationVector4D
|
||||||
import androidx.compose.animation.core.VectorConverter
|
import androidx.compose.animation.core.VectorConverter
|
||||||
import androidx.compose.animation.core.calculateTargetValue
|
import androidx.compose.animation.core.calculateTargetValue
|
||||||
import androidx.compose.animation.splineBasedDecay
|
import androidx.compose.animation.splineBasedDecay
|
||||||
@@ -13,15 +15,60 @@ import androidx.compose.ui.input.pointer.pointerInput
|
|||||||
import androidx.compose.ui.input.pointer.util.VelocityTracker
|
import androidx.compose.ui.input.pointer.util.VelocityTracker
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import eu.wewox.pagecurl.ExperimentalPageCurlApi
|
import eu.wewox.pagecurl.ExperimentalPageCurlApi
|
||||||
import eu.wewox.pagecurl.config.CurlDirection
|
import eu.wewox.pagecurl.config.DragDirection
|
||||||
import eu.wewox.pagecurl.utils.rotate
|
import eu.wewox.pagecurl.utils.rotate
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
|
||||||
|
@ExperimentalPageCurlApi
|
||||||
|
internal fun Modifier.curlGesture(
|
||||||
|
state: PageCurlState.InternalState,
|
||||||
|
enabled: Boolean,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
direction: DragDirection,
|
||||||
|
start: Edge,
|
||||||
|
end: Edge,
|
||||||
|
edge: Animatable<Edge, AnimationVector4D>,
|
||||||
|
onChange: () -> Unit,
|
||||||
|
): Modifier =
|
||||||
|
curlGesture(
|
||||||
|
key = state,
|
||||||
|
enabled = enabled,
|
||||||
|
direction = direction,
|
||||||
|
onStart = {
|
||||||
|
scope.launch {
|
||||||
|
state.animateJob?.cancel()
|
||||||
|
edge.snapTo(start)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCurl = { a, b ->
|
||||||
|
scope.launch {
|
||||||
|
edge.animateTo(Edge(a, b))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEnd = {
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
edge.animateTo(end)
|
||||||
|
} finally {
|
||||||
|
onChange()
|
||||||
|
edge.snapTo(start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel = {
|
||||||
|
scope.launch {
|
||||||
|
edge.animateTo(start)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@ExperimentalPageCurlApi
|
@ExperimentalPageCurlApi
|
||||||
internal fun Modifier.curlGesture(
|
internal fun Modifier.curlGesture(
|
||||||
key: Any?,
|
key: Any?,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
direction: CurlDirection,
|
direction: DragDirection,
|
||||||
onStart: () -> Unit,
|
onStart: () -> Unit,
|
||||||
onCurl: (Offset, Offset) -> Unit,
|
onCurl: (Offset, Offset) -> Unit,
|
||||||
onEnd: () -> Unit,
|
onEnd: () -> Unit,
|
||||||
|
@@ -1,27 +1,14 @@
|
|||||||
package eu.wewox.pagecurl.page
|
package eu.wewox.pagecurl.page
|
||||||
|
|
||||||
import androidx.compose.animation.core.Animatable
|
|
||||||
import androidx.compose.animation.core.AnimationVector4D
|
|
||||||
import androidx.compose.animation.core.TwoWayConverter
|
|
||||||
import androidx.compose.animation.core.VisibilityThreshold
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.saveable.Saver
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.unit.Constraints
|
|
||||||
import eu.wewox.pagecurl.ExperimentalPageCurlApi
|
import eu.wewox.pagecurl.ExperimentalPageCurlApi
|
||||||
import eu.wewox.pagecurl.config.CurlDirection
|
|
||||||
import eu.wewox.pagecurl.config.PageCurlConfig
|
import eu.wewox.pagecurl.config.PageCurlConfig
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@ExperimentalPageCurlApi
|
@ExperimentalPageCurlApi
|
||||||
@Composable
|
@Composable
|
||||||
@@ -42,23 +29,23 @@ public fun PageCurl(
|
|||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.curlGesture(
|
.curlGesture(
|
||||||
key = internalState,
|
state = internalState,
|
||||||
enabled = updatedCurrent < state.max - 1,
|
enabled = updatedCurrent < state.max - 1,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
direction = config.interaction.forward,
|
direction = config.interaction.forward,
|
||||||
start = internalState.rightCurl,
|
start = internalState.rightEdge,
|
||||||
end = internalState.leftCurl,
|
end = internalState.leftEdge,
|
||||||
animatable = internalState.forward,
|
edge = internalState.forward,
|
||||||
onChange = { state.current = updatedCurrent + 1 }
|
onChange = { state.current = updatedCurrent + 1 }
|
||||||
)
|
)
|
||||||
.curlGesture(
|
.curlGesture(
|
||||||
key = internalState,
|
state = internalState,
|
||||||
enabled = updatedCurrent > 0,
|
enabled = updatedCurrent > 0,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
direction = config.interaction.backward,
|
direction = config.interaction.backward,
|
||||||
start = internalState.leftCurl,
|
start = internalState.leftEdge,
|
||||||
end = internalState.rightCurl,
|
end = internalState.rightEdge,
|
||||||
animatable = internalState.backward,
|
edge = internalState.backward,
|
||||||
onChange = { state.current = updatedCurrent - 1 }
|
onChange = { state.current = updatedCurrent - 1 }
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -67,141 +54,16 @@ public fun PageCurl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (updatedCurrent < state.max) {
|
if (updatedCurrent < state.max) {
|
||||||
Box(Modifier.drawCurl(config.curl, internalState.forward.value.a, internalState.forward.value.b)) {
|
Box(Modifier.drawCurl(config.curl, internalState.forward.value.top, internalState.forward.value.bottom)) {
|
||||||
content(updatedCurrent)
|
content(updatedCurrent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedCurrent > 0) {
|
if (updatedCurrent > 0) {
|
||||||
Box(Modifier.drawCurl(config.curl, internalState.backward.value.a, internalState.backward.value.b)) {
|
Box(Modifier.drawCurl(config.curl, internalState.backward.value.top, internalState.backward.value.bottom)) {
|
||||||
content(updatedCurrent - 1)
|
content(updatedCurrent - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalPageCurlApi
|
|
||||||
@Composable
|
|
||||||
public fun rememberPageCurlState(
|
|
||||||
max: Int,
|
|
||||||
initialCurrent: Int = 0,
|
|
||||||
): PageCurlState =
|
|
||||||
rememberSaveable(
|
|
||||||
max, initialCurrent,
|
|
||||||
saver = Saver(
|
|
||||||
save = { it.current },
|
|
||||||
restore = {
|
|
||||||
PageCurlState(
|
|
||||||
max = it,
|
|
||||||
initialCurrent = initialCurrent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
PageCurlState(
|
|
||||||
max = max,
|
|
||||||
initialCurrent = initialCurrent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalPageCurlApi
|
|
||||||
public class PageCurlState(
|
|
||||||
public val max: Int,
|
|
||||||
initialCurrent: Int = 0,
|
|
||||||
) {
|
|
||||||
public var current: Int by mutableStateOf(initialCurrent)
|
|
||||||
internal set
|
|
||||||
|
|
||||||
internal var internalState: InternalState? by mutableStateOf(null)
|
|
||||||
|
|
||||||
internal fun setup(constraints: Constraints) {
|
|
||||||
if (internalState?.constraints == constraints) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val maxWidthPx = constraints.maxWidth.toFloat()
|
|
||||||
val maxHeightPx = constraints.maxHeight.toFloat()
|
|
||||||
|
|
||||||
val left = Curl(Offset(0f, 0f), Offset(0f, maxHeightPx))
|
|
||||||
val right = Curl(Offset(maxWidthPx, 0f), Offset(maxWidthPx, maxHeightPx))
|
|
||||||
|
|
||||||
val forward = Animatable(right, Curl.VectorConverter, Curl.VisibilityThreshold)
|
|
||||||
val backward = Animatable(left, Curl.VectorConverter, Curl.VisibilityThreshold)
|
|
||||||
|
|
||||||
internalState = InternalState(constraints, left, right, forward, backward)
|
|
||||||
}
|
|
||||||
|
|
||||||
public suspend fun snapTo(value: Int) {
|
|
||||||
internalState?.reset()
|
|
||||||
current = value
|
|
||||||
}
|
|
||||||
|
|
||||||
internal data class InternalState(
|
|
||||||
val constraints: Constraints,
|
|
||||||
val leftCurl: Curl,
|
|
||||||
val rightCurl: Curl,
|
|
||||||
val forward: Animatable<Curl, AnimationVector4D>,
|
|
||||||
val backward: Animatable<Curl, AnimationVector4D>,
|
|
||||||
) {
|
|
||||||
internal suspend fun reset() {
|
|
||||||
forward.snapTo(rightCurl)
|
|
||||||
backward.snapTo(leftCurl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalPageCurlApi
|
|
||||||
private fun Modifier.curlGesture(
|
|
||||||
key: Any?,
|
|
||||||
enabled: Boolean,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
direction: CurlDirection,
|
|
||||||
start: Curl,
|
|
||||||
end: Curl,
|
|
||||||
animatable: Animatable<Curl, AnimationVector4D>,
|
|
||||||
onChange: () -> Unit,
|
|
||||||
): Modifier =
|
|
||||||
curlGesture(
|
|
||||||
key = key,
|
|
||||||
enabled = enabled,
|
|
||||||
direction = direction,
|
|
||||||
onStart = {
|
|
||||||
scope.launch {
|
|
||||||
animatable.snapTo(start)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onCurl = { a, b ->
|
|
||||||
scope.launch {
|
|
||||||
animatable.animateTo(Curl(a, b))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onEnd = {
|
|
||||||
scope.launch {
|
|
||||||
try {
|
|
||||||
animatable.animateTo(end)
|
|
||||||
} finally {
|
|
||||||
onChange()
|
|
||||||
animatable.snapTo(start)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onCancel = {
|
|
||||||
scope.launch {
|
|
||||||
animatable.animateTo(start)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
internal data class Curl(val a: Offset, val b: Offset) {
|
|
||||||
companion object {
|
|
||||||
val VectorConverter: TwoWayConverter<Curl, AnimationVector4D> =
|
|
||||||
TwoWayConverter(
|
|
||||||
convertToVector = { AnimationVector4D(it.a.x, it.a.y, it.b.x, it.b.y) },
|
|
||||||
convertFromVector = { Curl(Offset(it.v1, it.v2), Offset(it.v3, it.v4)) }
|
|
||||||
)
|
|
||||||
|
|
||||||
val VisibilityThreshold: Curl =
|
|
||||||
Curl(Offset.VisibilityThreshold, Offset.VisibilityThreshold)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
181
pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt
Normal file
181
pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package eu.wewox.pagecurl.page
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.AnimationVector4D
|
||||||
|
import androidx.compose.animation.core.TwoWayConverter
|
||||||
|
import androidx.compose.animation.core.VisibilityThreshold
|
||||||
|
import androidx.compose.animation.core.keyframes
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.unit.Constraints
|
||||||
|
import eu.wewox.pagecurl.ExperimentalPageCurlApi
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@ExperimentalPageCurlApi
|
||||||
|
@Composable
|
||||||
|
public fun rememberPageCurlState(
|
||||||
|
max: Int,
|
||||||
|
initialCurrent: Int = 0,
|
||||||
|
): PageCurlState =
|
||||||
|
rememberSaveable(
|
||||||
|
max, initialCurrent,
|
||||||
|
saver = Saver(
|
||||||
|
save = { it.current },
|
||||||
|
restore = {
|
||||||
|
PageCurlState(
|
||||||
|
max = it,
|
||||||
|
initialCurrent = initialCurrent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
PageCurlState(
|
||||||
|
max = max,
|
||||||
|
initialCurrent = initialCurrent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalPageCurlApi
|
||||||
|
public class PageCurlState(
|
||||||
|
public val max: Int,
|
||||||
|
initialCurrent: Int = 0,
|
||||||
|
) {
|
||||||
|
public var current: Int by mutableStateOf(initialCurrent)
|
||||||
|
internal set
|
||||||
|
|
||||||
|
internal var internalState: InternalState? by mutableStateOf(null)
|
||||||
|
|
||||||
|
internal fun setup(constraints: Constraints) {
|
||||||
|
if (internalState?.constraints == constraints) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxWidthPx = constraints.maxWidth.toFloat()
|
||||||
|
val maxHeightPx = constraints.maxHeight.toFloat()
|
||||||
|
|
||||||
|
val left = Edge(Offset(0f, 0f), Offset(0f, maxHeightPx))
|
||||||
|
val right = Edge(Offset(maxWidthPx, 0f), Offset(maxWidthPx, maxHeightPx))
|
||||||
|
|
||||||
|
val forward = Animatable(right, Edge.VectorConverter, Edge.VisibilityThreshold)
|
||||||
|
val backward = Animatable(left, Edge.VectorConverter, Edge.VisibilityThreshold)
|
||||||
|
|
||||||
|
internalState = InternalState(constraints, left, right, forward, backward)
|
||||||
|
}
|
||||||
|
|
||||||
|
public suspend fun snapTo(value: Int) {
|
||||||
|
current = value.coerceIn(0, max - 1)
|
||||||
|
internalState?.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
public suspend fun next(block: suspend Animatable<Edge, AnimationVector4D>.(Size) -> Unit = DefaultNext) {
|
||||||
|
internalState?.animateTo(
|
||||||
|
target = { current + 1 },
|
||||||
|
animate = { forward.block(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public suspend fun prev(block: suspend Animatable<Edge, AnimationVector4D>.(Size) -> Unit = DefaultPrev) {
|
||||||
|
internalState?.animateTo(
|
||||||
|
target = { current - 1 },
|
||||||
|
animate = { backward.block(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inner class InternalState(
|
||||||
|
val constraints: Constraints,
|
||||||
|
val leftEdge: Edge,
|
||||||
|
val rightEdge: Edge,
|
||||||
|
val forward: Animatable<Edge, AnimationVector4D>,
|
||||||
|
val backward: Animatable<Edge, AnimationVector4D>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
var animateJob: Job? = null
|
||||||
|
|
||||||
|
suspend fun reset() {
|
||||||
|
forward.snapTo(rightEdge)
|
||||||
|
backward.snapTo(leftEdge)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun animateTo(
|
||||||
|
target: () -> Int,
|
||||||
|
animate: suspend InternalState.(Size) -> Unit
|
||||||
|
) {
|
||||||
|
animateJob?.cancel()
|
||||||
|
|
||||||
|
val targetIndex = target()
|
||||||
|
if (targetIndex < 0 || targetIndex >= max) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
coroutineScope {
|
||||||
|
animateJob = launch {
|
||||||
|
try {
|
||||||
|
reset()
|
||||||
|
animate(Size(constraints.maxWidth.toFloat(), constraints.maxHeight.toFloat()))
|
||||||
|
} finally {
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
snapTo(target())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public data class Edge(val top: Offset, val bottom: Offset) {
|
||||||
|
internal companion object {
|
||||||
|
val VectorConverter: TwoWayConverter<Edge, AnimationVector4D> =
|
||||||
|
TwoWayConverter(
|
||||||
|
convertToVector = { AnimationVector4D(it.top.x, it.top.y, it.bottom.x, it.bottom.y) },
|
||||||
|
convertFromVector = { Edge(Offset(it.v1, it.v2), Offset(it.v3, it.v4)) }
|
||||||
|
)
|
||||||
|
|
||||||
|
val VisibilityThreshold: Edge =
|
||||||
|
Edge(Offset.VisibilityThreshold, Offset.VisibilityThreshold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val DefaultNext: suspend Animatable<Edge, AnimationVector4D>.(Size) -> Unit = { size ->
|
||||||
|
animateTo(
|
||||||
|
targetValue = size.start,
|
||||||
|
animationSpec = keyframes {
|
||||||
|
durationMillis = DefaultAnimDuration
|
||||||
|
size.end at 0
|
||||||
|
size.middle at DefaultMidPointDuration
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val DefaultPrev: suspend Animatable<Edge, AnimationVector4D>.(Size) -> Unit = { size ->
|
||||||
|
animateTo(
|
||||||
|
targetValue = size.end,
|
||||||
|
animationSpec = keyframes {
|
||||||
|
durationMillis = DefaultAnimDuration
|
||||||
|
size.start at 0
|
||||||
|
size.middle at DefaultAnimDuration - DefaultMidPointDuration
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val DefaultAnimDuration: Int = 450
|
||||||
|
private const val DefaultMidPointDuration: Int = 150
|
||||||
|
|
||||||
|
private val Size.start: Edge
|
||||||
|
get() = Edge(Offset(0f, 0f), Offset(0f, height))
|
||||||
|
|
||||||
|
private val Size.middle: Edge
|
||||||
|
get() = Edge(Offset(width, height / 2f), Offset(width / 2f, height))
|
||||||
|
|
||||||
|
private val Size.end: Edge
|
||||||
|
get() = Edge(Offset(width, height), Offset(width, height))
|
Reference in New Issue
Block a user