Jetpack Compose学习(11)——Navigation页面导航的使用

原文:Jetpack Compose学习(11)——Navigation页面导航的使用 - Stars-One的杂货小窝

在Android原生的View开发中的,也是有Navigation,原生我之后可能再出篇教程,今天讲解的则是compose版本的Navigation组件的使用

本系列以往文章请查看此分类链接Jetpack compose学习

说明

在原生中,官方比较推荐使用单Activity+多Fragment模式来构建一个APP,而Fragment的切换需要使用FragmentManager来进行管理,比如add replace等方法,对于开发者来说,也是比较繁琐,于是官方在Jetpack组件中就是推出了Navigation的库,可以方便开发者快速去管理多Fragment的页面堆栈问题

而今天的主要针对compose,compose的架构也是一个Activity+多个可组合项,而如何去切换可组合项?

以往的做法就是定义一个选中下标,下标更改了,重新渲染一下页面,从而达到切换页面的效果

而有了Navigation,则是可以在使用层面更为优雅和规范,也是方便管理,不易出现奇奇怪怪的堆栈问题等

基本使用

这里我们以一个使用了NavigationBar组件的页面进行改造

PS:这里我是新版本的AS创建的项目,默认使用的是Material3的依赖包,所以底部导航栏组件就是NavigationBar,如果是Material2的依赖包,则是叫BottomNavigation

效果如下图所示:
Jetpack Compose学习(11)——Navigation页面导航的使用

就是实现了两个底部菜单,然后可以进行页面的切换,代码如下所示:

@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
fun MyApp() {
    
    var currentSelect by remember {
        mutableStateOf(0)
    }

    val menuData = listOf(
        BottomItemData("首页", Icons.Filled.Home),
        BottomItemData("设置", Icons.Filled.Settings)
    )

    Scaffold(modifier = Modifier.fillMaxSize()
    , bottomBar = {
        NavigationBar() {
            menuData.forEachIndexed { index, bottomItemData ->
                NavigationBarItem(
                    selected = index == currentSelect,
                    onClick = {
                        currentSelect = index
                    },
                    icon = {
                        Icon(
                            imageVector = bottomItemData.icon,
                            contentDescription = "点击按钮"
                        )
                    },
                    label = {
                        Text(
                            text = (bottomItemData.label)
                        )
                    },
                )
            }
        }
    }
    ) { innerPadding ->
        //下面第3步就是对此部分代码进行更改
        
        //IDE强制要使用者innerPadding,这里就简单的打印一下
        println(innerPadding)
        if (currentSelect == 0) {
            Text("首页")
        }else{
            Text("设置")
        }

    }
}

从上面代码中,我们可以看到我们是通过currentSelect这个变量来进行页面的显示管理,实际上这里管理会比较繁琐,这个时候就得用到今天的主角Navigation

1.引入依赖

implementation "androidx.navigation:navigation-compose:2.5.3"

2.声明NavController

首先,就是使用rememberNavController获得NavController对象

//导航
val navController = rememberNavController()

PS: 之后的跳转和返回上一级就是使用此对象过来就行

3.使用NavHost,声明页面路由

在布局content参数的函数里使用下面这段代码:

NavHost(navController = navController, startDestination = "MainPage") {
    //声明名为MainPage的页面路由
    composable("MainPage"){
        //页面路由对应的页面组件
        MainPage()
    }
    composable("SettingPage"){
        SettingPage()
    }
}

上面是声明了两个页面,MainPage和SettingPage则是它两的路由名,后面页面跳转需要使用到

NavHost方法中,参数需要上一步的NavController对象,startDestination则是开始的页面为MainPage

简单的解释,就是可以把NavHost也看出一个页面组件,这个组件默认显示的是MainPage页面

