Koreography

repository·main·Indexed 42 days ago

https://github.com/sagar-viradiya/koreography

A lightweight Kotlin DSL utility for Jetpack Compose that simplifies the orchestration of low-level Animatable animations. It allows developers to define complex sequences of parallel and sequential animations using a hierarchical DSL without manually managing coroutine lifecycles. Key features include the rememberKoreography composable, LaunchKoreography for state-based triggers, and execution methods such as dance, repeatDance, and danceForever.

Tokens
3.2K
Snippets
17
Records
18
Agent score
51%

What's inside Koreography

  1. How Koreography works: Moves and Choreography

    main

    Koreography is a Kotlin DSL for orchestrating low-level Jetpack Compose Animatable objects.

    • Move: The fundamental unit of animation. It targets a specific Animatable with a targetValue and an animationSpec.
    • Choreography: A composition of moves. You can define how these moves relate to each other using sequentialMoves (one after another) or parallelMoves (all at once).
    • Nesting: You can nest parallelMoves and sequentialMoves to create complex, hierarchical animation sequences.
  2. Define animation sequences with Move

    main

    The Move interface is the core abstraction for defining animations in Koreography. It uses a DSL-like structure to compose different types of animation behaviors:

    1. Single Moves: Animating a single Animatable to a targetValue.
    2. Sequential Moves: A group of moves that execute one after another.
    3. Parallel Moves: A group of moves that execute at the same time.

    You build these by calling DSL functions like move(), sequentialMoves(), and parallelMoves() within a scope that manages a list of moves.

    // Conceptual structure of Move composition
    sequentialMoves { 
        move(animatable1, target1)
        parallelMoves {
            move(animatable2, target2)
            move(animatable3, target3)
        }
    }
  3. Choreograph sequential animations

    main

    To animate multiple properties one after another, use the rememberKoreography block and declare move calls directly within it. Each move will wait for the previous one to complete.

    val alphaAnimatable = remember { Animatable(0f) }
    val scaleAnimatable = remember { Animatable(0f) }
    
    val koreography = rememberKoreography {
        move(
            animatable = alphaAnimatable,
            targetValue = 1f,
            animationSpec = tween(500)
        )
        
        move(
            animatable = scaleAnimatable,
            targetValue = 2f,
            animationSpec = tween(500)
        )
    }
  4. Choreograph parallel animations

    main

    To animate multiple properties at the same time, wrap your move calls inside a parallelMoves block within rememberKoreography.

    val alphaAnimatable = remember { Animatable(0f) }
    val scaleAnimatable = remember { Animatable(0f) }
    
    val koreography = rememberKoreography {
        parallelMoves {
            move(
                animatable = alphaAnimatable,
                targetValue = 1f,
                animationSpec = tween(500)
            )
            
            move(
                animatable = scaleAnimatable,
                targetValue = 2f,
                animationSpec = tween(500)
            )
        }
    }
  5. Create complex nested choreographies

    main

    You can nest sequentialMoves inside parallelMoves (or vice versa) to create sophisticated animation patterns. For example, you can have three animations running in parallel, where one of those parallel animations is actually a sequence of two other animations.

    val alphaAnimatable = remember { Animatable(0f) }
    val scaleAnimatable = remember { Animatable(0f) }
    val rotationAnimatable = remember { Animatable(0f) }
    val translationXAnimatable = remember { Animatable(0f) }
    
    val koreography = rememberKoreography {
        parallelMoves {
            move(
                animatable = alphaAnimatable,
                targetValue = 1f,
                animationSpec = tween(500)
            )
            
            move(
                animatable = scaleAnimatable,
                targetValue = 2f,
                animationSpec = tween(500)
            )
            
            sequentialMoves {
                move(
                    animatable = rotationAnimatable,
                    targetValue = 20f,
                    animationSpec = tween(500)
                )
                
                move(
                    animatable = translationXAnimatable,
                    targetValue = 200f,
                    animationSpec = tween(500)
                )
            }
        }
    }
  6. Execute a choreography

    main

    Once a choreography is defined using rememberKoreography, you can trigger it using the following methods. Note that you must provide a coroutineScope (typically obtained via rememberCoroutineScope()) to handle the animation lifecycle.

    // One time execution
    koreography.dance(scope = coroutineScope) { 
        // Optional: callback when finished 
    }
    
    // Repeat execution a specific number of times
    koreography.repeatDance(count = 3, scope = coroutineScope) { 
        // Optional: callback when finished 
    }
    
    // Infinite execution (runs as long as the composition is alive)
    koreography.danceForever(scope = coroutineScope) { 
        // Optional: callback when finished 
    }
  7. Execute choreography based on state change

    main

    Use the LaunchKoreography composable to trigger a choreography whenever a specific state changes. This behaves similarly to the standard Compose LaunchedEffect API.

    val alphaAnimatable = remember { Animatable(0f) }
    val scaleAnimatable = remember { Animatable(0f) }
    
    // The choreography inside this block will re-run whenever 'state' changes
    LaunchKoreography(state) {
        move(
            animatable = alphaAnimatable,
            targetValue = 1f,
            animationSpec = tween(500)
        )
        
        move(
            animatable = scaleAnimatable,
            targetValue = 2f,
            animationSpec = tween(500)
        )
    }
  8. Compose parallel animations with parallelMoves()

    main

    The parallelMoves function allows you to group multiple Move objects so they execute simultaneously. Inside the parallelMoves block, you can call move(), sequentialMoves(), or nested parallelMoves().

    Usage: Call parallelMoves { ... } and define the parallel animations within the lambda.

    parallelMoves {
        move(animatableA, 10f)
        move(animatableB, 20f)
    }
  9. Define a choreography using the `koreography` DSL

    main

    Use the koreography function to define a sequence of animation moves using a Kotlin DSL. The DSL operates on a Move.SequentialMoves object, allowing you to compose sequential and parallel animations. This returns a Koreography instance that can be executed later.

    val myKoreography = koreography<Float, AnimationVector1D> {
        // Define moves here using the DSL
    }
  10. Compose sequential animations with sequentialMoves()

    main

    The sequentialMoves function allows you to group multiple Move objects so they execute in order. Inside the sequentialMoves block, you can call move(), parallelMoves(), or even nested sequentialMoves().

    Usage: Call sequentialMoves { ... } and define the sequence of animations within the lambda.

    sequentialMoves {
        move(animatableA, 10f)
        move(animatableB, 20f)
    }