这才是我的完全体:当Jetpack Compose遇到Navigation

4,203 阅读2分钟

在这里插入图片描述 很多Android项目使用Jetpack Navigation进行页面切换。Navigation在设计上高度抽象,只负责导航逻辑不关心页面的具体实现,无论是Activity、Fragment甚至是一个已定义View都可以基于Navigation实现导航。当然,Composable也是可以的。

Jetpack Compose作为一个声明式UI框架经常拿来与React 、Flutter等作对比,但是遗憾的是Compose一直缺少其他框架的导航机制,现在使用Jetpack自家的Navigation,可以将Composable抽象为Destination从而进行导航,补齐了Compose的短板。

Installation

使用navigation-compose,只需要在build.gradle中添加依赖:

implementation "androidx.navigation:navigation-compose:1.0.0-alpha02"

NavController

Navigation中我们通过findNavController扩展方法获取NavController,然后进行跳转。 NavController中管理NavGraph等配置信息,所以是stateful的,在Compose的纯函数中需要通过以下方式获取一个有状态的实例

val navController = rememberNavController()

NavHost

NavHost是NavController的持有者,NavHostFragment是Fragment对于NavHost的实现。Compose基于composable函数渲染UI,没有Fragment这样的具体实例做载体,所以Compose的NavHost更加抽象,你可以将其理解为一个容器,内部通过NavController在“页面切换”时,渲染当前UI

val navController = rememberNavController()
NavHost(
        navController = navController,
        startDestination = "first_screen"
) {
    composable("first_screen") {
        // first screen
    }
    composable("second_screen") {
        // second screen
    }
}

如上,NavHost接受两个参数,navController和startDestination,这是Navigation的标准用法,不再赘述。其DSL内部的composable用来声明各个页面

A Navigation Sample

Compose中一个完整的Navigation定义如下:

@Composable
fun ComposeNavigation() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "first_screen"
    ) {
        composable("first_screen") {
            FirstScreen(navController = navController)
        }
        composable("second_screen") {
            SecondScreen(navController = navController)
        }
        composable("third_screen") {
            ThirdScreen(navController = navController)
        }
    }
}

配置了三个页面,初始页面是first_screen composable()的参数作为Destination的id,用于后续跳转

@Composable
fun FirstScreen(navController: NavController) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "First Screen\n" +
                    "Click me to go to Second Screen",
            color = Color.Green,
            style = TextStyle(textAlign = TextAlign.Center),
            modifier = Modifier.padding(24.dp).clickable(onClick = {
                // this will navigate to second screen
                navController.navigate("second_screen")
            })
        )
    }
}

如上,FirstScreen中,通过navController.navigate("second_screen")跳转到SecondScreen。

同样的, 其他的页面定义如下:

@Composable
fun SecondScreen(navController: NavController) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Second Screen\n" +
                    "Click me to go to Third Screen",
            color = Color.Yellow,
            style = TextStyle(textAlign = TextAlign.Center),
            modifier = Modifier.clickable(onClick = {
                // this will navigate to third screen
                navController.navigate("third_screen")
            })
        )
    }
}

@Composable
fun ThirdScreen(navController: NavController) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Third Screen\n" +
                    "Click me to go to First Screen",
            color = Color.Red,
            style = TextStyle(textAlign = TextAlign.Center),
            modifier = Modifier.clickable(onClick = {
                // this will navigate to first screen
                navController.navigate("first_screen")
            })
        )
    }
}

最后,需要在setContent中调用ComposeNavigation

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        ComposeNavigationTheme {
            ComposeNavigation()
        }
    }
}

之后,就可以在Compose项目中进行页面切换了,而且还支持BackStack的回退。

Fin

以前如果想使用Compose实现多页面的APP,只能在Fragment或者Activity内部写Compose代码。现在有了Navigation,可以彻底摆脱Fragment或者Activity了,这得益于Navigation高度抽象的设计,有兴趣的同学可以阅读NavController源码了解其中细节。