android-native

طراحی انیمیشن‌های native اندروید با Jetpack Compose و استانداردهای Material 3

android-native برای ساخت انیمیشن‌های روان و بومی در اپلیکیشن‌های Android است و بین Jetpack Compose، View animations، MotionLayout و Material Transitions انتخاب مناسبی پیشنهاد می‌کند.

این skill با ارائه‌ی الگوهای آماده برای spring، easing، transition، shared element، کاهش حرکت و haptic feedback کمک می‌کند رابط کاربری شما هم عملکرد native داشته باشد و هم با سیستم موشن Material Design 3 سازگار بماند.

چه چیزی می‌گیرید

  • نمونه‌کد Kotlin برای animateAsState، AnimatedContent و AnimatedVisibility در Jetpack Compose
  • الگوهای Material 3 برای spring، easing، زمان‌بندی و transitionهای صفحه
  • راهنمای استفاده از ObjectAnimator، ViewPropertyAnimator، SpringAnimation و MotionLayout
  • الگوهای shared element، Lottie، haptic feedback و پشتیبانی از reduced motion
  • کمک به انتخاب API مناسب برای رابط‌های Compose و View-based

برای چه کاری خوب است

  • توسعه‌دهندگان Android که با Kotlin و Jetpack Compose رابط‌های متحرک می‌سازند
  • اپلیکیشن‌هایی که به انیمیشن‌های native، عملکرد روان و انطباق با Material Design 3 نیاز دارند
  • بازطراحی transitionهای صفحه، micro-interactionها و انیمیشن‌های سیستم در پروژه‌های Android

برای چه کاری نیست

  • ساخت انیمیشن‌های وب با CSS، JavaScript یا کتابخانه‌هایی مانند GSAP
  • تولید موشن‌گرافیک مستقل، ویدیو یا انیمیشن‌های پیچیده خارج از رابط کاربری Android
  • پروژه‌هایی که به کدنویسی native Android یا Kotlin دسترسی ندارند

دربارهٔ این اسکیل

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

دانلود ZIP