1. 从基础到进阶:为什么我们需要自定义导航动画?
如果你已经用 Compose Navigation 做过几个简单的页面跳转,可能会觉得,嗯,这玩意儿挺方便的,声明一下路由,调用一下 navigate 方法,页面就过去了。默认的那个淡入淡出效果,虽然不丑,但用久了总觉得有点“官方”,少了点自己应用的个性。尤其是在今天这个用户体验至上的时代,一个精心设计的、与产品调性匹配的页面过渡动画,往往是区分“好用”和“好用又好看”应用的关键细节。
我自己在项目里就深有体会。早期图省事,全用的默认动画,产品经理跑过来看,说:“这跳转感觉有点生硬啊,和我们活泼的界面风格不太搭。” 后来花了点时间研究 Compose Navigation 的自定义动画能力,实现了几个符合产品气质的过渡效果,上线后,虽然用户不会特意夸这个动画,但整体的操作流畅感和精致度确实上了一个台阶,留存数据也有了些许改善。所以,别小看这几百毫秒的动画,它直接关系到用户对你产品质感的感知。
Compose Navigation 的自定义动画,本质上是对 AnimatedNavHost 和 composable 函数几个关键参数的深度定制。它不像有些框架,改个动画要写一堆复杂的插值器和估值器。在 Compose 的世界里,一切都是声明式的,动画也是。你只需要告诉系统:“页面进来的时候,我希望它这样动;出去的时候,那样动。” 剩下的,Compose 的动画系统会帮你处理得平滑又高效。
那么,具体能怎么“动”呢?范围其实非常广。从最简单的透明度变化(渐隐渐现),到位置的移动(上下左右滑动),再到大小的缩放,甚至这些效果的组合。你还可以控制动画的时长、缓动曲线(是先快后慢还是先慢后快),从而创造出或轻盈、或沉稳、或富有弹性的各种过渡感觉。接下来,我们就抛开理论,直接进入实战,看看怎么把这些炫酷又实用的动画效果,一步步搬到你的项目里。
2. 核心概念回顾与准备工作:NavController 与 BackStack
在动手写动画之前,我们必须把 Compose Navigation 的两个“地基”打牢:NavController 和返回栈(BackStack)。如果对它们理解不透,后面自定义动画时很容易踩坑。
NavController:你应用的导航指挥官
你可以把 NavController 想象成你应用里的 GPS 导航系统。它知道所有的“目的地”(也就是你的各个 Composable 页面),并且持有一张“地图”(NavGraph,导航图)。你想去哪,告诉它一声(调用 navigate(route)),它就能带你过去。同时,它还默默记着你走过的所有路,这个记录本就是“返回栈”。
在实际编码中,你会遇到两个长得像的类:NavController 和 NavHostController。简单来说,NavHostController 是专门为 Compose 环境优化的 NavController。我们通常这么用:在应用的根组件(比如 MainActivity 的 setContent 里)使用 rememberNavController() 来创建它。记住,整个应用最好只创建这一个实例,然后通过参数传递或者依赖注入的方式,分享给所有需要导航的组件。这样能保证导航状态的一致性和可预测性。
@Composable
fun MyApp() {
// 在根组件创建,保证单例
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("detail") { DetailScreen(navController) }
}
}
返回栈:你的导航历史记录
返回栈是一个“后进先出”的栈结构。每次你导航到一个新页面(比如从 Home 到 Detail),Detail 就会被“压入”栈顶。当你按下返回键,栈顶的 Detail 被“弹出”,你就回到了 Home。这个机制是 Android 导航的基石,保证了符合用户直觉的返回操作。
在 Compose Navigation 中,你可以通过 navController.currentBackStackEntryAsState() 来观察当前栈顶入口的变化,这对于根据当前页面更新 UI(比如显示不同的顶部栏)非常有用。
val currentBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = currentBackStackEntry?.destination
// 可以根据 currentDestination?.route

2884

被折叠的 条评论
为什么被折叠?