源码如下,点击展开
@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
fun MyApp() {
    var currentSelect by remember {
        mutableStateOf(0)
    }

    //导航
    val navController = rememberNavController()

    val menuData = listOf(
        BottomItemData("首页", Icons.Filled.Home),
        BottomItemData("设置", Icons.Filled.Settings)
    )

    Scaffold(modifier = Modifier.fillMaxSize()
    , bottomBar = {
        NavigationBar() {
            menuData.forEachIndexed { index, bottomItemData ->
                NavigationBarItem(
                    selected = index == currentSelect,
                    onClick = {
                        currentSelect = index
                    },
                    icon = {
                        Icon(
                            imageVector = bottomItemData.icon,
                            contentDescription = "点击按钮"
                        )
                    },
                    label = {
                        Text(
                            text = (bottomItemData.label)
                        )
                    },
                )
            }
        }
    }
    ) { innerPadding ->
        //IDE强制要使用者innerPadding,这里就简单的打印一下
        println(innerPadding)

        NavHost(navController = navController, startDestination = "MainPage") {
            composable("MainPage"){
                MainPage()
            }
            composable("SettingPage"){
                SettingPage()
            }
        }

    }
}

@Preview(showBackground = true)
@Composable
fun MainPage(){
    Column() {
        Text(text = "主页")
    }
}

@Preview(showBackground = true)
@Composable
fun SettingPage() {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "设置页面")
    }
}

3.页面跳转

下面则是两个重要的方法:

  • navController.navigate() 页面跳转
  • navController.popBackStack() 回上一级,相当于点击返回键

这里,我们结合NavigationBar实现点击底部按钮切换不同的页面

实现效果如下图所示:
Jetpack Compose学习(11)——Navigation页面导航的使用

代码如下图所示
Jetpack Compose学习(11)——Navigation页面导航的使用

PS:这里需要注意,如果使用了navController.navigate()来进行页面跳转,实际上就是和打开一个新的Activity类似,存在一个页面堆栈

比如说上图的效果,如果点返回键,则是回到上次的页面,而不会立即退出APP

而且,每次调用navigate()会创建一个新的页面对象

如果需要单一创建一个对象,可以这样写

navController.navigate("MainPage"){
    launchSingleTop = true
}

之后不过怎么跳转,都会用的同一个页面对象

不过一般我们会是在页面之间跳转的,比如说要再MainPage页面跳转到SettingPage页面,要怎么写呢?

Jetpack Compose学习(11)——Navigation页面导航的使用

MainPage传递NavController的参数,之后页面中的按钮来可以使用此对象来完成页面跳转的功能

不过官方比较推荐我们传递一个函数参数,如下代码所示:

//方法需要传函数参数
@Composable
fun MainPage(toSettingPage:()->Unit) {
    Column() {
        Text(text = "主页")
        Button(onClick = { toSettingPage.invoke() }) {
            Text("跳转到设置页面")
        }
    }
}

NavHost(navController = navController, startDestination = "MainPage") {
    composable("MainPage"){
        //这里也要同步修改
        //下面的是kotlin中的lambda的简化写法,不明白的可以先去了解下
        MainPage(){
            navController.navigate("SettingPage")
        }
    }
    composable("SettingPage"){
        SettingPage()
    }
}

如何传参

上面的基本使用,例子也是比较简单

一般情况下,我们可能跳转到新页面的时候需要传递数据,比如说从列表页到详情页,需要传递点击的item项的id,从而详情页可以拿到id去请求接口从而获得详情的数据并展示,这个时候该如何操作呢?

针对上面的代码,我们假设进入Setting页面,需要传递一个字符串的参数名为id,则有下面的步骤:

1.修改NavHost定义路由

NavHost(navController = navController, startDestination = "MainPage") {
    composable("MainPage"){
        MainPage(){
            navController.navigate("SettingPage")
        }
    }
    composable("SettingPage/{id}"){
        //这里获取传参过来数据
        val id = it.arguments?.getString("id")
        //可以传入SettingPage中,然后在页面中使用数据,这里我就没写了
        SettingPage()
    }
}

这里上面,可以看到加了个{id}(有点类似Spring Boot里的接口写法),就是标明需要传参的意思

这里实际上还可以声明类型,默认不写的话,就是说id是String类型的

arguments这个变量实际上就是我们Intent中常用的Bundle,所以获取数值的是调用了getString()方法

设置参数的类型

如果想要id是整数的话,应该怎么做呢?

如下面代码所示:

composable("SettingPage/{id}", arguments = listOf(navArgument("id") {
    type = NavType.IntType
})) {
  //省略....
}

通过arguments参数来设置传参对应配置,接收的是一个列表,也很好理解,因为会有多个参数嘛

而list里的元素的类型,则是NamedNavArgument,官方提供navArgument()方法方便我们快速构造NamedNavArgument类型

