agentleFS
soilmasssoilmass/motion-design-agent/skills/android-native/SKILL.md

android-native

Skill7 starsChanged 10 months agoMITView on GitHub

Android native animation using Jetpack Compose, View animations, and Material Motion. Use when targeting Android apps with native performance and Material Design compliance.

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Files
SKILL.md

Android Native Animation

Native Android animation APIs aligned with Material Design 3 motion system.

API Selection

APIUse CaseM3 Support
Jetpack ComposeModern declarative UIExcellent
ObjectAnimatorView system animationsGood
ViewPropertyAnimatorSimple view animationsGood
Material TransitionsM3 container transformNative
MotionLayoutComplex choreographyGood

Jetpack Compose Animation

Basic Animation

// Animate state changes
var expanded by remember { mutableStateOf(false) }

val size by animateDpAsState(
    targetValue = if (expanded) 200.dp else 100.dp,
    animationSpec = spring(
        dampingRatio = 0.9f,
        stiffness = Spring.StiffnessMedium
    )
)

Box(modifier = Modifier.size(size))

M3 Spring Presets

object M3Springs {
    // Fast spatial (small elements)
    val fastSpatial = spring<Float>(
        dampingRatio = 0.9f,
        stiffness = 1400f
    )

    // Default spatial (standard UI)
    val defaultSpatial = spring<Float>(
        dampingRatio = 0.9f,
        stiffness = 700f
    )

    // Slow spatial (large elements)
    val slowSpatial = spring<Float>(
        dampingRatio = 0.9f,
        stiffness = 300f
    )

    // Effects (opacity, color - no overshoot)
    val fastEffects = spring<Float>(
        dampingRatio = 1.0f,
        stiffness = 3800f
    )

    val defaultEffects = spring<Float>(
        dampingRatio = 1.0f,
        stiffness = 1600f
    )
}

AnimatedContent

AnimatedContent(
    targetState = currentScreen,
    transitionSpec = {
        // M3 shared axis transition
        slideInHorizontally { width -> width } + fadeIn() with
        slideOutHorizontally { width -> -width } + fadeOut()
    }
) { screen ->
    when (screen) {
        Screen.Home -> HomeScreen()
        Screen.Detail -> DetailScreen()
    }
}

AnimatedVisibility

AnimatedVisibility(
    visible = isVisible,
    enter = fadeIn(animationSpec = tween(300)) +
            scaleIn(initialScale = 0.95f),
    exit = fadeOut(animationSpec = tween(200)) +
           scaleOut(targetScale = 0.95f)
) {
    Card { /* content */ }
}

Transition Animation

val transition = updateTransition(targetState = selected)

val scale by transition.animateFloat(
    transitionSpec = { spring(dampingRatio = 0.9f, stiffness = 700f) }
) { state ->
    if (state) 1.1f else 1.0f
}

val color by transition.animateColor(
    transitionSpec = { spring(dampingRatio = 1.0f, stiffness = 1600f) }
) { state ->
    if (state) MaterialTheme.colorScheme.primary else Color.Gray
}

Material 3 Transitions

Container Transform

// Using accompanist-navigation-material
NavHost(navController) {
    composable("list") {
        ItemList(onItemClick = { item ->
            navController.navigate("detail/${item.id}")
        })
    }
    composable(
        "detail/{id}",
        enterTransition = {
            fadeIn(animationSpec = tween(300)) +
            scaleIn(initialScale = 0.9f)
        },
        exitTransition = {
            fadeOut(animationSpec = tween(200)) +
            scaleOut(targetScale = 0.9f)
        }
    ) {
        DetailScreen()
    }
}

Shared Element (Compose 1.7+)

SharedTransitionLayout {
    AnimatedContent(targetState = showDetail) { show ->
        if (show) {
            DetailScreen(
                Modifier.sharedElement(
                    rememberSharedContentState(key = "image"),
                    animatedVisibilityScope = this@AnimatedContent
                )
            )
        } else {
            ListScreen(
                Modifier.sharedElement(
                    rememberSharedContentState(key = "image"),
                    animatedVisibilityScope = this@AnimatedContent
                )
            )
        }
    }
}

View System (Legacy)

ObjectAnimator

// M3 standard easing
val translateAnim = ObjectAnimator.ofFloat(view, "translationY", 0f, 100f).apply {
    duration = 300
    interpolator = PathInterpolator(0.2f, 0f, 0f, 1f) // M3 standard
}

// M3 emphasized two-segment path
val emphasizedPath = Path().apply {
    moveTo(0f, 0f)
    cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f)
    cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f)
}
val emphasizedInterpolator = PathInterpolator(emphasizedPath)

ViewPropertyAnimator

view.animate()
    .translationY(100f)
    .alpha(1f)
    .setDuration(300)
    .setInterpolator(PathInterpolator(0.05f, 0.7f, 0.1f, 1f)) // emphasized-decelerate
    .start()

SpringAnimation (AndroidX)

val springAnim = SpringAnimation(view, DynamicAnimation.TRANSLATION_Y).apply {
    spring = SpringForce().apply {
        dampingRatio = 0.9f  // M3 spatial
        stiffness = 700f    // M3 default
    }
}
springAnim.animateToFinalPosition(100f)

M3 Timing Functions

object M3Interpolators {
    // Standard (most animations)
    val standard = PathInterpolator(0.2f, 0f, 0f, 1f)

    // Emphasized-decelerate (entrances)
    val emphasizedDecelerate = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)

    // Emphasized-accelerate (exits)
    val emphasizedAccelerate = PathInterpolator(0.3f, 0f, 0.8f, 0.15f)

    // Standard-decelerate
    val standardDecelerate = PathInterpolator(0f, 0f, 0f, 1f)

    // Exact M3 emphasized (two-segment path)
    val emphasized: Interpolator by lazy {
        PathInterpolator(Path().apply {
            moveTo(0f, 0f)
            cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f)
            cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f)
        })
    }
}

Duration Scale

TokenAndroid DurationUse
short-150msMicro-interactions
short-4200msButtons, FAB
medium-2300msDialogs, bottom sheets
long-2500msPage transitions
extra-long-41000msHero moments

Reduced Motion

// Check user preference
val reduceMotion = ViewCompat.isAccessibilityReduceMotionEnabled(context)

// Compose
val reduceMotion = LocalReduceMotion.current

@Composable
fun AnimatedBox(visible: Boolean) {
    val reduceMotion = LocalReduceMotion.current

    AnimatedVisibility(
        visible = visible,
        enter = if (reduceMotion) fadeIn() else fadeIn() + scaleIn(),
        exit = if (reduceMotion) fadeOut() else fadeOut() + scaleOut()
    ) {
        Box()
    }
}

Haptic Feedback

// Pair with animations
val view = LocalView.current

fun onButtonPress() {
    view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
    // Trigger animation
}

// Compose
val haptic = LocalHapticFeedback.current
haptic.performHapticFeedback(HapticFeedbackType.LongPress)

Lottie Integration

// Compose
val composition by rememberLottieComposition(
    LottieCompositionSpec.RawRes(R.raw.animation)
)
val progress by animateLottieCompositionAsState(
    composition,
    iterations = LottieConstants.IterateForever
)

LottieAnimation(
    composition = composition,
    progress = { progress }
)

Related Skills

  • Dependencies: easing, duration, spring
  • See also: platform-adaptation, accessibility, lottie
  • Tokens: tokens/android-motion.tokens.json

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.