For my android app i'm using a custom modifier which draws a dashed border as detailed in this stackOverflow answer.
I now want to conditionally animate that dashed border: If the view is currently selected, then animate, else pause.
I managed to implement this behavior using an infinite transition.
However, there's some "jumping" motion when toggling animate
, which i'd like to avoid, e.g. by just pausing the infiniteTransition
. But how do i do this?
fun Modifier.dashedBorder(
color: Color,
strokeWidth: Dp,
strokeLength: Dp,
animate: Boolean = false,
) = composed(
factory = {
val density = LocalDensity.current
val strokeWidthPx = density.run { strokeWidth.toPx() }
val strokeLengthPx = density.run { strokeLength.toPx() }
val infiniteTransition = rememberInfiniteTransition()
val offset by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = strokeLengthPx * 2,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Restart
)
)
this.then(
Modifier.drawWithCache {
onDrawBehind {
val stroke = Stroke(
width = strokeWidthPx,
pathEffect = PathEffect.dashPathEffect(
intervals = floatArrayOf(strokeLengthPx, strokeLengthPx),
phase = if (animate) offset else 0f, // <-- cause of jumping motion
)
)
drawRect(
color = color,
style = stroke,
)
}
}
)
}
)
Hope it will be helpful to you:
fun Modifier.dashedBorder(
color: Color,
strokeWidth: Dp,
strokeLength: Dp,
animate: Boolean = true,
) = composed(
factory = {
val density = LocalDensity.current
val strokeWidthPx = density.run { strokeWidth.toPx() }
val strokeLengthPx = density.run { strokeLength.toPx() }
// store the last animate value be next animate start
var lastAnimValue by remember { mutableStateOf(0f) }
val anim = remember(animate) { Animatable(lastAnimValue) }
LaunchedEffect(animate) {
if (animate) {
anim.animateTo(
// Important !!! Animate Target need add the lastAnim value, Simple math knowledge :)
(strokeLengthPx * 2 + lastAnimValue), animationSpec =
infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Restart,
)
) {
lastAnimValue = value // store the anim value
}
}
}
this.then(
Modifier.drawWithCache {
onDrawBehind {
val stroke = Stroke(
width = strokeWidthPx,
pathEffect = PathEffect.dashPathEffect(
intervals = floatArrayOf(strokeLengthPx, strokeLengthPx),
phase = anim.value, // always use the anim
)
)
drawRect(
color = color,
style = stroke,
)
}
}
)
}
)