【译】体验在Jetpack Compose使用Navigation

通过本文你会了解到怎么在Jetpack Compose里面使用Navigation。你还会了解到在navigate的时候怎么传递基础类型和自定义类型数据。

注意:Jetpack Compose最近已经发布了beta版本。这就意味着API的结构不会有太大的变动了。因此,现在这是学习怎么用Android开发下一代UI工具的时候了。Navigation是android开发的核心内容之一,所以你就好好看看本文吧。

使用Jetpack Compose Beta版本需要使用Android Studio Canary Arctic Fox的版本。

介绍

Jetpack Compose 是可以和Android组件比如Fragment进行交互的。因此如果你如果你已有一个现成的项目想要转到Jetpack Compose,你不需要做任何修稿。

但是如果你想迁移整个应用不再要任何Android组件比如Fragment,或者说你要用Compose创建一个全新的应用,那这篇文章就是为你准备的。

我们将用Navigation这个架构组件去实现页面之间的跳转。如果你还不熟悉Navigation的话,我强烈建议你阅读这篇文章:jitpack-navigation-component

用Compose创建一个有两个页面的应用

与传统的Android开发不同的是,Jetpack Compose没有Fragment,Activity这些约束。用Jetpack Compose可以用任意一个Composable的函数来表示页面的一部分或者是整个页面。如果你是从头开始创建一个项目的话,我建议你只用Composable,充分利用Compose强大的能力。

为了充分理解Compose的Navigation,让我们创建一个包含两个页面的Subscription:SubscriptionsListScreenSubscriptionDetailsScreen。看一下代码:

class Subscription(val name : String, val price : String, val details : String)

@Composable
fun SubscriptionsListScreen(subscriptionslist : List<Subscription>){
    LazyColumn(
        modifier = Modifier.fillMaxWidth()
    ){
        items(subscriptionslist){subscription->
            SubscriptionsListItem(subscription)
        }
    }
}

@Composable
fun SubscriptionsListItem(subscription : Subscription){
    Column(
        modifier = Modifier.fillMaxWidth()
    ){
        Text(text = subscription.name, style = MaterialTheme.typography.h6)
    }
}

@Composable
fun SubscriptionDetailsScreen(subscriptionName : String){
    Column(
        modifier = Modifier.fillMaxWidth()
    ){
        val subscription = SubscriptionsDatabase.subsList.single { it.name.equals(subscriptionName, true)  }
        Text(text = subscription.name, style = MaterialTheme.typography.h5)
        Text(text = subscription.name, style = MaterialTheme.typography.body1)
        Text(text = subscription.details, style = MaterialTheme.typography.body2)
    }
}
复制代码

这段代码里面,有3个Composable函数代表了两个页面:

  • SubscriptionsListScreen是表示subscription列表页面的Composable,参数是订阅列表。
  • SubscriptionsListItem表示每个订阅项的UI,参数是一个Subscription类。
  • SubscriptionDetailsScreen表示订阅的详情,从列表可以调到详情页,参数是Subscription类。

这里,SubscriptionsListScreen和SubscriptionDetailsScreen分别表示列表和详情页。和传统的开发相比它们和Activity和Fragment是一样的。SubscriptionsListItem表示了页面的一部分,就想RecyclerView的Adapter Item一样。

我们的任务是在这两个页面之间用Navigation框架实现跳转,同时传递订阅的信息。

集成

为了让Navigation能在Compose里面工作起来,我们得加上下面的依赖。有了这个依赖Navigation才能和Composable函数适配:

implementation "androidx.navigation:navigation-compose:1.0.0-alpha08"
复制代码

Navigation框架

在了解Navigation组件之前,你得知道Android 里的remember是啥。在声明式UI系统里面,代码本身就是用来描述UI的。需要在任何时候都能表示UI而不仅仅是在初始化的时候。

为了达到这个目的,我们需要尽可能简单有效的维护一个状态,Jetpack Compose提供了remember。remember会保持对状态的跟踪。为了更好的理解和学习remember和其他Jetpack Compose的组件,可以阅读下面的文章Jetpack Compose 组件(Part 2)

现在可以开始对Composable实现一个Navigation框架了。通过上面集成的库,Navigation组件已经完全适配Composable了。

首先我们需要学习的是remmeberNavController这个Composable函数,它创建了一个NavHostController来处理ComposeNavigator。我们可以用NavHostController来实现两个Composable函数之间的跳转。

val navController = rememberNavController()
复制代码

接着,我们需要创建一个包含所有页面的对象的密封类。对于我们的例子,我们有两个对象:SubscriptionsList和SubscriptionDetails。看一下代码:

sealed class Screen(val route: String, @StringRes val resourceId: Int) {
    object Home : Screen("SubscriptionsList", R.string.subs_list)
    object Details : Screen("SubscriptionDetails", R.string.sub_details)
}
复制代码

现在可以创建主Composable了,将所有的compose页面嵌套到NavHost这个Composable函数里面。一旦这个被调用,任何只要在NavGraphBuilder里面配置的Composable都可以通过navController进行跳转。在我们的例子里面,有两个页面需要在NavHost里面声明,如下所示:

