Compose Graphics
Advanced Compose visuals - Material 3 Expressive motion physics, AGSL shaders (Android 13+), Canvas/DrawScope generative, graphicsLayer effects.
- Skill ID
- athevon/genjutsu/compose-graphics
- Publisher
- athevon
- Repository
- genjutsu
- Installs
- 0
- Files
- 4
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
athevon/genjutsu/compose-graphicsInstalls these files- SKILL.md
- references/agsl-recipes.md
- references/canvas-generative.md
- references/m3-expressive-deep.md
What this skill tells the agent
Version-sensitive. Every API name, SDK gate and browser-support claim below was verified on 2026-09-08 against primary sources. What against, and when, is in _jutsu/VERSIONS.md. If that date is old, re-verify before acting on a version number.Compose Graphics
Advanced Compose visuals: M3 Expressive motion physics, AGSL shaders (Android 13+), Canvas / DrawScope, graphicsLayer effects. Loaded for advanced thesis (shader, expressive, M3 Expressive, AGSL, Canvas, holographic). Foundation:../compose-motion/SKILL.mdcovers basics. Concise rules here. Deep-dive inreferences/.
Decision Tree: Which API for Which Need
| Need | API |
|---|---|
| Spring physics with bounce / overshoot | MotionScheme.expressive() (M3 Expressive) |
| Pixel-level shader | RuntimeShader + Modifier.graphicsLayer { renderEffect = ... } (Android 13+) |
| Generative drawing (paths, particles, fractals) | Canvas { drawScope -> ... } |
| GPU effects (blur, shadows, color filters) | Modifier.graphicsLayer { renderEffect = ... } or Modifier.blur(...) |
| Adaptive system materials (Material You glassmorphism) | Modifier.background(MaterialTheme.colorScheme.surfaceContainerHighest) |
| Liquid glass on Android | AGSL shader recipe (no native API like iOS yet) |
Domain 1: Material 3 Expressive
What It Is
The 2025 Material 3 evolution introduces spring-based motion physics replacing fixed-duration tweens. New shape morphing API via androidx.graphics.shapes. New MotionScheme selectable on the theme. Aimed at hero moments, key interactions, brand-defining UI.
MotionScheme
| Scheme | Personality | Use For |
|---|---|---|
MotionScheme.standard() | Calmer, less overshoot | Default for chrome, lists, navigation |
MotionScheme.expressive() | More overshoot, longer settle | Hero reveals, FABs, primary CTAs |
Apply on the theme:
MaterialTheme(motionScheme = MotionScheme.expressive()) {
// children read tokens via MaterialTheme.motionScheme.*
}Tokens exposed:
| Token | Domain | Spring (damping / stiffness) - standard | ... expressive |
|---|---|---|---|
fastSpatialSpec() | Position / size | 0.9 / 1400 | 0.6 / 800 |
defaultSpatialSpec() | Position / size | 0.9 / 700 | 0.8 / 380 |
slowSpatialSpec() | Position / size | 0.9 / 300 | 0.8 / 200 |
fastEffectsSpec() | Opacity / color | 1.0 / 3800 | 1.0 / 3800 |
defaultEffectsSpec() | Opacity / color | 1.0 / 1600 | 1.0 / 1600 |
slowEffectsSpec() | Opacity / color | 1.0 / 800 | 1.0 / 800 |
Spatial vs Effects: spatial = anything physical (height, offset, scale). Effects = visual properties without inertia (alpha, color, elevation). Both families are springs - the effects ones are just critically damped (dampingRatio = 1.0) and much stiffer, so they never overshoot. Only the spatial springs differ betweenstandard()andexpressive().
Availability:MotionScheme,MaterialTheme(motionScheme = ...)andMaterialExpressiveThemeare stable in material3 1.4.0 (no opt-in needed).MaterialShapesand the shape-morphing helpers are not - they need 1.5.0-alpha and@OptIn(ExperimentalMaterial3ExpressiveApi::class).
Hero Card Expand (Expressive Springs)
@Composable
fun ExpressiveHero() {
var expanded by remember { mutableStateOf(false) }
MaterialTheme(motionScheme = MotionScheme.expressive()) {
val transition = updateTransition(targetState = expanded, label = "expand")
val height by transition.animateDp(
transitionSpec = { MaterialTheme.motionScheme.slowSpatialSpec() },
label = "height"
) { if (it) 400.dp else 100.dp }
val alpha by transition.animateFloat(
transitionSpec = { MaterialTheme.motionScheme.defaultEffectsSpec() },
label = "alpha"
) { if (it) 1f else 0f }
Card(
modifier = Modifier
.fillMaxWidth()
.height(height)
.clickable { expanded = !expanded }
) {
Box(modifier = Modifier.alpha(alpha)) {
Text("Detail content", modifier = Modifier.padding(24.dp))
}
}
}
}Spring Tuning Recipes
| Mood | Spec |
|---|---|
| Hero reveal | spring(stiffness = Spring.StiffnessLow, dampingRatio = Spring.DampingRatioMediumBouncy) |
| Snappy expressive | spring(stiffness = Spring.StiffnessMediumLow, dampingRatio = 0.7f) |
| Calm spatial | MaterialTheme.motionScheme.defaultSpatialSpec() (use the token) |
| Critical (no overshoot) | spring(stiffness = Spring.StiffnessHigh, dampingRatio = 1f) |
Shape Morphing (MaterialShapes + Morph)
Two different libraries, and the split matters:
androidx.graphics.shapes:graphics-shapes(stable 1.1.0) shipsRoundedPolygon,CornerRoundingandMorph(start, end). ItsMorph.toPath(progress)returns an `android.graphics.Path`.- The predefined Material shapes (
MaterialShapes.Circle,Pentagon,Cookie4Sided,Sunny,Heart, ...) live in material3, not in graphics-shapes. They are still@ExperimentalMaterial3ExpressiveApiand ship only inandroidx.compose.material3:material3:1.5.0-alpha*- they are not in stable material3 1.4.0. material3 also addsMorph.toPath(progress)returning a Compose `Path`, plusRoundedPolygon.toShape().
Never write .asAndroidPath().asComposePath(): with the graphics-shapes import it does not compile (android.graphics.Path has no asAndroidPath()), and with the material3 import it is a pointless round trip. And because MaterialShapes are normalized to a 0..1 box, the path must be scaled to the draw size - dropping the raw path into GenericShape yields a ~1px shape in the top-left corner.
import androidx.compose.material3.MaterialShapes
import androidx.compose.material3.toPath // Morph.toPath -> androidx.compose.ui.graphics.Path
import androidx.graphics.shapes.Morph
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MorphingBadge(active: Boolean) {
val morph = remember { Morph(MaterialShapes.Circle, MaterialShapes.Cookie4Sided) }
val progress by animateFloatAsState(
targetValue = if (active) 1f else 0f,
animationSpec = MaterialTheme.motionScheme.slowSpatialSpec(),
label = "morph",
)
Box(
modifier = Modifier
.size(96.dp)
.clip(MorphShape(morph, progress))
.background(MaterialTheme.colorScheme.primary)
)
}
/** MaterialShapes are normalized to 0..1: scale the path to the draw size, then centre it. */
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private class MorphShape(private val morph: Morph, private val progress: Float) : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density,
): Outline {
val path = morph.toPath(progress = progress)
path.transform(Matrix().apply { scale(x = size.width, y = size.height) })
path.translate(size.center - path.getBounds().center)
return Outline.Generic(path)
}
}For a static (non-morphing) shape, skip all of this: MaterialShapes.Cookie4Sided.toShape() returns a ready-to-use Shape that already handles the scaling.
When to Use Expressive vs Standard
- Expressive: hero moments, key interactions, FABs, primary CTAs. 1-3% of UI.
- Standard: default for the rest of the app. Mixing too much Expressive feels chaotic - every element fighting for attention.
Domain 2: AGSL Shaders (Android 13+)
What It Is
AGSL is Android's shader language. Similar to GLSL with simplifications (half4 instead of vec4, restricted feature set, sandbox-safe). RuntimeShader compiles your AGSL source. Bind to a Compose modifier via Modifier.graphicsLayer { renderEffect = ... }.
Setup
@ComposableMore from athevon
Use this skill in a project
Open any RiverX project, search the Skills panel for athevon/genjutsu/compose-graphics, and install it in one click.
