Add MomentumAnimation for catalog
This commit is contained in:
1 parent
60a9493237
commit
8faad0217b
7 files changed
+224
-138
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -24,6 +24,8 @@ fun RowScope.LiquidBottomTab(
|
||||
modifier
|
||||
.clip(ContinuousCapsule)
|
||||
.clickable(
|
||||
interactionSource = null,
|
||||
indication = null,
|
||||
role = Role.Tab,
|
||||
onClick = onClick
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.kyant.backdrop.catalog.components
|
||||
import android.graphics.RuntimeShader
|
||||
import android.os.Build
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -14,6 +15,7 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -36,8 +38,6 @@ import androidx.compose.ui.graphics.layer.drawLayer
|
||||
import androidx.compose.ui.graphics.rememberGraphicsLayer
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.input.pointer.util.VelocityTracker
|
||||
import androidx.compose.ui.input.pointer.util.addPointerInputChange
|
||||
import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
@@ -51,6 +51,7 @@ import com.kyant.backdrop.backdrops.layerBackdrop
|
||||
import com.kyant.backdrop.backdrops.rememberCombinedBackdrop
|
||||
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
|
||||
import com.kyant.backdrop.catalog.utils.inspectDragGestures
|
||||
import com.kyant.backdrop.catalog.utils.rememberMomentumAnimation
|
||||
import com.kyant.backdrop.drawBackdrop
|
||||
import com.kyant.backdrop.effects.blur
|
||||
import com.kyant.backdrop.effects.refraction
|
||||
@@ -60,9 +61,12 @@ import com.kyant.backdrop.highlight.Highlight
|
||||
import com.kyant.backdrop.shadow.InnerShadow
|
||||
import com.kyant.backdrop.shadow.Shadow
|
||||
import com.kyant.capsule.ContinuousCapsule
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sign
|
||||
|
||||
@Composable
|
||||
fun LiquidBottomTabs(
|
||||
@@ -104,10 +108,15 @@ fun LiquidBottomTabs(
|
||||
|
||||
val animationScope = rememberCoroutineScope()
|
||||
var pressStartPosition by remember { mutableStateOf(Offset.Zero) }
|
||||
val progressAnimation = remember { Animatable(0f) }
|
||||
val scaleXAnimation = remember { Animatable(0f) }
|
||||
val scaleYAnimation = remember { Animatable(0f) }
|
||||
val velocityAnimation = remember { Animatable(0f) }
|
||||
val offsetAnimation = remember { Animatable(0f) }
|
||||
val panelOffset by remember(density) {
|
||||
derivedStateOf {
|
||||
val fraction = (offsetAnimation.value / constraints.maxWidth).fastCoerceIn(-1f, 1f)
|
||||
with(density) {
|
||||
4f.dp.toPx() * fraction.sign * EaseOut.transform(abs(fraction))
|
||||
}
|
||||
}
|
||||
}
|
||||
val tabOffsetAnimation = remember(selectedTabIndex, tabWidth) {
|
||||
Animatable(
|
||||
Snapshot.withoutReadObservation {
|
||||
@@ -115,29 +124,9 @@ fun LiquidBottomTabs(
|
||||
}
|
||||
)
|
||||
}
|
||||
var isDragging by remember { mutableStateOf(false) }
|
||||
|
||||
val onDragStart = {
|
||||
animationScope.launch {
|
||||
launch {
|
||||
progressAnimation.animateTo(
|
||||
1f,
|
||||
spring(1f, 800f, 0.001f)
|
||||
)
|
||||
}
|
||||
launch {
|
||||
scaleXAnimation.animateTo(
|
||||
1f,
|
||||
spring(0.3f, 300f, 0.001f)
|
||||
)
|
||||
}
|
||||
launch {
|
||||
scaleYAnimation.animateTo(
|
||||
1f,
|
||||
spring(0.5f, 300f, 0.001f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val momentumAnimation = rememberMomentumAnimation(maxScale = 1.333f)
|
||||
|
||||
val onDragStop = {
|
||||
animationScope.launch {
|
||||
@@ -152,24 +141,6 @@ fun LiquidBottomTabs(
|
||||
)
|
||||
}
|
||||
}
|
||||
launch {
|
||||
progressAnimation.animateTo(
|
||||
0f,
|
||||
spring(1f, 800f, 0.001f)
|
||||
)
|
||||
}
|
||||
launch {
|
||||
scaleXAnimation.animateTo(
|
||||
0f,
|
||||
spring(0.3f, 300f, 0.001f)
|
||||
)
|
||||
}
|
||||
launch {
|
||||
scaleYAnimation.animateTo(
|
||||
0f,
|
||||
spring(0.5f, 300f, 0.001f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,25 +150,29 @@ fun LiquidBottomTabs(
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
var job: Job? = null
|
||||
|
||||
snapshotFlow { selectedTabIndex() }
|
||||
.drop(1)
|
||||
.collect { index ->
|
||||
val targetOffset = index * tabWidth
|
||||
if (progressAnimation.targetValue != 1f) {
|
||||
launch {
|
||||
onDragStart()
|
||||
delay(200)
|
||||
onDragStop()
|
||||
}
|
||||
} else {
|
||||
onDragStop()
|
||||
}
|
||||
launch {
|
||||
tabOffsetAnimation.animateTo(
|
||||
targetOffset,
|
||||
spring(1f, 800f, 0.5f)
|
||||
)
|
||||
}
|
||||
|
||||
job?.cancel()
|
||||
job = null
|
||||
if (!isDragging) {
|
||||
job = launch {
|
||||
momentumAnimation.startPressingAnimation()
|
||||
delay(200L)
|
||||
momentumAnimation.endPressingAnimation()
|
||||
}
|
||||
}
|
||||
isDragging = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +198,7 @@ half4 main(float2 coord) {
|
||||
}
|
||||
|
||||
val drawInteractiveHighlightModifier = Modifier.drawWithContent {
|
||||
val progress = progressAnimation.value.fastCoerceIn(0f, 1f)
|
||||
val progress = momentumAnimation.progress
|
||||
if (progress > 0f) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && interactiveHighlightShader is RuntimeShader) {
|
||||
drawRect(
|
||||
@@ -258,6 +233,9 @@ half4 main(float2 coord) {
|
||||
|
||||
Row(
|
||||
Modifier
|
||||
.graphicsLayer {
|
||||
translationX = panelOffset
|
||||
}
|
||||
.drawBackdrop(
|
||||
backdrop = backdrop,
|
||||
shape = { ContinuousCapsule },
|
||||
@@ -267,7 +245,7 @@ half4 main(float2 coord) {
|
||||
refraction(24f.dp.toPx(), 24f.dp.toPx())
|
||||
},
|
||||
layerBlock = {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
val scale = lerp(1f, 1f + 2f.dp.toPx() / size.height, progress)
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
@@ -295,11 +273,14 @@ half4 main(float2 coord) {
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.graphicsLayer {
|
||||
translationX = panelOffset
|
||||
}
|
||||
.drawBackdrop(
|
||||
backdrop = backdrop,
|
||||
shape = { ContinuousCapsule },
|
||||
highlight = {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
Highlight.Default.copy(alpha = progress)
|
||||
},
|
||||
effects = {
|
||||
@@ -316,10 +297,11 @@ half4 main(float2 coord) {
|
||||
Box(
|
||||
Modifier
|
||||
.graphicsLayer {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
val scale = lerp(1f, 1f + 2f.dp.toPx() / size.height, progress)
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
translationX = panelOffset
|
||||
}
|
||||
.drawBehind {
|
||||
drawLayer(tabsLayer)
|
||||
@@ -332,102 +314,83 @@ half4 main(float2 coord) {
|
||||
Box(
|
||||
Modifier
|
||||
.layout { measurable, constraints ->
|
||||
val padding = (4f.dp.toPx() * (1f - progressAnimation.value)).fastRoundToInt()
|
||||
val progress = momentumAnimation.progress
|
||||
val padding = (4f.dp.toPx() * (1f - progress)).fastRoundToInt()
|
||||
val placeable = measurable.measure(constraints.offset(-padding * 2, 0))
|
||||
layout(placeable.width + padding, placeable.height) {
|
||||
placeable.place(padding, 0)
|
||||
}
|
||||
}
|
||||
.graphicsLayer {
|
||||
translationX = tabOffsetAnimation.value.fastCoerceIn(0f, size.width * (tabsCount - 1))
|
||||
translationX =
|
||||
tabOffsetAnimation.value.fastCoerceIn(0f, size.width * (tabsCount - 1)) +
|
||||
panelOffset
|
||||
}
|
||||
.pointerInput(Unit) {
|
||||
val velocityTracker = VelocityTracker()
|
||||
|
||||
val updateVelocity: (Float) -> Unit = { v ->
|
||||
val alpha = 0.05f
|
||||
val targetVelocity = velocityAnimation.velocity * (1f - alpha) + v * alpha
|
||||
animationScope.launch {
|
||||
velocityAnimation.snapTo(targetVelocity)
|
||||
}
|
||||
}
|
||||
val settleVelocity: () -> Unit = {
|
||||
animationScope.launch {
|
||||
velocityAnimation.animateTo(
|
||||
0f,
|
||||
spring(0.5f, 500f, 1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
inspectDragGestures(
|
||||
onDragStart = { down ->
|
||||
velocityTracker.addPointerInputChange(down)
|
||||
|
||||
isDragging = true
|
||||
pressStartPosition = down.position
|
||||
onDragStart()
|
||||
},
|
||||
onDragEnd = { change ->
|
||||
velocityTracker.addPointerInputChange(change)
|
||||
settleVelocity()
|
||||
velocityTracker.resetTracking()
|
||||
|
||||
onDragEnd = {
|
||||
settle()
|
||||
onDragStop()
|
||||
animationScope.launch {
|
||||
offsetAnimation.animateTo(
|
||||
0f,
|
||||
spring(1f, 300f, 0.5f)
|
||||
)
|
||||
}
|
||||
},
|
||||
onDragCancel = {
|
||||
settleVelocity()
|
||||
velocityTracker.resetTracking()
|
||||
|
||||
settle()
|
||||
onDragStop()
|
||||
}
|
||||
) { change, dragAmount ->
|
||||
velocityTracker.addPointerInputChange(change)
|
||||
updateVelocity(velocityTracker.calculateVelocity().x)
|
||||
|
||||
val targetOffset =
|
||||
) { _, dragAmount ->
|
||||
val targetTabOffset =
|
||||
(tabOffsetAnimation.value + dragAmount.x)
|
||||
.fastCoerceIn(0f, size.width.toFloat() * 3)
|
||||
animationScope.launch {
|
||||
tabOffsetAnimation.snapTo(targetOffset)
|
||||
}
|
||||
.fastCoerceIn(0f, size.width.toFloat() * (tabsCount - 1))
|
||||
animationScope.launch { tabOffsetAnimation.snapTo(targetTabOffset) }
|
||||
|
||||
val targetOffset = offsetAnimation.value + dragAmount.x
|
||||
animationScope.launch { offsetAnimation.snapTo(targetOffset) }
|
||||
}
|
||||
}
|
||||
.then(momentumAnimation.modifier)
|
||||
.drawBackdrop(
|
||||
backdrop = rememberCombinedBackdrop(backdrop, tabsBackdrop),
|
||||
shape = { ContinuousCapsule },
|
||||
highlight = {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
Highlight.Default.copy(alpha = progress)
|
||||
},
|
||||
shadow = {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
Shadow(alpha = progress)
|
||||
},
|
||||
innerShadow = {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
InnerShadow(
|
||||
radius = 8f.dp * progress,
|
||||
alpha = progress
|
||||
)
|
||||
},
|
||||
effects = {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
refractionWithDispersion(
|
||||
10f.dp.toPx() * progress,
|
||||
12f.dp.toPx() * progress
|
||||
)
|
||||
},
|
||||
layerBlock = {
|
||||
scaleX = lerp(1f, 1f + 20f.dp.toPx() / size.height, scaleXAnimation.value)
|
||||
scaleY = lerp(1f, 1f + 20f.dp.toPx() / size.height, scaleYAnimation.value)
|
||||
val velocity = velocityAnimation.value / size.width
|
||||
scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f)
|
||||
scaleX = momentumAnimation.scaleX
|
||||
scaleY = momentumAnimation.scaleY
|
||||
val velocity = momentumAnimation.velocity / 5000f
|
||||
scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.15f, 0.15f)
|
||||
scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.15f, 0.15f)
|
||||
},
|
||||
onDrawSurface = {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
drawRect(
|
||||
if (isLightTheme) Color.Black.copy(0.1f)
|
||||
else Color.White.copy(0.1f),
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.kyant.backdrop.catalog.components
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
@@ -16,7 +14,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -32,13 +29,14 @@ import com.kyant.backdrop.Backdrop
|
||||
import com.kyant.backdrop.backdrops.layerBackdrop
|
||||
import com.kyant.backdrop.backdrops.rememberCombinedBackdrop
|
||||
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
|
||||
import com.kyant.backdrop.catalog.utils.rememberMomentumAnimation
|
||||
import com.kyant.backdrop.drawBackdrop
|
||||
import com.kyant.backdrop.effects.blur
|
||||
import com.kyant.backdrop.effects.refractionWithDispersion
|
||||
import com.kyant.backdrop.highlight.Highlight
|
||||
import com.kyant.backdrop.shadow.InnerShadow
|
||||
import com.kyant.backdrop.shadow.Shadow
|
||||
import com.kyant.capsule.ContinuousCapsule
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun LiquidSlider(
|
||||
@@ -63,12 +61,10 @@ fun LiquidSlider(
|
||||
}
|
||||
}
|
||||
|
||||
val animationScope = rememberCoroutineScope()
|
||||
val progressAnimationSpec = spring(0.5f, 300f, 0.001f)
|
||||
val progressAnimation = remember { Animatable(0f) }
|
||||
|
||||
val trackBackdrop = rememberLayerBackdrop()
|
||||
|
||||
val momentumAnimation = rememberMomentumAnimation(maxScale = 1.5f)
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
@@ -116,51 +112,47 @@ fun LiquidSlider(
|
||||
val targetValue = lerp(valueRange.start, valueRange.endInclusive, targetFraction)
|
||||
onValueChange(targetValue.fastCoerceIn(valueRange.start, valueRange.endInclusive))
|
||||
},
|
||||
Orientation.Horizontal,
|
||||
startDragImmediately = true,
|
||||
onDragStarted = {
|
||||
animationScope.launch {
|
||||
progressAnimation.animateTo(1f, progressAnimationSpec)
|
||||
}
|
||||
},
|
||||
onDragStopped = {
|
||||
animationScope.launch {
|
||||
progressAnimation.animateTo(0f, progressAnimationSpec)
|
||||
}
|
||||
}
|
||||
Orientation.Horizontal
|
||||
)
|
||||
.then(momentumAnimation.modifier)
|
||||
.drawBackdrop(
|
||||
backdrop = rememberCombinedBackdrop(backdrop, trackBackdrop),
|
||||
shape = { ContinuousCapsule },
|
||||
highlight = {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
Highlight.AmbientDefault.copy(alpha = progress)
|
||||
},
|
||||
shadow = {
|
||||
Shadow(
|
||||
radius = 4f.dp,
|
||||
color = Color.Black.copy(0.05f)
|
||||
color = Color.Black.copy(alpha = 0.05f)
|
||||
)
|
||||
},
|
||||
innerShadow = {
|
||||
val progress = progressAnimation.value
|
||||
val progress = momentumAnimation.progress
|
||||
InnerShadow(
|
||||
radius = 4f.dp * progress,
|
||||
alpha = progress
|
||||
)
|
||||
},
|
||||
effects = {
|
||||
refractionWithDispersion(6f.dp.toPx(), size.height / 2f)
|
||||
val progress = momentumAnimation.progress
|
||||
blur(8f.dp.toPx() * (1f - progress))
|
||||
refractionWithDispersion(
|
||||
height = 6f.dp.toPx() * progress,
|
||||
amount = size.height / 2f * progress
|
||||
)
|
||||
},
|
||||
layerBlock = {
|
||||
val progress = progressAnimation.value
|
||||
val scale = lerp(1f, 1.5f, progress)
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
scaleX = momentumAnimation.scaleX
|
||||
scaleY = momentumAnimation.scaleY
|
||||
val velocity = momentumAnimation.velocity / 5000f
|
||||
scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.15f, 0.15f)
|
||||
scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.15f, 0.15f)
|
||||
},
|
||||
onDrawSurface = {
|
||||
val progress = progressAnimation.value.fastCoerceIn(0f, 1f)
|
||||
drawRect(Color.White.copy(1f - progress))
|
||||
val progress = momentumAnimation.progress
|
||||
drawRect(Color.White.copy(alpha = 1f - progress))
|
||||
}
|
||||
)
|
||||
.size(40f.dp, 24f.dp)
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.kyant.backdrop.catalog.utils
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.input.pointer.util.VelocityTracker
|
||||
import androidx.compose.ui.input.pointer.util.addPointerInputChange
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun rememberMomentumAnimation(
|
||||
maxScale: Float,
|
||||
progressAnimationSpec: FiniteAnimationSpec<Float> =
|
||||
spring(1f, 1000f, 0.01f),
|
||||
velocityAnimationSpec: FiniteAnimationSpec<Float> =
|
||||
spring(0.5f, 250f, 0.01f),
|
||||
scaleXAnimationSpec: FiniteAnimationSpec<Float> =
|
||||
spring(0.4f, 400f, 0.01f),
|
||||
scaleYAnimationSpec: FiniteAnimationSpec<Float> =
|
||||
spring(0.6f, 400f, 0.01f)
|
||||
): MomentumAnimation {
|
||||
val animationScope = rememberCoroutineScope()
|
||||
return remember(
|
||||
maxScale,
|
||||
animationScope,
|
||||
progressAnimationSpec,
|
||||
velocityAnimationSpec,
|
||||
scaleXAnimationSpec,
|
||||
scaleYAnimationSpec
|
||||
) {
|
||||
MomentumAnimation(
|
||||
maxScale = maxScale,
|
||||
animationScope = animationScope,
|
||||
progressAnimationSpec = progressAnimationSpec,
|
||||
velocityAnimationSpec = velocityAnimationSpec,
|
||||
scaleXAnimationSpec = scaleXAnimationSpec,
|
||||
scaleYAnimationSpec = scaleYAnimationSpec
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class MomentumAnimation(
|
||||
val maxScale: Float,
|
||||
private val animationScope: CoroutineScope,
|
||||
private val progressAnimationSpec: FiniteAnimationSpec<Float>,
|
||||
private val velocityAnimationSpec: FiniteAnimationSpec<Float>,
|
||||
private val scaleXAnimationSpec: FiniteAnimationSpec<Float>,
|
||||
private val scaleYAnimationSpec: FiniteAnimationSpec<Float>
|
||||
) {
|
||||
|
||||
private val velocityTracker = VelocityTracker()
|
||||
|
||||
private val progressAnimation = Animatable(0f)
|
||||
private val velocityAnimation = Animatable(0f)
|
||||
private val scaleXAnimation = Animatable(1f)
|
||||
private val scaleYAnimation = Animatable(1f)
|
||||
|
||||
val progress: Float get() = progressAnimation.value
|
||||
val velocity: Float get() = velocityAnimation.value
|
||||
val scaleX: Float get() = scaleXAnimation.value
|
||||
val scaleY: Float get() = scaleYAnimation.value
|
||||
|
||||
var isDragging: Boolean by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
val modifier: Modifier = Modifier.pointerInput(Unit) {
|
||||
inspectDragGestures(
|
||||
onDragStart = {
|
||||
isDragging = true
|
||||
velocityTracker.resetTracking()
|
||||
startPressingAnimation()
|
||||
},
|
||||
onDragEnd = { change ->
|
||||
isDragging = false
|
||||
val velocity = velocityTracker.calculateVelocity()
|
||||
updateVelocity(velocity)
|
||||
velocityTracker.addPointerInputChange(change)
|
||||
velocityTracker.resetTracking()
|
||||
endPressingAnimation()
|
||||
settleVelocity()
|
||||
},
|
||||
onDragCancel = {
|
||||
isDragging = false
|
||||
velocityTracker.resetTracking()
|
||||
endPressingAnimation()
|
||||
settleVelocity()
|
||||
}
|
||||
) { change, _ ->
|
||||
isDragging = true
|
||||
velocityTracker.addPointerInputChange(change)
|
||||
val velocity = velocityTracker.calculateVelocity()
|
||||
updateVelocity(velocity)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateVelocity(velocity: Velocity) {
|
||||
animationScope.launch { velocityAnimation.animateTo(velocity.x, velocityAnimationSpec) }
|
||||
}
|
||||
|
||||
private fun settleVelocity() {
|
||||
animationScope.launch { velocityAnimation.animateTo(0f, velocityAnimationSpec) }
|
||||
}
|
||||
|
||||
fun startPressingAnimation() {
|
||||
animationScope.launch {
|
||||
launch { progressAnimation.animateTo(1f, progressAnimationSpec) }
|
||||
launch { scaleXAnimation.animateTo(maxScale, scaleXAnimationSpec) }
|
||||
launch { scaleYAnimation.animateTo(maxScale, scaleYAnimationSpec) }
|
||||
}
|
||||
}
|
||||
|
||||
fun endPressingAnimation() {
|
||||
animationScope.launch {
|
||||
launch { progressAnimation.animateTo(0f, progressAnimationSpec) }
|
||||
launch { scaleXAnimation.animateTo(1f, scaleXAnimationSpec) }
|
||||
launch { scaleYAnimation.animateTo(1f, scaleYAnimationSpec) }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user