@Composable
fun SubscriptionsMain() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) {
            SubscriptionsListScreen( SubscriptionsDatabase.subsList)
        }
        composable(Screen.Details.route) {
            SubscriptionDetailsScreen( /* TODO paramter implementation pending */)
        }
    }
}
复制代码

这里的Composable是NavGraphBuilder的扩展函数,用来往NavGraphBuilder上添加Composable的。下一步就是传递参数。SubscriptionDetailsScreen组合函数需要一个Subscription的数据类来展示详情。

脑子里想象一下我们需要把SubscriptionsMain的组合函数渲染到MainActivity这个类。

为了支持参数和deep link,Composable函数有两个默认值是emptyList的list类型的参数。直接看下这个函数到底长啥样:

public fun NavGraphBuilder.composable(
    route: String,
    arguments: List<NamedNavArgument> = emptyList(),
    deepLinks: List<NavDeepLink> = emptyList(),
    content: @Composable (NavBackStackEntry) -> Unit
)
复制代码

这里的argumens和deepLinks是可选的。在我们的例子里面,我们需要将参数传递给详情页,首页两个参数都不需要。基本上,我们需要将订阅的名称用字符串类型作为参数传递进去,看一下代码:

@Composable
fun SubscriptionsMain() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) {
            SubscriptionsListScreen( SubscriptionsDatabase.subsList)
        }
        composable(Screen.Details.route,
            arguments = listOf(navArgument("name") { type = NavType.StringType }) {
            SubscriptionDetailsScreen( it.arguments?.getString("name") ?: 0)
        }
    }
}
复制代码

如果你已经看了我们更新的SubscriptionsMain函数的话,在详情页的Composable里面,我们已经声明了订阅的名称作为一个字符串类型,并且我们在lambda的表达式里面用到了。

实现Composable函数之间的跳转

总的来说,想实现任何Android组件之间的跳转,需要借助navController。Composable函数也一样。从一个Composable触发或者是启动另一个Composable都需要navController。

与传统的方式不同的是,Composable还不能直接找到navController。因此需要将我们在SubscriptionsMain中创建的navController传递进去,目标Composable函数不要用navController作为一个入参。在我们的例子里面,SubscriptionsListScreen和SubscriptionsListItem需要navController。看一下修改完的Composable:

@Composable
fun SubscriptionsListScreen(navController: NavController, subscriptionslist : List<Subscription>){
    LazyColumn(
        modifier = Modifier.fillMaxWidth()
    ){
        items(subscriptionslist){subscription->
            SubscriptionsListItem(navController, subscription)
        }
    }
}

@Composable
fun SubscriptionsListItem(navController: NavController, subscription : Subscription){
    Column(
        modifier = Modifier.fillMaxWidth()
    ){
        Text(text = subscription.name, style = MaterialTheme.typography.h6)
    }
}
复制代码

现在我们需要将navController传给修改完的函数:

@Composable
fun SubscriptionsMain() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) {
            SubscriptionsListScreen( navController, SubscriptionsDatabase.subsList)
        }
        composable(Screen.Details.route,
            arguments = listOf(navArgument("name") { type = NavType.StringType }) {
            SubscriptionDetailsScreen( it.arguments?.getString("name") ?: 0)
        }
    }
}
复制代码

现在我们可以从订阅列表页跳转到详情页了,看一下代码:

@Composable
fun SubscriptionsListItem(navController: NavController, 
                          subscription : Subscription){
    Column(
        modifier = Modifier.fillMaxWidth()
            .clickable {
                /* Navigation to details screen with subscription name */
                navController.navigate("details/${subscription.name}")
            }
    ){
        Text(text = subscription.name, style = MaterialTheme.typography.h6)
    }
}
复制代码

就这些了。我们已经学习了如何创建Navigation的框架,并且在不用页面跳转中传递参数。

自定义对象的参数

Jetpack的Navigation 目前还处在alpha的版本,像传递parcelable对象这种需求目前还不支持。有一种解决方案,我们可以吧对象转成gson字符串,然后在接收的地方再解析成对象。

首先我们需要添加如下的gson依赖到我们的项目里面:

implementation "com.google.code.gson:gson:2.8.6"
复制代码

然后我们需要把自定义对象转成字符串,将字符串作为参数,代码:

val gsonString = Gson().toJson(subscription)
navController.navigate("details/$gsonString")
复制代码

在另一边,我们可以通过fromJson将字符串转成对象。看代码:

@Composable
fun SubscriptionsMain() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) {
            SubscriptionsListScreen( navController, SubscriptionsDatabase.subsList)
        }
        composable(Screen.Details.route,
            arguments = listOf(navArgument("name") { type = NavType.StringType })) {
            it.arguments?.getString("name")?.let {
                val subscription = Gson().fromJson(it,Subscription::class.java)
                SubscriptionDetailsScreen( subscription )
            }
        }
    }
}
复制代码

That's All

希望你有一些收获,感谢你的阅读。更多Android进阶知识,扫码即可领取~

<img src="https://hnxx.oss-cn-shanghai.aliyuncs.com/official/1704935888404.jpg?t=0.014764499839815759" style="margin: auto" />


 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值