我们可以看看navArgument方法的参数

navArgument(
    name: String,
    builder: NavArgumentBuilder.() -> Unit
)

name则是需要与我们路由上定义的参数名一致,而builder则是一个函数,我们可以写个函数来快速进行参数的配置,主要可以设置以下三个属性:

  • type 类型
  • defaultValue 默认值
  • nullable 是否可空

PS:改了参数类型,对应的取值调用的getXX等方法也要修改哦!

可选参数

上面的例子,实际上是必传的一个参数,如果需要可选参数,有以下规则:

  1. 可选参数必须使用查询参数语法?argName={argName} 来添加
  2. 可选参数必须具有 defaultValue nullable = true (将默认值设置为 null)

第二点就是上面提到过的两个属性,例如我们加多一个age的可选参数:

composable("SettingPage/{id}?age={age}", arguments = listOf(
    navArgument("id") {
        type = NavType.IntType
    }, navArgument("age") {
        type = NavType.IntType
        defaultValue = 0
    }
)) {
    //省略...
}

2.调用传参

上面说了那么多,那么应该如何去调用呢?

NavHost(navController = navController, startDestination = "MainPage") {
    composable("MainPage") {
        MainPage() {
            //跳转的时候传参
            navController.navigate("SettingPage/11?age=18")
        }
    }

    composable("SettingPage/{id}?age={age}", arguments = listOf(
        navArgument("id") {
            type = NavType.IntType
            defaultValue = 0
            nullable = true
        }, navArgument("age") {
            type = NavType.IntType
            defaultValue = 0
        }
    )) {
        //这里获取传参过来数据
        val id = it.arguments?.getInt("id")
        Log.d("starsone", "MyApp: $id")
        SettingPage()
    }
}

由于上面,我们是将跳转封装在了函数,所以只需要调整MainPage路由里的函数即可navController.navigate("SettingPage/11?age=18")

当然,这里age是可以不传的,默认则是0

关于写法优化

路由常量管理

在上面的例子的,我们涉及到的页面也是比较少,但是,在APP更新迭代之后,页面可以会成倍地增加

这个时候我们如果还是每个路由直接写死的一个字符串变量,那么万一哪天不小心改了,就会导致路由无法跳转了,针对这个问题,解决思路也比较简单,使用枚举或者常量来管理路由名就可

object Pages{
    const val MainPage = "mainPage"
    const val SettingPage = "settingPage"
}

NavHost(navController = navController, startDestination = Pages.MainPage) {
    composable(Pages.MainPage) {
        MainPage() {
            navController.navigate("${Pages.SettingPage}/11")
        }
    }

    composable("${Pages.SettingPage}/{id}?age={age}", arguments = listOf(
        navArgument("id") {
            type = NavType.IntType
            defaultValue = 0
            nullable = true
        }, navArgument("age") {
            type = NavType.IntType
            defaultValue = 0
        }
    )) {
        //这里获取传参过来数据
        val id = it.arguments?.getString("id")
        Log.d("starsone", "MyApp: $id")
        SettingPage()
    }
}

嵌套导航(多路由组合)

除此之外,如果NavHost里的composable多了起来,也不好方便管理是吧,我们可以按照业务逻辑来进行一个嵌套导航(嵌套导航结构)

我自己按照理解称呼为多路由组合,即把页面包含下钻页面的几个页面整合到一起

在NavHost里使用navigation()来实现嵌套导航结构:

NavHost(navController, startDestination = "home") {
    ...
    // 当导航到 login 路由时,自动找到 username 目标进行显示
    navigation(startDestination = "username", route = "login") {
        composable("username") { ... }
        composable("password") { ... }
        composable("registration") { ... }
    }
    ...
}

为了更好的复用,我们还可以将其抽取成一个方法(这里官方就是使用了扩展方法):

fun NavGraphBuilder.loginGraph(navController: NavController) {
    navigation(startDestination = "username", route = "login") {
        composable("username") { ... }
        composable("password") { ... }
        composable("registration") { ... }
    }
}

NavHost(navController, startDestination = "home") {
    ...
    // 当导航到 login 路由时,自动找到 username 目标进行显示
    loginGraph()
    ...
}

composable传递函数

