Compose Navigation

2023-05-26/2023-05-26

Compose Navigation

迁移到Navigation

添加依赖项

dependencies {
  implementation "androidx.navigation:navigation-compose:{latest_version}"
  // ...
}

https://developer.android.com/jetpack/androidx/releases/navigation?hl=zh-cn

在这个链接处找到最新版 Navigation Compose。

设置NavController

我们使用NavController来操纵堆栈,即控制页面的路由,我们需要在Screen处就创建NavController,然后,所有需要引用 NavController 的可组合项都可以访问它

设置NavHost

Navigation 的 3 个主要部分是 NavControllerNavGraphNavHostNavController 始终与一个 NavHost 可组合项相关联。NavHost 充当容器,负责显示导航图的当前所在页面。当在可组合项之间进行导航时,NavHost 的内容会自动进行重组

设置NavGraph

使用 [NavGraphBuilder.composable](https://developer.android.com/reference/kotlin/androidx/navigation/compose/package-summary?hl=zh-cn#(androidx.navigation.NavGraphBuilder).composable(kotlin.String, kotlin.collections.List, kotlin.collections.List, kotlin.Function1)) 扩展函数来将各个可组合目的地添加到导航图中,并定义必要的导航信息。接收的参数为一个string类型的路由地址,还可以配置带参数的导航以及deepLink

扩展函数里的则是对应的screen

此时我们可以写出这样的代码

@Composable
fun RallyNavHost(
    navController: NavHostController,
    modifier: Modifier = Modifier
) {
    NavHost(
        navController = navController,
        startDestination = Overview.route,
        modifier = modifier
    ) {
        composable(route = Overview.route) {
            OverviewScreen(
                onClickSeeAllAccounts = {
                    navController.navigateSingleTopTo(Accounts.route)
                },
                onClickSeeAllBills = {
                    navController.navigateSingleTopTo(Bills.route)
                },
                onAccountClick = { accountType ->
                    navController.navigateToSingleAccount(accountType)
                }
            )
        }
        composable(route = Accounts.route) {
            AccountsScreen(
                onAccountClick = { accountType ->
                    navController.navigateToSingleAccount(accountType)
                }
            )
        }
        composable(route = Bills.route) {
            BillsScreen()
        }
        composable(
            route = SingleAccount.routeWithArgs,
            arguments = SingleAccount.arguments,
            deepLinks = SingleAccount.deepLinks
        ) { navBackStackEntry ->
            val accountType =
                navBackStackEntry.arguments?.getString(SingleAccount.accountTypeArg)
            SingleAccountScreen(accountType)
        }
    }
}

其中RallyNavHost是对NavHost的封装

每一个composable代表一个可路由项

导航堆栈控制

防止重复导航

比如我们重复调用navigate到同一个页面,这显然是不行的,应该是只创建单一页面,类似于创建Activity里的SingleTop

可以使用navController.navigate(route) { launchSingleTop = true }

解决

回到最开始的页面

可以使用

popUpTo(startDestination) { saveState = true }` - 弹出到导航图的起始目的地,以免在您选择标签页时在返回堆栈上积累大量目的地

restoreState = true- 确定此导航操作是否应恢复PopUpToBuilder.saveStatepopUpToSaveState` 属性之前保存的任何状态。请注意,如果之前未使用要导航到的目的地 ID 保存任何状态,此项不会产生任何影响

我们可以封装上述操作为一个扩展函数:

fun NavHostController.navigateSingleTopTo(route: String) =
    this.navigate(route) {
        // Pop up to the start destination of the graph to
        // avoid building up a large stack of destinations
        // on the back stack as users select items
        popUpTo(
            this@navigateSingleTopTo.graph.findStartDestination().id
        ) {
            saveState = true
        }
        // Avoid multiple copies of the same destination when
        // reselecting the same item
        launchSingleTop = true
        // Restore state when reselecting a previously selected item
        restoreState = true
    }

tab状态更改

我们需要在重组过程中得到当前最新的页面从而更改tab的选中状态,流程大概是这样的

tab提供选中的click监听—某个tab被click—使用navigator改变页面—触发重组—重组过程中拿到最新的页面—通过最新的页面去更改tab的状态

可以通过下面俩行代码拿到最新的页面

val currentBackStack by navController.currentBackStackEntryAsState()
        val currentDestination = currentBackStack?.destination

然后就可以通过currentDestination去更新tab了

带参数的Navigate以及Deep Link

带参数的导航

带参数的Navigate以及Deep Link需要在composable扩展函数中定义arguments参数,比如

composable(
    route =
        "${SingleAccount.route}/{${SingleAccount.accountTypeArg}}",
    arguments = listOf(
        navArgument(SingleAccount.accountTypeArg) { type = NavType.StringType }
    )
) {
    SingleAccountScreen()
}

object SingleAccount : RallyDestination {
    // Added for simplicity, this icon will not in fact be used, as SingleAccount isn't
    // part of the RallyTabRow selection
    override val icon = Icons.Filled.Money
    override val route = "single_account"
    const val accountTypeArg = "account_type"
    val routeWithArgs = "$route/{$accountTypeArg}"
    val arguments = listOf(
        navArgument(accountTypeArg) { type = NavType.StringType }
    )
    val deepLinks = listOf(
        navDeepLink { uriPattern = "rally://$route/{$accountTypeArg}" }
    )
}

然后,就可以使用

navBackStackEntry.arguments?.getString(SingleAccount.accountTypeArg)

获取Navigate过程携带的参数了

Deep Link

首先要配置AndroidManifest

通过 <activity> 内的 <intent-filter> 创建一个新的 intent 过滤器,相应操作为 VIEW,类别为 BROWSABLEDEFAULT。使用 data 标记添加 **scheme**和host

<activity
    android:name=".RallyActivity"
    android:windowSoftInputMode="adjustResize"
    android:label="@string/app_name"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="rally" android:host="single_account" />
    </intent-filter>
</activity>

接下来在composable扩展函数中配置deeplink

composable(
    route = SingleAccount.routeWithArgs,
    // ...
    deepLinks = listOf(navDeepLink {
        uriPattern = "rally://${SingleAccount.route}/{${SingleAccount.accountTypeArg}}"
    })
)

使用adb验证:

在已连接的模拟器或设备上重新安装应用,打开命令行并执行以下命令,以便模拟深层链接启动

adb shell am start -d "rally://single_account/Checking" -a android.intent.action.VIEW


标题:Compose Navigation
作者:OkAndGreat
地址:http://zhongtai521.wang/articles/2023/05/26/1691545336695.html

评论
发表评论
       
       
取消