Yann Badoual
09/09/2020, 3:07 PMvar animationIndex by remember { mutableStateOf(0) }
val definition = remember(animationIndex) { buildTransition(animationIndex = animationIndex) }
val state = transition(
definition = definition,
initState = AnimationState.START,
toState = AnimationState.END,
onStateChangeFinished = {
if (animationIndex < animationCount) {
animationIndex++
}
}
)
This works, but feels weird, and has some drawbacks:
• Doesn't work well with @Preview animation inspector
• animationIndex
can't be used in the code depending on the animation, because when one animation is completed, there's a brief moment where animationIndex
was incremented but state
is still on the AnimationState.END
because the next one didn't start. So I have to add the index as a key inside the state to fix this. It works fine, but error proneDoris Liu
09/10/2020, 12:14 AMDoris Liu
09/10/2020, 12:16 AMYann Badoual
09/10/2020, 7:57 AMhttps://github.com/badoualy/kanji-strokeview/raw/master/ART/preview.gif▾
Path
that I animate (with a DashPathEffect
). Each stroke is considered as an independant animation => the interpolator/duration settings should be different for each stroke.
I feel like it's a mix case of keyframe and tween. In this sense, the waypoint concept would be nice, to be able to iterate through states easily.Yann Badoual
09/10/2020, 8:29 AMnextState
that I found in your sample, thanks!
But I think a wrapper API to avoid having to do that manually would be nice :)
private sealed class AnimatedStrokeState {
data class START(val strokeIndex: Int) : AnimatedStrokeState()
data class END(val strokeIndex: Int) : AnimatedStrokeState()
}
private fun buildTransition(
pathMeasureList: List<PathMeasure>
): TransitionDefinition<AnimatedStrokeState> {
return transitionDefinition {
pathMeasureList.forEachIndexed { i, pathMeasure ->
val startState = AnimatedStrokeState.START(i)
state(startState) {
this[StrokeIndex] = i
this[StrokeProgress] = 0f
}
val endState = AnimatedStrokeState.END(i)
state(endState) {
this[StrokeIndex] = i
this[StrokeProgress] = 1f
}
val isLastPath = i == pathMeasureList.lastIndex
transition(startState to endState) {
StrokeProgress using tween(
delayMillis = if (i == 0) 250 else 0,
durationMillis = (pathMeasure.length * 10L).toInt(), // Panoramix' formula
easing = FastOutSlowInEasing
)
if (!isLastPath) {
// Chain
nextState = AnimatedStrokeState.START(i + 1)
}
}
if (!isLastPath) {
snapTransition(
endState to AnimatedStrokeState.START(i + 1),
nextState = AnimatedStrokeState.END(i + 1)
)
}
}
}
}
Yann Badoual
09/10/2020, 11:09 AMval state = transition(
definition = definition,
initState = AnimatedStrokeState.START(0),
toState = AnimatedStrokeState.END(0)
)
Instead of something like:
val state = transition(
definition = definition,
initState = AnimatedStrokeState.START(0),
toState = AnimatedStrokeState.END(strokes.lastIndex)
)
which we could have with waypoint/intermediary steps
Also this approach doesn't make it possible to run the full animation on android studio's animation inspector. We can only inspect from one state to another: "nextState" is not usedDoris Liu
09/10/2020, 6:40 PMDoris Liu
09/10/2020, 6:42 PMtransition
) and the actual state that the animation ends on.Doris Liu
09/10/2020, 6:43 PMYann Badoual
09/10/2020, 7:40 PM