上面也是说到官方不推荐我们传NavController实例到composable里,因为composable需要单元测试,且单元测试的时候传个NavController太多余

所以推荐我们使用函数

但是这里研究过后,发现有个问题官方没有提到,如果我页面里可能有几个按钮,且会跳转到不同的参数呢?

这个时候,就是需要对函数进行封装,我们以上面给的代码为例

@Composable
fun MainPage(toSettingPage:()->Unit) {
    Column() {
        Text(text = "主页")
        Button(onClick = { toSettingPage.invoke() }) {
            Text("跳转到设置页面")
        }
    }
}

NavHost(navController = navController, startDestination = "MainPage") {
    composable("MainPage"){
        //这里也要同步修改
        //下面的是kotlin中的lambda的简化写法,不明白的可以先去了解下
        MainPage(){
            navController.navigate("SettingPage")
        }
    }
    composable("SettingPage"){
        SettingPage()
    }
}

下面进行修改


NavHost(navController = navController, startDestination = Pages.PAGE_MAIN) {
    //这里定义一个页面跳转的action,接收路由和参数列表数据
    val navAction = { route: String, args: List<Pair<String, Any>> ->
        val value = args.joinToString("&") { it.first + "=" + it.second }

        val fullRoute = when {
            args.isEmpty() -> route
            route.contains("?") -> "{$route}$value"
            else -> "$route?$value"
        }
        //拼接路由和参数列表,形成真正的路由
        Log.d("starsone", "MyApp跳转路径: $fullRoute")
        
        //调用方法进行页面跳转
        navController.navigate(fullRoute) {
            popUpTo(navController.graph.findStartDestination().id) {
                saveState = true
            }
            launchSingleTop = true
            restoreState = true
        }
    }
    
    composable("subject") {
        SubjectPage(navController = navController)
    }

    composable(Pages.PAGE_MAIN) {
        MainPage(navController = navController)
    }

    composable(Pages.PAGE_SETTING) {
        SettingPage()
    }

}

@Composable
fun IndexPage(lambda: (String, List<Pair<String, Any>>) -> Unit) {
    Surface() {
        //传递路由和名称
        Button(onClick = { lambda.invoke("setting", listOf(Pair("age",10))) }) {
            Text("测试")
        }
    }
}

上面是我想到的比较好的解决方案了,如果子页面也需要页面跳转,可以将函数进行往下传递

实际上,我个人觉得还是有些不优雅,因为还是去修改需要页面跳转的组件的参数列表,且因为多了个参数,导致组件就没法预览了,有些麻烦

不过上面是我想到的比较好的解决方案了,如果各位有更好的方案,欢迎评论区提出

与底部导航栏联用

这里以官方给出的示例,贴下代码并讲解

使用到了navController.currentBackStackEntryAsState()navBackStackEntry?.destination

您可以利用 NavController.currentBackStackEntryAsState() 方法从 NavHost 函数中获取 navController 状态,并与 BottomNavigation 组件共享此状态。这意味着 BottomNavigation 会自动拥有最新状态。

不过官方使用的是密封类里构造底部菜单数据,我这里则是使用了list直接构造,也差不了太多

可以看到只要在首页的话,再次点击即可退出APP,不会出现多余的页面堆栈的情况,动图如下:

Jetpack Compose学习(11)——Navigation页面导航的使用

@Composable
fun MyApp() {
    var currentSelect by remember {
        mutableStateOf(0)
    }

    //导航
    val navController = rememberNavController()

    val menuData = listOf(
        BottomItemData(Pages.PAGE_MAIN,"首页", Icons.Filled.Home),
        BottomItemData(Pages.PAGE_SETTING,"设置", Icons.Filled.Settings)
    )

    Scaffold(modifier = Modifier.fillMaxSize()
    , bottomBar = {
        NavigationBar() {
            val navBackStackEntry by navController.currentBackStackEntryAsState()
            val currentDestination = navBackStackEntry?.destination

            menuData.forEach { bottomItemData ->
                NavigationBarItem(
                    //设置当前项是否为选中
                    selected = currentDestination?.hierarchy?.any { it.route == bottomItemData.route } == true,
                    icon = {
                        Icon(
                            imageVector = bottomItemData.icon,
                            contentDescription = "点击按钮"
                        )
                    },
                    label = {
                        Text(
                            text = (bottomItemData.label)
                        )
                    },
                    onClick = {
                        navController.navigate(bottomItemData.route) {
                            //使用此方法,可以避免生成一个重复的路由堆栈
                            popUpTo(navController.graph.findStartDestination().id) {
                                saveState = true
                            }
                            //避免重复选择会创建一个新的页面副本
                            launchSingleTop = true
                            // Restore state when reselecting a previously selected item
                            //当重新选择之前已选择项目恢复页面状态
                            restoreState = true
                        }
                    },

                )
            }
        }
    }
    ) { innerPadding ->

        println(innerPadding)
        NavHost(navController = navController, startDestination = Pages.PAGE_MAIN) {
            composable(Pages.PAGE_MAIN) {
                MainPage()
            }
            composable(Pages.PAGE_SETTING) {
                SettingPage()
            }

        }
    }
}

