Hi <@UNH9ZT3NZ>, Is there a way to seek a Color An...
# compose
a
Hi @Doris Liu, Is there a way to seek a Color Animation based on percentage? I essentially need to animate between Colors, ex.
Color.Red -> Color.Yellow -> Color.Green
. I wanted to be able to get the color given a certain percentage. I tried
updateTransition(...)
but it seems that it only applies to a targeted state but not the in-betweens. Any advice? Thanks in advance.
a
Thank for the reply. Doesn’t
lerp
only accepts two value? and if so does that mean stitching two `lerp`s together?
d
In your example:
Color.Red -> Color.Yellow -> Color.Green
, at what percentage do you expect the color to be yellow? Or is yellow the color for a specific state?
a
Color.Red
at
0f
,
Color.Yellow
at
0.5f
and
Color.Green
at
1.0f
and if the value is in between ex.
0.25f
it must must be the color between
Color.Red and Color.Yellow
probably an orange-y color?
d
In that case you need to transform the input fraction first, specifically scale the fraction based on the range that it falls in. So
0.25f
would in the range [0f, 0.5f], and it's halfway in that range (i.e. 0.25f/(0.5f - 0f) = 50%), then you can use the
lerp
with colors that are specific for the [0, 0.5f] range.
a
thats what I thought as well… was just wondering whether there is a better way of doing it or an existing api. If im not mistaken
ObjectAnimator.ofArgb(Color.Red, Color.Yellow, Color.Green)
would do the same thing, and was just wondering it something similar was available in compose?
d
Oh boy
ObjectAnimator
really brings back memories. 🤣 I was under the impression that you wanted to manually seek the Colors. If the goal is to animate the color through a few way-point colors, consider using
keyframes
as your choice of
AnimationSpec
😄 1
You could do something like:
Copy code
keyframes<Color> {
    durationMillis = 100
    Color.Red at 0
    Color.Yellow at 50
    Color.Green at 100
}
See https://developer.android.com/reference/kotlin/androidx/compose/animation/core/KeyframesSpec
a
Wouldn’t I still need to provide a target color though? since this is only the
animationSpec
?
d
Yes, you need to provide a target color, and it needs to be the same as the entry in keyframes at the time of
durationMillis
if any. Alternatively, you could skip defining the color values at time 0 and time
durationMillis
and have the keyframes derive those from the initial value and target value. e.g.:
animateColorAsState(if (foo) Color.Red else Color.Green, animationSpec = keyframes { Color.yellow at durationMillis * 0.5f })
a
But then again, I dont really have a defined color to target.. I only have the percentage… so I guess the earlier idea of lerping would be the btter choice sint it?
d
What's your use case?
a
Maybe this would describe it better?
Copy code
Column {
    var percentage by remember {
        mutableStateOf(0.45f)
    }
                
    Slider(
        value = percentage,
        onValueChange = { value ->
            percentage = value
        },
    )

    val color = myCustomLerp(
        Color.Red, 
        Color.Yellow, 
        Color.Green, 
        percentage
    )
    ...
}
d
Yea, in this case a custom lerping is the easiest way to approach it. Alternatively, you could consider creating a
TargetBasedAnimation
using the keyframes, and drive it yourself: https://developer.android.com/jetpack/compose/animation#targetbasedanimation
❤️ 1
a
Alright! Thank you very much 😄
c
@Doris Liu In fact, this looks really complicated, isn’t there a simpler way to use it than
ObjectAnimator
. I want to make a scale animation, which are [1, 1.5, -1, 2], `ObjectAnimator`/`ValueAnimator` only needs to provide a duration and it will automatically handle the time corresponding to these values ​​for me, but Compose seems to be unable to... I miss it a bit.
a
I kind of agree.. Also I hope the animation api would have a seek capability. As for now though you could maybe use this lerp function I made if you needed the functionality:
Copy code
fun lerp(
    stops: List<Color>,
    fraction: Float
): Color {
    assert(stops.isNotEmpty())

    val segment: Float = 1f / (stops.size - 1)
    val startIndex = 0.coerceAtLeast((fraction / segment).toInt())
    val endIndex =  (startIndex + 1).coerceAtMost(stops.size - 1)

    val segmentFraction = (fraction % segment) / segment

    return androidx.compose.ui.graphics.lerp(stops[startIndex], stops[endIndex], segmentFraction)
}
The lerp function is for
Color
but you could easily replace it with whatever type you need.
d
Re: @Chachako For the example you gave, I'd do something like this in Compose:
Copy code
val anim = TargetBasedAnimation(
                initialValue = 1f,
                targetValue = 2f,
                typeConverter = Float.VectorConverter, animationSpec = keyframes {
                    durationMillis = 300
                    1.5f at 100
                    -1f at 200
                })
            var percentage : Float = 0.2f
            anim.getValueFromNanos((anim.durationNanos * percentage).toLong())
Re @Archie For single value seeking, I'd encourage you to use the
TargetBasedAnimation
. For
Transition
level seeking, we are working on a solution - there's a lot implications for seeking a
Transition
that we need to work out before we can comfortably put that feature in an API. 🙂
❤️ 2
c
@Doris Liu Thank you for your code. Is percentage for another
animAsState
? This is great, but I personally think that it’s a bit boilerplate code. Is it possible to solve this problem in the future?
d
There's a tradeoff between making the code extremely minimalistic vs coherent across the system. The former is not what we are going for. 🙂 We try to make common cases extremely easy to use, but for relatively uncommon use cases like this, more setup is to be expected.
❤️ 1
We may add an extension fun to TargetBasedAnimation:
TargetBasedAnimation.getValueByProgress(..)
since it seems like it could be useful in other cases as well.
c
The extension function seems good!
👍 1