KraftShadeAnimatedView
KraftShadeAnimatedView
is a Jetpack Compose wrapper for AnimatedKraftTextureView, providing animation capabilities for shader effects in Compose UIs.
Overview
KraftShadeAnimatedView
integrates the animation capabilities of AnimatedKraftTextureView
into Jetpack Compose applications. It builds on the foundation of KraftShadeEffectView and adds functionality specifically for creating time-based animations with shader effects.
This component is ideal for Compose applications that need to create dynamic, animated visual effects that change over time, such as transitions, procedural animations, or time-based visual effects.
Key Features
- Integrates
AnimatedKraftTextureView
into Jetpack Compose UIs - Provides a state-based API for managing animated effects
- Supports play/pause controls for animation
- Includes a built-in
TimeInput
for time-based animations - Handles proper resource cleanup with Compose's lifecycle
Basic Usage
Here's a simple example of using KraftShadeAnimatedView
to create an animated saturation effect in a Compose UI:
@Composable
fun AnimatedSaturationDemo() {
// Create and remember the state
val state = rememberKraftShadeAnimatedState()
var isPlaying by remember { mutableStateOf(true) }
var aspectRatio by remember { mutableFloatStateOf(1f) }
val context = LocalContext.current
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Animated view
KraftShadeAnimatedView(
modifier = Modifier
.weight(1f)
.aspectRatio(aspectRatio),
state = state
)
// Play/Pause button
Button(
modifier = Modifier.padding(16.dp),
onClick = {
if (isPlaying) {
state.stop()
} else {
state.play()
}
isPlaying = !isPlaying
}
) {
Text(if (isPlaying) "Pause" else "Play")
}
}
// Set up the animated effect
LaunchedEffect(Unit) {
state.setEffectAndPlay { windowSurface, timeInput ->
val bitmap = context.loadBitmapFromAsset("sample/cat.jpg")
aspectRatio = bitmap.width.toFloat() / bitmap.height
// Create a saturation input that oscillates between 0 and 1
val saturationInput = timeInput.bounceBetween(0f, 1f)
pipeline(windowSurface) {
serialSteps(bitmap.asTexture(), windowSurface) {
step(SaturationKraftShader()) { shader ->
shader.saturation = saturationInput.get()
}
}
}
}
}
}
Components
KraftShadeAnimatedView Composable
@Composable
fun KraftShadeAnimatedView(
modifier: Modifier = Modifier,
state: KraftShadeAnimatedState = rememberKraftShadeAnimatedState()
)
The main Composable function that creates an AnimatedKraftTextureView
and integrates it into your Compose UI.
Parameters:
modifier
: Standard Compose modifier for customizing the view's layoutstate
: AKraftShadeAnimatedState
that manages the view's state and operations
KraftShadeAnimatedState
class KraftShadeAnimatedState(scope: CoroutineScope)
Manages the state of the KraftShadeAnimatedView
and provides methods to interact with it.
Key methods:
setEffectAndPlay(effectExecutionProvider: AnimatedEffectExecutionProvider)
: Sets the effect and starts the animationsetEffectAndPause(effectExecutionProvider: AnimatedEffectExecutionProvider)
: Sets the effect without starting the animationplay()
: Starts or resumes the animationstop()
: Pauses the animationgetTimeInput()
: Gets theTimeInput
instance for creating time-based animations
Properties:
playing
: Indicates whether the animation is currently playing
rememberKraftShadeAnimatedState
@Composable
fun rememberKraftShadeAnimatedState(): KraftShadeAnimatedState
A Compose helper function that creates and remembers a KraftShadeAnimatedState
instance, ensuring it survives recomposition.
Example: Complex Animation with Multiple Effects
This example shows how to create a more complex animation with multiple time-based effects:
@Composable
fun ComplexAnimationDemo() {
val state = rememberKraftShadeAnimatedState()
var aspectRatio by remember { mutableFloatStateOf(1f) }
val context = LocalContext.current
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Animated view
KraftShadeAnimatedView(
modifier = Modifier
.weight(1f)
.aspectRatio(aspectRatio),
state = state
)
// Control buttons
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(onClick = { state.play() }) {
Text("Play")
}
Spacer(modifier = Modifier.width(16.dp))
Button(onClick = { state.stop() }) {
Text("Pause")
}
}
}
// Set up the animated effect
LaunchedEffect(Unit) {
state.setEffectAndPlay { windowSurface, timeInput ->
val bitmap = context.loadBitmapFromAsset("sample/cat.jpg")
aspectRatio = bitmap.width.toFloat() / bitmap.height
// Create multiple time-based inputs with different periods
val saturationInput = timeInput.bounceBetween(0.5f, 1.5f, periodMs = 3000)
val contrastInput = timeInput.bounceBetween(0.8f, 1.2f, periodMs = 5000)
val hueRotationInput = timeInput.map { (it % 10000) / 10000f * 360f }
// Create a swirl effect with animated center point
val swirlCenterX = timeInput.bounceBetween(0.3f, 0.7f, periodMs = 4000)
val swirlCenterY = timeInput.bounceBetween(0.3f, 0.7f, periodMs = 6000)
val swirlAngle = timeInput.bounceBetween(0f, 2f, periodMs = 2000)
pipeline(windowSurface) {
serialSteps(bitmap.asTexture(), windowSurface) {
// Color adjustments
step(SaturationKraftShader()) { shader ->
shader.saturation = saturationInput.get()
}
step(ContrastKraftShader()) { shader ->
shader.contrast = contrastInput.get()
}
step(HueKraftShader()) { shader ->
shader.setHueInDegree(hueRotationInput.get())
}
// Distortion effect
step(SwirlKraftShader()) { shader ->
shader.center = GlVec2(swirlCenterX.get(), swirlCenterY.get())
shader.angle = swirlAngle.get()
shader.radius = 0.5f
}
}
}
}
}
}
Working with TimeInput in Compose
The TimeInput
class provides several methods for creating time-based animations that work well with Compose:
bounceBetween
fun bounceBetween(min: Float, max: Float, periodMs: Long = 2000): SampledInput<Float>
Creates an input that oscillates between the minimum and maximum values over the specified period.
map
fun <T> map(mapper: (Long) -> T): SampledInput<T>
Creates a custom input by mapping the elapsed time to a value of type T.
Lifecycle Management
KraftShadeAnimatedView
automatically handles the animation lifecycle in sync with Compose's lifecycle:
- When the Composable enters the composition, a new
AnimatedKraftTextureView
is created - The view is attached to the provided
KraftShadeAnimatedState
- When the Composable leaves the composition, a
DisposableEffect
callsterminate()
to clean up resources - The animation automatically stops when the view is detached from the window
This ensures proper resource management and prevents unnecessary rendering when the view is not visible.
Considerations
- Animations run at the device's refresh rate (typically 60fps)
- For complex effects, monitor performance and consider simplifying shaders if frame drops occur
- Always call
stop()
when the animation is not visible to conserve battery - For non-animated effects, use KraftShadeEffectView instead
- The
playing
property can be used to update UI elements based on the animation state