返回

Jetpack Compose 导航标签页数字显示问题解决

Android

NavigationSuiteScaffoldLayout 中标签页显示数字而非字符串资源的问题解析

使用 Jetpack Compose 构建用户界面时,NavigationSuiteScaffoldLayout 提供了一种便捷的方式来构建自适应导航布局。标签页的标题通常使用字符串资源(string resources)定义,以便于本地化和管理。但是,开发者可能会遇到标签页标题显示为数字,而不是预期的字符串资源的问题,影响用户体验。

问题根源:直接引用字符串资源 ID

该问题产生的根源在于,在构建 NavigationRailItemNavigationSuitelabel 时,错误地直接使用了字符串资源 ID,而不是将其解析为实际的字符串。

查看如下代码片段:

label = { Text(text = appDestinations.label.toString()) }

这里,appDestinations.label 是一个整型值,代表了字符串资源在 R.string 中的 ID。直接调用 toString() 方法只能将其转换为数字的字符串形式,并非真正的字符串内容。contentDescription 也犯了同样的错误。因此用户界面上只能看到数字编号而不是文字内容。

解决方案:正确解析字符串资源

为了正确显示字符串资源,必须使用 stringResource() 函数将资源 ID 转换为对应的字符串值。stringResource() 函数接收一个整型参数(资源 ID),并返回相应的字符串。下面介绍几种具体的实现方式。

1. 使用 stringResource() 函数

直接在 label 中调用 stringResource(),并传入字符串资源的 ID 作为参数。这是最简单且常用的解决方案。

操作步骤:

  1. 修改 NavigationRailItemNavigationSuitelabel 属性。
  2. stringResource(appDestinations.label) 替换 appDestinations.label.toString()
  3. 对于 contentDescription 同理操作, 用stringResource(appDestinations.contentDescription)替换appDestinations.contentDescription.toString()

代码示例:

NavigationRailItem(
    // ...其他代码...
    label = { Text(text = stringResource(appDestinations.label)) },
    icon = {
        Icon(
            imageVector = appDestinations.icon,
            contentDescription = stringResource(appDestinations.contentDescription)
        )
    },
    // ...其他代码...
)

修改NavigationSuite中的item:

item(
    // ...其他代码...
    label = { Text(text = stringResource(appDestinations.label)) },
    icon = {
        Icon(
            imageVector = appDestinations.icon,
            contentDescription = stringResource(appDestinations.contentDescription)
        )
    },
    // ...其他代码...
)

2. 在 AppDestinations 枚举类中添加属性

另一种方式是,为 AppDestinations 枚举类添加一个新属性,该属性在初始化时即解析好字符串资源。这有利于代码复用和提高性能。但是会增大程序体积。如果频繁添加内容,还是推荐上一种方案,比较灵活。

操作步骤:

  1. AppDestinations 枚举类添加一个 String 类型的属性 labelString
  2. 在枚举类的构造函数中,使用 stringResource() 函数解析 label 并赋值给 labelString
  3. NavigationRailItemNavigationSuitelabel 中直接使用 appDestinations.labelString

代码示例:

enum class AppDestinations(
    @StringRes val label: Int,
    val icon: ImageVector,
    @StringRes val contentDescription: Int
) {
    HOME(R.string.home, Icons.Default.Home, R.string.home),
    FAVORITES(R.string.favorites, Icons.Default.Favorite, R.string.favorites),
    SHOPPING(R.string.shopping, Icons.Default.ShoppingCart, R.string.shopping),
    PROFILE(R.string.profile, Icons.Default.AccountBox, R.string.profile);

    val labelString: String
        @Composable
        get() = stringResource(label)
    val contentDescriptionString: String
        @Composable
        get() = stringResource(contentDescription)

}
//在setContent方法内部的开头,最好是val navItems = listOf("Songs", "Artists", "Playlists")下方处,提前调用并缓存字符串,提高性能。
setContent{
    //缓存字符串
    val appDestinationLabels = remember {
        AppDestinations.entries.associateWith { it.labelString }
    }
    val contentDescriptionLabels = remember {
        AppDestinations.entries.associateWith { it.contentDescriptionString }
    }
    // ...
}

//使用
NavigationRailItem(
    // ...其他代码...
    label = { Text(text = appDestinationLabels[appDestinations] ?: "") },
    icon = {
        Icon(
            imageVector = appDestinations.icon,
            contentDescription = contentDescriptionLabels[appDestinations] ?: ""
        )
    },
    // ...其他代码...
)

NavigationSuite的修改方法相同,将对应的部分替换掉即可。这里使用remember进行缓存,提高了性能。

安全建议与补充说明

确保资源 ID 的正确性: 确保所使用的字符串资源 ID 存在于 strings.xml 文件中,避免应用崩溃或显示错误信息。同时注意检查是否有单词拼写错误。

处理多语言支持: 使用 stringResource() 函数能够方便地支持多语言。只需要为不同的语言提供相应的 strings.xml 文件,应用就能够自动加载对应的字符串。利用这一点可以轻松将应用推广到各个国家地区。

关于性能问题的讨论: 有的观点认为stringResource会造成性能问题,诚然,每帧都去查找对应的字符串的确浪费了一些资源。不过对于UI上的静态资源来说,这并不是一个很重要的缺点。根据具体需求,在调用次数多时可以使用remember进行缓存,降低开销。如果想在创建的时候就完成转换,可以使用枚举类的方式。总的来说还是要视情况选择合适的方案。