//页面的枚举类
object Pages{
    const val PAGE_MAIN = "main"
    const val PAGE_SETTING = "setting"
}


//底部菜单的实体数据类,包含路由,文字和图标
data class BottomItemData(val route:String,val label: String, val icon: ImageVector)

//两个页面

@Composable
fun MainPage() {
    Surface() {
        Text(text = "首页")
    }
}
@Composable
fun SettingPage() {
    Surface() {
        Text(text = "设置")
    }
}

如何隐藏底部导航栏?

这里,我们可能会遇到一种常见的情况,在首页有个按钮,点击之后会进入到一个新的页面,而新的页面里是没有底部导航栏的,这种情况要怎么实现呢?

开始我也没啥头绪,直到看到了上面官方的例子,我们可以学以致用,还是用currentDestination这个对象来实现,我们只需要在bottomBar渲染出NavigationBar进行判断就可以了,如下面的代码所示:

//为了代码可读性,就稍微省略掉了一些代码

val menuData = listOf(
    BottomItemData(Pages.PAGE_MAIN, "首页", Icons.Filled.Home),
    BottomItemData(Pages.PAGE_SETTING, "设置", Icons.Filled.Settings)
)

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

Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = {
    //只要当页面为首页或者为设置页面,才展示底部菜单栏
    if (currentDestination?.hierarchy?.any { it.route == Pages.PAGE_MAIN || it.route == Pages.PAGE_SETTING } == true) {
        NavigationBar() {...}
    }
}

当然,我们还可以再进一步优化下写法:

val menuData = listOf(
    BottomItemData(Pages.PAGE_MAIN, "首页", Icons.Filled.Home),
    BottomItemData(Pages.PAGE_SETTING, "设置", Icons.Filled.Settings)
)
//将底部栏菜单数据的路由名整成一个list
val routes = menuData.map { it.route }

val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
 
Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = {
    //只要当页面为首页或者为设置页面,才展示底部菜单栏
    if (currentDestination?.hierarchy?.any { routes.contains(it.route) } == true) {
        NavigationBar() {...}
    }
}

PS:如果想要隐藏右侧的悬浮按钮,也是同理

之后下图就是演示的效果(实际上比较简单了,按钮跳转到另外的页面就可以了,代码就不贴出来了)

Jetpack Compose学习(11)——Navigation页面导航的使用

踩坑

上面的示例一般情况下可以正常使用,但是如果你的页面需要传参,就会导致底部栏无法出现选中的情况

对于此情况,需要将判断进行修改即可,如以下代码(其他杂的代码就省略了..)


NavigationBar() {
    menuData.forEach { bottomItemData ->
        NavigationBarItem(
            //设置当前项是否为选中
            selected = currentDestination.hierarchy.any {  it.route?.contains( bottomItemData.route) == true}
        )
    }
}


上面代码,简单讲解下,如果某个页面需要传参,路由名为setting?age={age},那么我们的bottomItemData.rote即为setting

所以才需要contains来进行判断即可满足条件,一般来说不会出现相同的路由名,所以使用上稍微注意下既可以了

深层链接

深层链接实际就是Intent-Filter的用法,这里还是比较少用,就不准备详细说明了,具体使用可以参考导航 - Jetpack Compose Docs

参考

原文链接:https://www.cnblogs.com/stars-one/p/17154864.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Jetpack Compose学习(11)——Navigation页面导航的使用 - Python技术站

