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
| API | Use Case | M3 Support |
|---|---|---|
| Jetpack Compose | Modern declarative UI | Excellent |
| ObjectAnimator | View system animations | Good |
| ViewPropertyAnimator | Simple view animations | Good |
| Material Transitions | M3 container transform | Native |
| MotionLayout | Complex choreography | Good |
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
| Token | Android Duration | Use |
|---|---|---|
short-1 | 50ms | Micro-interactions |
short-4 | 200ms | Buttons, FAB |
medium-2 | 300ms | Dialogs, bottom sheets |
long-2 | 500ms | Page transitions |
extra-long-4 | 1000ms | Hero 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