
Android 动画对比指南View 系统 vs Jetpack Compose 本指南用于学习 Android 动画演进配合AnimationLearningApp教学项目使用项目Gitee地址https://gitee.com/developer_wind/AnimationLearningApp目录动画体系对比总览View 动画系统详解Compose 动画系统详解Compose 特有动画实战对照表最佳实践建议动画体系对比总览特性View 系统Jetpack Compose声明方式命令式 / XML声明式状态管理手动更新 View状态驱动自动重绘动画 API分散 (Animation/Animator)统一 (animate*AsState)学习曲线陡峭平缓性能需手动优化自动优化重绘代码量多少View 动画系统详解1. View Animation (补间动画)特点只改变视觉效果不改变实际属性// XML 定义 (res/anim/scale_up.xml)setxmlns:androidhttp://schemas.android.com/apk/res/androidscale android:fromXScale0.5android:toXScale1.0android:fromYScale0.5android:toYScale1.0android:pivotX50%android:pivotY50%android:duration300/alpha android:fromAlpha0.0android:toAlpha1.0android:duration300//set// Kotlin 使用valanimationAnimationUtils.loadAnimation(context,R.anim.scale_up)view.startAnimation(animation)缺点❌ 动画结束后 View 实际位置不变点击事件仍在原位置❌ 无法监听中间状态❌ XML 与代码分离2. Property Animation (属性动画)特点真正改变属性值支持任意对象// ObjectAnimator - 单个属性ObjectAnimator.ofFloat(view,rotationY,0f,360f).apply{duration1000interpolatorAccelerateDecelerateInterpolator()start()}// AnimatorSet - 组合动画AnimatorSet().apply{playTogether(ObjectAnimator.ofFloat(view,scaleX,1f,1.5f),ObjectAnimator.ofFloat(view,scaleY,1f,1.5f),ObjectAnimator.ofFloat(view,alpha,1f,0f))duration500start()}// ValueAnimator - 自定义值动画ValueAnimator.ofFloat(0f,1f).apply{duration1000addUpdateListener{animator-valprogressanimator.animatedValueasFloat view.translationXprogress*100}start()}缺点❌ 代码冗长❌ 需要手动管理 Animator 生命周期❌ 多个属性联动复杂3. LayoutTransition (布局变化动画)vallayoutfindViewByIdLinearLayout(R.id.container)valtransitionLayoutTransition()transition.setDuration(300)layout.layoutTransitiontransition// 添加/移除 View 时自动动画layout.addView(newView)layout.removeView(oldView)4. Activity/Fragment 转场动画// 老式 APIstartActivity(intent)overridePendingTransition(R.anim.slide_in_right,R.anim.slide_out_left)// Fragment 转场fragmentTransaction.setCustomAnimations(R.anim.fade_in,R.anim.fade_out,R.anim.slide_in_left,R.anim.slide_out_right)Compose 动画系统详解1. animate*AsState (状态驱动动画)最常用状态变化时自动动画ComposablefunAnimatedBox(){varexpandedbyremember{mutableStateOf(false)}// 各种类型的动画valsizebyanimateDpAsState(targetValueif(expanded)200.dpelse100.dp,animationSpecspring(stiffnessSpring.StiffnessLow))valcolorbyanimateColorAsState(targetValueif(expanded)Color.RedelseColor.Blue,animationSpectween(300))valalphabyanimateFloatAsState(targetValueif(expanded)1felse0.5f)valrotationbyanimateFloatAsState(targetValueif(expanded)360felse0f)Box(modifierModifier.size(size).background(color).alpha(alpha).rotate(rotation).clickable{expanded!expanded})}支持类型animateFloatAsState- 浮点数 (alpha, rotation, scale)animateDpAsState- 尺寸 (size, padding, elevation)animateColorAsState- 颜色animateIntAsState- 整数animateOffsetAsState- 偏移量animateContentSize- 内容尺寸变化2. AnimatedVisibility (显示/隐藏动画)ComposablefunShowHideDemo(){varvisiblebyremember{mutableStateOf(true)}Column{Button(onClick{visible!visible}){Text(if(visible)隐藏else显示)}AnimatedVisibility(visiblevisible,enterfadeIn()slideInVertically(initialOffsetY{-50}),exitfadeOut()slideOutVertically(targetOffsetY{50})){Card{Text(Hello Compose!)}}}}enter/exit 组合fadeIn()/fadeOut()- 淡入淡出slideInHorizontally/Vertically()- 滑动scaleIn()/scaleOut()- 缩放expandIn()/shrinkOut()- 展开/收缩3. updateTransition (多属性协同过渡)适合多个属性联动的复杂动画ComposablefunCardExpansionDemo(){varexpandedbyremember{mutableStateOf(false)}valtransitionupdateTransition(targetStateexpanded,labelcardTransition)valwidthbytransition.animateDp(labelwidth){state-if(state)300.dpelse100.dp}valheightbytransition.animateDp(labelheight){state-if(state)200.dpelse100.dp}valcornerRadiusbytransition.animateDp(labelcornerRadius){state-if(state)24.dpelse8.dp}valelevationbytransition.animateDp(labelelevation){state-if(state)16.dpelse4.dp}Card(modifierModifier.width(width).height(height).clickable{expanded!expanded},shapeRoundedCornerShape(cornerRadius),elevationCardDefaults.cardElevation(elevation)){Box(contentAlignmentAlignment.Center){Text(if(expanded)展开else收起)}}}4. Crossfade (内容切换动画)ComposablefunScreenSwitcher(){varcurrentScreenbyremember{mutableStateOf(home)}Crossfade(targetStatecurrentScreen,labelscreenCrossfade){screen-when(screen){home-HomeScreen()profile-ProfileScreen()settings-SettingsScreen()}}}5. AnimatedContent (内容替换动画)ComposablefunCounterDemo(){varcountbyremember{mutableStateOf(0)}AnimatedContent(targetStatecount,labelcounterAnimation,transitionSpec{// 数字增加从右滑入向左滑出if(targetStateinitialState){slideInHorizontally{it}fadeIn()togetherWith slideOutHorizontally{-it}fadeOut()}else{slideInHorizontally{-it}fadeIn()togetherWith slideOutHorizontally{it}fadeOut()}}){targetCount-Text(text$targetCount,styleMaterialTheme.typography.headlineLarge)}}Compose 特有动画1. infiniteTransition (无限循环动画)ComposablefunLoadingPulse(){valinfiniteTransitionrememberInfiniteTransition(labelpulse)valscalebyinfiniteTransition.animateFloat(initialValue1f,targetValue1.2f,animationSpecinfiniteRepeatable(animationtween(500),repeatModeRepeatMode.Reverse),labelscale)valalphabyinfiniteTransition.animateFloat(initialValue1f,targetValue0.5f,animationSpecinfiniteRepeatable(animationtween(500),repeatModeRepeatMode.Reverse),labelalpha)Box(modifierModifier.size(100.dp).scale(scale).alpha(alpha).background(Color.Blue,CircleShape))}2. animateItem (列表项动画) - Compose 1.5ComposablefunAnimatedList(items:ListString){LazyColumn{items(itemsitems,key{it}){item-Box(modifierModifier.animateItem(fadeInSpecfadeIn(animationSpectween(300)),fadeOutSpecfadeOut(animationSpectween(300))).padding(8.dp)){Text(item)}}}}3. 手势驱动动画 (draggable spring)ComposablefunDraggableCard(){varoffsetXbyremember{mutableStateOf(0f)}valdraggableStaterememberDraggableState{delta-offsetXdelta}valanimatedOffsetbyanimateFloatAsState(targetValueoffsetX,animationSpecspring(dampingRatioSpring.DampingRatioMediumBouncy,stiffnessSpring.StiffnessLow),labeldragOffset)Box(modifierModifier.offset{IntOffset(animatedOffset.toInt(),0)}.draggable(statedraggableState,orientationOrientation.Horizontal,onDragStopped{velocity-// 根据滑动速度决定返回或移除if(abs(offsetX)100){offsetX300f// 滑出}else{offsetX0f// 弹回}}).size(200.dp).background(Color.Green,RoundedCornerShape(16.dp)))}4. Navigation Compose 转场动画ComposablefunAppNavGraph(navController:NavHostController){NavHost(navControllernavController,startDestinationhome){composable(routehome,enterTransition{fadeIn()},exitTransition{fadeOut()}){HomeScreen(navController)}composable(routedetail/{id},enterTransition{slideInHorizontally{it}},exitTransition{slideOutHorizontally{-it}},popEnterTransition{slideInHorizontally{-it}},popExitTransition{slideOutHorizontally{it}}){backStackEntry-DetailScreen(backStackEntry.arguments?.getString(id))}}}5. SharedElement (共享元素转场) - 实验性ComposablefunSharedElementDemo(navController:NavHostController){NavHost(navController,startDestinationlist){composable(list){SharedElementList(onItemClick{id-navController.navigate(detail/$id)})}composable(detail/{id},enterTransition{fadeIn()sharedElementTransition(sharedContentStaterememberSharedContentState(keyimage-${it.targetState.arguments?.getString(id)}),boundsTransformBoundsTransform{_,_-tween(300)})}){DetailScreen()}}}实战对照表需求View 系统实现Compose 实现代码量对比按钮点击缩放ObjectAnimator AnimatorSetanimate*AsState10:1列表项添加动画LayoutTransition notifyItemInsertedanimateItem8:1页面切换淡入淡出overridePendingTransitionAnimatedContent5:1加载脉冲动画ValueAnimator repeatinfiniteTransition6:1拖拽卡片弹回ValueAnimator 手势监听draggable spring12:1显示/隐藏动画View.VISIBLE AnimationAnimatedVisibility4:1最佳实践建议✅ 推荐做法优先使用animate*AsState- 简单场景一行搞定交互元素用spring()- 物理感更强用户体验更好多属性联动用updateTransition- 保持动画同步列表用animateItem- 自动处理增删动画无限循环用infiniteTransition- 性能优化❌ 避免做法在 Compose 中使用 View 动画- 性能差且不兼容在动画中频繁创建状态- 会导致无限重绘忽略label参数- 调试时无法追踪动画在remember外创建动画- 每次重绘重置动画学习路径入门animate*AsStateAnimatedVisibility进阶updateTransitionAnimatedContent高级infiniteTransition 手势驱动 共享元素参考资源官方动画文档Compose Animation CodelabAnimation Playground 示例