Hello Jetpack Compose developers,
Today we’ll be finishing up our blog series on animations in Jetpack Compose! This content was inspired by Nicole Terc’s Composable Sheep talk from droidcon NYC.
Over the past two weeks, we covered some basics graphics, animation canvases, and basic animations. This week, we’ll polish up our garden with some more complex animation combos and background shaders.
Creating more complex animations
We’ll be continuing to use our drawSunflower
method and Sketch
composable to build animations in this post, so if you haven’t already, check out the previous blog posts to see how we built those functions!
So far, we’ve built a basic sunflower, then animated its rotation, size, and color individually. Now, to create more complex animations, let’s try animated multiple properties at once! We can combine petal color animations with rotation to create a rainbow spinner effect, and we can also combine the color, size, and rotation animations together for an exciting effect:
@Composable fun AnimatedPetalColorAndRotationSunflower() { Sketch( modifier = Modifier.size(100.dp), targetValue = 360f, animationSpec = infiniteRepeatable(tween(durationMillis = 3000, easing = LinearEasing)) ) { animationState -> drawSunflower(hue = animationState, rotation = animationState) }
} @Composable fun AnimatedEverythingSunflower() { Sketch( modifier = Modifier.size(100.dp), targetValue = 360f, animationSpec = infiniteRepeatable( tween(durationMillis = 800, delayMillis = 50, easing = LinearOutSlowInEasing), repeatMode = RepeatMode.Reverse
) ) { animationState ->
drawSunflower( sizePct = animationState / 360f, rotation = animationState, color = Color.hsv(hue = animationState, saturation = 1f, value = 1f) ) } }
Our garden looks colorful and dynamic now:
Adding a background shader
The last step to complete our garden is to surround the flowers with some vibrant grass, which we can do by adding a dynamic shader background. I started by checking out BackgroundShaderScreen.kt and GradientShaderScreen.kt to see how shaders were incorporated into the composable sheep project. For Android apps, you can use the Android Graphics Shading Language (AGSL), which is very similar to the popular OpenGL Shading Languade (GLSL) if you’re already familiar with shaders. Since our project is built with Compose Multiplatform, though, we also need to support the desktop version of the app. Instead of AGSL, for desktop we can use Skia’s Shading Language (SKSL).
Let’s first try to modify the GradientShaderScreen.kt code to show a nice blend of greens! Throughout this process, we can use the Skia Shaders Playground or the Shdr Editor (for GLSL) tools to help visualize the results.
Basic green gradient shader
Here’s what the shader code currently looks like (and literally looks like):
uniform float2 iResolution; uniform float iTime; vec4 main(in float2 fragCoord) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; // Time varying pixel color vec3 col = 0.8 + 0.2*cos(iTime*2.0+uv.xxx*2.0+vec3(1,2,4)); // Output to screen return vec4(col,1.0); }
The line where col
is assigned is where the time varying color is decided, so to make the gradient only show shades of green, this is the line we’ll have to change. This vec3
represents the RGB values of the shader color, so the first step to building a new gradient was to pick a few shades of green and note their RGB values.
Once the colors were chosen, all I had to do was experiment a bit in the shader preview tool and dust off my trigonometry knowledge to get the RGB values to vary between those of my chosen shades of green; this resulted in the following updated shader code:
uniform float2 iResolution; uniform float iTime; vec4 main(in float2 fragCoord) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = iTime/iResolution.xy; // Time varying pixel color float r = 0.3 + 0.5*sin(iTime*2.0+uv.x*3.0); float g = 0.9 + 0.1*cos(iTime*2.0+uv.x*3.0); float b = 0.3 + 0.4*cos(iTime*2.0+uv.x*3.0); // Output to screen return vec4(r, g, b, 1.0); }
Now that we’re happy with the shader code, all that’s left to do is use it in the app as our garden background. Since our desktop and mobile implementations will be different, we can add a backgroundBrush
parameter to our main composable and write the appropriate code in each module. We will again be using LaunchedEffect
to create a changing time value (like we did previously with Sketch
) that can be passed in as the iTime
float for the shader code. And, since we don’t want to cause unnecessary recomposition, we will make sure to use the lambda Modifier drawBehind
. For the desktop app, we will be using Skia’s RuntimeEffect
to build a shader, as covered in this article on Skia shaders for Compose desktop. For the Android app, we’ll use RuntimeShader
, as described by the AGSL documentation. Both of these versions can then be converted to an Android ShaderBrush
and passed into the common main composable. Check out the GitHub repo for the fully updated MyGarden
code .
Here’s the result:
Complex shader
Now that we have the shader infrastructure set up, we can play around more with creating different shader effects. The noodleZoom
shader in GradientShaderScreen.kt, originally from Twitter user @notargs, creates an awesome zooming web-like effect that I think would look pretty cool behind our flowers. But it’s not green! So, let’s try using our RGB and trig knowledge again to change the original shader code, which is shown here:
uniform float2 iResolution; uniform float iTime; // Source: @notargs https://twitter.com/notargs/status/1250468645030858753 float f(vec3 p) { p.z -= iTime * 10.; float a = p.z * .1; p.xy *= mat2(cos(a), sin(a), -sin(a), cos(a)); return .1 - length(cos(p.xy) + sin(p.yz)); } half4 main(vec2 fragcoord) { vec3 d = .5 - fragcoord.xy1 / iResolution.y; vec3 p=vec3(0); for (int i = 0; i < 32; i++) { p += f(p) * d; } return ((sin(p) + vec3(2, 5, 12)) / length(p)).xyz1;
This, the RGB values are determined by the vec3(2, 5, 12)
in the return statement. The brightness of the color also depends on this vector, so we’ll also want to make sure we don’t make the values too high or low. After some experimenting in the shader playground again, I settled on vec3(0.95, 3.725, 2.05)
. Here’s how the final iteration of our garden looks:
Resources and feedback
The content covered today is part of PR #3 in the animated-garden repo.
To learn more about Jetpack Compose animations and creative coding, check out these resources:
- nicole-terc/composable-sheep (github.com)
- drinkthestars/composable-sheep-sketches: Funky composable sheep 😎 🐏 (github.com)
- Composable Sheep – A Compose Animations Journey
- Composable Sheep – The Creative Coding Epilogue
- Graphics in Jetpack Compose
- Animations in Jetpack Compose
- Customize animations in Jetpack Compose
- Android Brush: Gradients and Shaders
- Skia Shaders Playground
- AGSL with Jetpack Compose
- Skia shaders in Compose desktop
If you have any questions, use the feedback forum or message us on Twitter @surfaceduodev.
Since this is the end of the animated garden series, we will be livestreaming this week! You can also check out the archives on YouTube.
0 comments