This document provides detailed information about the animation APIs in the Summon library.
The core animation class that manages animations in the Summon framework.
package code.yousef.summon.animation
class Animation<T>(
val initialValue: T,
val targetValue: T,
val duration: Int = 300,
val delay: Int = 0,
val easing: EasingFunction = EasingFunctions.EaseInOut,
val onUpdate: (T) -> Unit,
val onComplete: () -> Unit = {}
) {
fun start()
fun pause()
fun resume()
fun stop()
fun reverse()
}
// Factory function
fun <T> createAnimation(
initialValue: T,
targetValue: T,
duration: Int = 300,
delay: Int = 0,
easing: EasingFunction = EasingFunctions.EaseInOut,
onUpdate: (T) -> Unit,
onComplete: () -> Unit = {}
): Animation<T>
The Animation class provides the core functionality for creating and controlling animations in Summon.
val animation = createAnimation(
initialValue = 0f,
targetValue = 1f,
duration = 500,
easing = EasingFunctions.EaseInOut,
onUpdate = { value ->
element.style.opacity = value.toString()
},
onComplete = {
println("Animation completed")
}
)
Button(
text = "Start Animation",
onClick = {
animation.start()
}
)
A component that animates the appearance and disappearance of its content.
package code.yousef.summon.animation
@Composable
fun AnimatedVisibility(
visible: Boolean,
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn(),
exit: ExitTransition = fadeOut(),
content: @Composable () -> Unit
)
AnimatedVisibility wraps content and applies enter/exit animations when the visibility changes.
var isVisible by remember { mutableStateOf(false) }
Button(
text = if (isVisible) "Hide" else "Show",
onClick = { isVisible = !isVisible }
)
AnimatedVisibility(
visible = isVisible,
enter = slideInHorizontally() + fadeIn(),
exit = slideOutHorizontally() + fadeOut()
) {
Card(
modifier = Modifier
.padding(16.px)
.backgroundColor("#f0f0f0")
) {
Text("This content animates in and out")
}
}
A component that animates between different content states.
package code.yousef.summon.animation
@Composable
fun <T> AnimatedContent(
targetState: T,
modifier: Modifier = Modifier,
transitionSpec: ContentTransitionSpec<T> = defaultContentTransitionSpec(),
content: @Composable (T) -> Unit
)
AnimatedContent animates between different content based on the target state.
var currentPage by remember { mutableStateOf(0) }
Row {
Button(
text = "Previous",
onClick = { currentPage = (currentPage - 1).coerceAtLeast(0) }
)
Button(
text = "Next",
onClick = { currentPage = (currentPage + 1).coerceAtMost(2) }
)
}
AnimatedContent(
targetState = currentPage,
transitionSpec = {
if (targetState > initialState) {
// Moving forward
slideInHorizontally(initialOffsetX = { it }) with
slideOutHorizontally(targetOffsetX = { -it })
} else {
// Moving backward
slideInHorizontally(initialOffsetX = { -it }) with
slideOutHorizontally(targetOffsetX = { it })
}
}
) { page ->
when (page) {
0 -> IntroPage()
1 -> DetailsPage()
2 -> SummaryPage()
}
}
Components for creating transitions between different states.
package code.yousef.summon.animation
// Create transitions
fun <T> createTransition(initialState: T): Transition<T>
// Update transition state
fun <T> Transition<T>.updateTargetState(targetState: T)
// Transition specifications
fun fadeIn(initialAlpha: Float = 0f, duration: Int = 300): EnterTransition
fun fadeOut(targetAlpha: Float = 0f, duration: Int = 300): ExitTransition
fun slideInHorizontally(initialOffsetX: (width: Int) -> Int = { -it }, duration: Int = 300): EnterTransition
fun slideOutHorizontally(targetOffsetX: (width: Int) -> Int = { -it }, duration: Int = 300): ExitTransition
fun slideInVertically(initialOffsetY: (height: Int) -> Int = { -it }, duration: Int = 300): EnterTransition
fun slideOutVertically(targetOffsetY: (height: Int) -> Int = { -it }, duration: Int = 300): ExitTransition
fun expandIn(expandFrom: AnimationDirection = AnimationDirection.Center, duration: Int = 300): EnterTransition
fun shrinkOut(shrinkTowards: AnimationDirection = AnimationDirection.Center, duration: Int = 300): ExitTransition
Transition components provide a high-level API for creating animated transitions between different states.
enum class BoxState { Small, Large }
val transition = createTransition(BoxState.Small)
var currentState by remember { mutableStateOf(BoxState.Small) }
Button(
text = "Toggle Size",
onClick = {
currentState = when (currentState) {
BoxState.Small -> BoxState.Large
BoxState.Large -> BoxState.Small
}
transition.updateTargetState(currentState)
}
)
Box(
modifier = Modifier
.backgroundColor("#0077cc")
.size(
transition.animateDp(
{ state ->
when (state) {
BoxState.Small -> 100.dp
BoxState.Large -> 200.dp
}
}
),
transition.animateDp(
{ state ->
when (state) {
BoxState.Small -> 100.dp
BoxState.Large -> 200.dp
}
}
)
)
)
Predefined easing functions for controlling animation timing.
package code.yousef.summon.animation
object EasingFunctions {
val Linear: EasingFunction
val EaseIn: EasingFunction
val EaseOut: EasingFunction
val EaseInOut: EasingFunction
val EaseInQuad: EasingFunction
val EaseOutQuad: EasingFunction
val EaseInOutQuad: EasingFunction
val EaseInCubic: EasingFunction
val EaseOutCubic: EasingFunction
val EaseInOutCubic: EasingFunction
val EaseInQuart: EasingFunction
val EaseOutQuart: EasingFunction
val EaseInOutQuart: EasingFunction
val EaseInQuint: EasingFunction
val EaseOutQuint: EasingFunction
val EaseInOutQuint: EasingFunction
val EaseInElastic: EasingFunction
val EaseOutElastic: EasingFunction
val EaseInOutElastic: EasingFunction
val EaseInBounce: EasingFunction
val EaseOutBounce: EasingFunction
val EaseInOutBounce: EasingFunction
}
typealias EasingFunction = (t: Float) -> Float
// Custom cubic bezier easing function
fun cubicBezier(x1: Float, y1: Float, x2: Float, y2: Float): EasingFunction
Easing functions control the rate of change of an animation's progress over time, giving animations a more natural feel.
val animation = createAnimation(
initialValue = 0f,
targetValue = 1f,
duration = 1000,
easing = EasingFunctions.EaseOutBounce,
onUpdate = { value ->
element.style.transform = "scale($value)"
}
)
// Custom easing function
val customEasing = cubicBezier(0.17, 0.67, 0.83, 0.67)
Modifiers for applying animations to components.
package code.yousef.summon.modifier
// Animate CSS properties
fun Modifier.animate(
property: String,
duration: Int = 300,
delay: Int = 0,
easing: EasingFunction = EasingFunctions.EaseInOut
): Modifier
// Animate transitions
fun Modifier.transition(
properties: List<String>,
duration: Int = 300,
delay: Int = 0,
easing: EasingFunction = EasingFunctions.EaseInOut
): Modifier
// Apply keyframe animations
fun Modifier.keyframes(
name: String,
duration: Int = 1000,
delay: Int = 0,
iterationCount: Int = 1,
direction: AnimationDirection = AnimationDirection.Normal,
fillMode: AnimationFillMode = AnimationFillMode.None,
playState: AnimationPlayState = AnimationPlayState.Running
): Modifier
Animation modifiers provide a convenient way to apply animations to components using modifiers.
Text(
text = "Hover Me",
modifier = Modifier
.transition(
properties = listOf("transform", "color"),
duration = 300,
easing = EasingFunctions.EaseOut
)
.hoverStyle(
Modifier
.transform("scale(1.1)")
.color("#0077cc")
)
)
Box(
modifier = Modifier
.size(100.px)
.backgroundColor("#0077cc")
.keyframes(
name = "pulse",
duration = 2000,
iterationCount = AnimationIterationCount.Infinite
)
)
API for defining keyframe animations.
package code.yousef.summon.animation
class KeyframesBuilder {
fun at(percentage: Int, block: KeyframeBuilder.() -> Unit)
fun from(block: KeyframeBuilder.() -> Unit)
fun to(block: KeyframeBuilder.() -> Unit)
}
fun defineKeyframes(name: String, builder: KeyframesBuilder.() -> Unit): String
class KeyframeBuilder {
fun property(name: String, value: String)
fun transform(value: String)
fun opacity(value: Float)
fun translateX(value: CSSSize)
fun translateY(value: CSSSize)
fun scale(value: Float)
fun rotate(value: String)
// ... other properties
}
The keyframes API allows defining complex animations with multiple steps or stages.
// Define keyframes animation
val bounceAnimation = defineKeyframes("bounce") {
at(0) {
transform("translateY(0)")
}
at(20) {
transform("translateY(-30px)")
}
at(40) {
transform("translateY(0)")
}
at(60) {
transform("translateY(-15px)")
}
at(80) {
transform("translateY(0)")
}
at(100) {
transform("translateY(0)")
}
}
// Apply the animation
Box(
modifier = Modifier
.size(100.px)
.backgroundColor("#0077cc")
.keyframes(
name = bounceAnimation,
duration = 2000,
iterationCount = AnimationIterationCount.Infinite
)
)
API for creating custom animations.
package code.yousef.summon.animation
// Create a custom animation
fun createCustomAnimation(builder: CustomAnimationBuilder.() -> Unit): CustomAnimation
class CustomAnimationBuilder {
fun property(name: String, from: String, to: String)
fun transform(from: String, to: String)
fun opacity(from: Float, to: Float)
fun translateX(from: CSSSize, to: CSSSize)
fun translateY(from: CSSSize, to: CSSSize)
fun scale(from: Float, to: Float)
fun rotate(from: String, to: String)
// ... other properties
fun duration(milliseconds: Int)
fun delay(milliseconds: Int)
fun easing(easing: EasingFunction)
fun iterations(count: Int)
fun direction(direction: AnimationDirection)
fun fillMode(mode: AnimationFillMode)
}
Custom animations provide a way to define reusable animations that can be applied to different components.
val fadeAndScale = createCustomAnimation {
opacity(from = 0f, to = 1f)
scale(from = 0.8f, to = 1f)
duration(500)
easing(EasingFunctions.EaseOutCubic)
}
Box(
modifier = Modifier
.size(100.px)
.backgroundColor("#0077cc")
.applyAnimation(fadeAndScale)
)
Convenient extension functions for common animation scenarios.
package code.yousef.summon.animation
// Animate numeric properties
@Composable
fun animateFloatAsState(
targetValue: Float,
animationSpec: AnimationSpec<Float> = spring(),
visibilityThreshold: Float = 0.01f,
label: String = "FloatAnimation"
): State<Float>
@Composable
fun animateIntAsState(
targetValue: Int,
animationSpec: AnimationSpec<Int> = spring(),
label: String = "IntAnimation"
): State<Int>
// Animate size properties
@Composable
fun animateDpAsState(
targetValue: Dp,
animationSpec: AnimationSpec<Dp> = spring(),
label: String = "DpAnimation"
): State<Dp>
// Animate color
@Composable
fun animateColorAsState(
targetValue: Color,
animationSpec: AnimationSpec<Color> = spring(),
label: String = "ColorAnimation"
): State<Color>
// Animation specs
fun <T> spring(
dampingRatio: Float = Spring.DampingRatioNoBouncy,
stiffness: Float = Spring.StiffnessMedium,
visibilityThreshold: T? = null
): SpringSpec<T>
fun <T> tween(
durationMillis: Int = 300,
delayMillis: Int = 0,
easing: Easing = FastOutSlowInEasing
): TweenSpec<T>
fun <T> repeatable(
iterations: Int,
animation: DurationBasedAnimationSpec<T>,
repeatMode: RepeatMode = RepeatMode.Restart
): RepeatableSpec<T>
fun <T> infiniteRepeatable(
animation: DurationBasedAnimationSpec<T>,
repeatMode: RepeatMode = RepeatMode.Restart
): InfiniteRepeatableSpec<T>
@Composable
fun AnimatedComponent() {
var expanded by remember { mutableStateOf(false) }
// Animate size
val width by animateDpAsState(
targetValue = if (expanded) 300.dp else 100.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
// Animate color
val backgroundColor by animateColorAsState(
targetValue = if (expanded) Color.Blue else Color.Red,
animationSpec = tween(durationMillis = 500)
)
Box(
modifier = Modifier
.width(width)
.height(100.dp)
.backgroundColor(backgroundColor)
.clickable { expanded = !expanded }
)
}
Utility functions and classes for working with animations.
package code.yousef.summon.animation
// Create animated values
fun Animatable(
initialValue: Float,
visibilityThreshold: Float = 0.01f
): Animatable<Float, AnimationVector1D>
// Animate to target value
suspend fun <T, V : AnimationVector> Animatable<T, V>.animateTo(
targetValue: T,
animationSpec: AnimationSpec<T> = spring(),
initialVelocity: T? = null,
block: (Animatable<T, V>.() -> Unit)? = null
): AnimationResult<T, V>
// Snap to value immediately
suspend fun <T, V : AnimationVector> Animatable<T, V>.snapTo(targetValue: T)
// Stop animation
fun <T, V : AnimationVector> Animatable<T, V>.stop()
// Animation state
data class AnimationState<T, V : AnimationVector>(
val value: T,
val velocity: V,
val isRunning: Boolean,
val finishedTimeNanos: Long
)
@Composable
fun DraggableComponent() {
val offsetX = remember { Animatable(0f) }
val offsetY = remember { Animatable(0f) }
Box(
modifier = Modifier
.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
.size(100.dp)
.backgroundColor(Color.Blue)
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
coroutineScope.launch {
offsetX.animateTo(
targetValue = offsetX.value + delta,
animationSpec = spring(stiffness = Spring.StiffnessHigh)
)
}
}
)
)
}
Advanced keyframe generation utilities.
package code.yousef.summon.animation
// Generate keyframes from a sequence
fun generateKeyframes(
name: String,
frames: List<KeyframeDefinition>
): String
// Keyframe definition
data class KeyframeDefinition(
val percentage: Int,
val properties: Map<String, String>
)
// Keyframe builder DSL
fun keyframeSequence(
name: String,
builder: KeyframeSequenceBuilder.() -> Unit
): String
class KeyframeSequenceBuilder {
fun frame(percentage: Int, builder: KeyframeBuilder.() -> Unit)
fun frames(vararg percentages: Int, builder: KeyframeBuilder.() -> Unit)
fun interpolate(
from: Int,
to: Int,
property: String,
fromValue: String,
toValue: String,
easing: EasingFunction = EasingFunctions.Linear
)
}
// Complex keyframe animation
val complexAnimation = keyframeSequence("complexMove") {
frame(0) {
transform("translate(0, 0) scale(1)")
opacity(1f)
}
// Interpolate between frames
interpolate(
from = 0,
to = 50,
property = "transform",
fromValue = "translate(0, 0)",
toValue = "translate(100px, 0)",
easing = EasingFunctions.EaseInOut
)
frame(50) {
transform("translate(100px, 0) scale(1.5)")
backgroundColor("#ff0000")
}
frames(75, 100) {
transform("translate(0, 0) scale(1)")
opacity(0.5f)
}
}