(0)
上一篇 2023年4月18日
下一篇 2023年4月18日

相关文章

  • 关于RecycleView的子元素锁定吸顶效果

      最近有一关于列表元素锁定吸顶的需求。处理方案比较简单的,在外层布局添加一个相同的标题栏控件,根据控件在滚动过中的位置来判断显示隐藏,从而达到效果。使用的关键代码是: int[] loc = new int[2]; view.getLocationOnScreen(loc); int[] fitLoc = new int[2]; outView.getLo…

    Android 2023年4月18日
    00
  • 扫一扫,原来这么简单

    二维码识别技术已广泛应用在移动支付、实用工具、电商购物、社交通讯等场景。然而,在实际生活中,二维码容易遇到距离远、暗光、强光、污损、模糊和大角度倾斜等复杂场景,导致识别困难,扫码体验差。华为HMS Core 统一扫码服务(Scan Kit)为开发者们的APP带来一站式扫码解决方案,并且拥有高识别率和快速识别等特点。 距离太远、码图过小? 在停车场扫码缴费、上…

    Android 2023年4月17日
    00
  • 直播回顾 | 点击率提升400%,Ta是怎么做到的?

    Discovery第18期直播已于3月30日圆满结束,本期直播邀请天眼查做客直播间,从天眼查与华为Push用户增长服务合作历程切入,聚焦用户增长,分享提升应用活跃度和渠道ROI的经验与见解。一起来回顾本期精彩内容吧! 【精彩对话】 Q1: 天眼查为什么选择华为Push用户增长服务实现拉新、促活和转化? 刘树维:天眼查作为国内领先的商业查询平台,我们发现用户对…

    Android 2023年4月17日
    00
  • Android报”ActivityNotFoundException”如何解决?

    Android应用程序在运行过程中,如果没有正确设置相关的配置参数或者代码逻辑存在问题,就会导致某些异常情况的出现。其中,”ActivityNotFoundException”是其中比较常见的一种异常,它表示无法找到指定的Activity组件,本文将从原因和解决办法两个方面详细讲解。 一、异常原因 包名或类名指定错误 如果在AndroidManfiest.x…

    Android 2023年4月3日
    00
  • 集成华为运动健康服务干货总览

    在接入华为运动健康服务的过程中你是否遇到过权限申请有困难、功能不会用的情况? 本期超强精华帖,一帖汇总集成华为运动健康服务你可能需要的各类干货,还不赶紧收藏起来!开发有困难,随时可查阅~ 如果你有感兴趣或想进一步了解的内容,欢迎进行留言,或查看华为运动健康文档获取更多详情! 权限申请篇 在申请运动健康服务的权限的过程中,你是否遇到这些疑惑:申请审核时长是多久…

    Android 2023年4月17日
    00
  • 【FAQ】集成分析服务的常见问题及解决方案

    常见问题一:如何验证Analytics是否上报/接入成功?以及关键日志含义是什么? 在初始化Analytics SDK前添加SDK日志开关如下: HiAnalyticsTools.enableLog (); 2.初始化SDK代码如下: HiAnalyticsInstance instance = HiAnalytics.getInstance (this);…

    Android 2023年4月18日
    00
  • Android报”ConcurrentModificationException”如何解决?

    针对Android应用中出现”ConcurrentModificationException”异常的原因和解决办法,我来进行详细的讲解。 原因 该异常通常在同时操作同一个数据结构(如List、Map、Set等)时出现,因为在多线程操作时,如果数据结构正在被一个线程修改,而另一个线程正在尝试遍历它,那么就会引发”ConcurrentModificationEx…

    Android 2023年4月3日
    00
  • HMS Core 6.10.0版本发布公告

    分析服务 ◆ 事件分析下新增商品订阅分析报告,帮助开发者了解应用内用户付费订阅概况,评估订阅付费价值; ◆ 营销分析、用户质量、转化分析以及过滤器中,新增广告系列/广告任务通过ID进行搜索的功能,通过更便捷高效的数据分析体验,帮助开发者合理评估广告投放的后端转化效果。 查看详情>> 运动健康服务 ◆ 血糖数据类型在原有指尖血糖数据开放的基础上,新…

    Android 2023年5月6日
    00
合作推广
合作推广
分享本页
返回顶部