夜雨聆风学习资料网

ARTICLE · 1295373

保姆级学习开发安卓手机软件实战(十八)--Navigation路由补充(带传入参数)

保姆级学习开发安卓手机软件实战(十八)--Navigation路由补充(带传入参数)
之前我们切换界面用的Navigation,但是都是没有传入参数的界面,这次我们来说说怎么加上有参数的路由。
以本软件为说明用例,我们的设备列表界面存在设备后,点击单个设备卡片,可以进入该设备的详细展示界面。这时候,就需要把设备id传入打开窗体中。
首先先把设备详细界面的框架搞出来(里面内容我就不加了哈)。
新建ui\screen\DeviceDetailViewModel.kt(主要就是之前我们设备列表界面是接受所有设备信息,这里筛选出选择仪器的信息):
package com.example.jstz1.ui.screenimport android.app.Applicationimport androidx.lifecycle.AndroidViewModelimport androidx.lifecycle.viewModelScopeimport com.example.jstz1.data.model.DeviceStateimport com.example.jstz1.data.remote.DeviceEventimport com.example.jstz1.data.remote.DeviceSessionManagerimport com.example.jstz1.data.remote.ManualCtrlimport com.example.jstz1.data.remote.PacketBuilderimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.StateFlowimport kotlinx.coroutines.flow.asStateFlowimport kotlinx.coroutines.launch/** * 设备详情页ViewModel * @param application Application上下文,AndroidViewModel要求,避免持有Activity上下文导致内存泄漏 * @param devId 当前页面查看的设备ID,固定为本页面的目标设备 */class DeviceDetailViewModel(    application: Application,    private val devId: String) : AndroidViewModel(application) {    /**     * 设备最新状态流     * null:离线 / 尚未收到该设备的状态数据     * MutableStateFlow:内部可变,仅ViewModel内部修改     */    private val _state = MutableStateFlow<DeviceState?>(null)    /**     * 对外暴露只读状态流,Compose页面collect观察UI刷新     */    val state: StateFlow<DeviceState?> = _state.asStateFlow()    init {        // viewModelScope:ViewModel生命周期协程,ViewModel销毁自动cancel,不会泄漏        viewModelScope.launch {            // 全局设备事件管理器:订阅所有设备推送过来的事件            DeviceSessionManager.events.collect { e ->                when (e) {                    // 设备状态更新事件                    is DeviceEvent.StateUpdated -> {                        // 只处理和当前页面devId匹配的设备,过滤其他设备事件                        if (e.state.devId == devId) {                            _state.value = e.state                        }                    }                    // 设备离线事件                    is DeviceEvent.Offline -> {                        if (e.devId == devId) {                            // 置为null,UI上展示离线                            _state.value = null                        }                    }                    // 设备上线事件:当前逻辑暂时不单独处理,按需在这里加逻辑                    is DeviceEvent.Online -> Unit                }            }        }    }}
    新建ui\screen\DeviceDetailScreen.kt:
/** * 设备详情页面 * @param devId 设备唯一ID【路由路径参数,由navController跳转时传入】 * @param ip 可选设备ip,非路由必传参数,按需传递 * @param onBack 返回上一页回调 * @param modifier 外层修饰符 * @param currentUser 当前登录用户信息,用于顶部栏展示 * @param onLogout 登出回调,点击顶部栏登出按钮触发 */@Composableinternal funDeviceDetailScreen(    devId: String,    ip: String? = null,    onBack: () -> Unit,    modifier: Modifier = Modifier,    currentUser: String? = null,    onLogout: () -> Unit = {}) {    // 获取Application上下文,给ViewModel使用,避免内存泄漏(不要直接拿Activity context)    val app: android.app.Application =        LocalContext.current.applicationContext as android.app.Application    // 构造ViewModel,传入Application + 路由拿到的devId    // viewModelFactory:自定义工厂,用来给ViewModel传构造参数(devId不能无参构造)    val vm: DeviceDetailViewModel = viewModel(        factory = viewModelFactory {            initializer { DeviceDetailViewModel(app, devId) }        }    )    Surface(modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {        Column(Modifier.fillMaxSize()) {            // 顶部导航栏:标题展示devId,支持返回、展示用户、登出            TopBar(                title = devId,                onBack = onBack,                currentUser = currentUser,                onLogout = onLogout            )        }    }}
这个界面只放了一个标题栏显示我们进入的是哪个仪器界面,可以看出绑定vm的方式有所不同,要传入devId。
那还有因为我们是设备列表点进来的卡片,所以设备列表界面也要做修改:
然后一层层传进去:
这一层层有点晕哈,如果大家没有分出很多文件,不需要这么多层哈,我是因为模块复用了,反正总的意思就是点击卡片,在最里层我们是知道设备id的,然后一层层传出去。
然后卡片的点击事件也记得要完善哈。
最后是新增路由:
const val DEV_DETAIL = "devDetail/{devId}"// 函数:根据 devId 拼接出真实路由fundetail_Dev(devId: String)="devDetail/$devId"
有关导航,也就是设备id通过设备列表界面传出来了,然后要跳转到设备详情页:
然后设备详情页的导航设置:
// 带参数的详情页        composable(            route = Route.DEV_DETAIL,            arguments = listOf(navArgument("devId") { type = NavType.StringType })//arguments:声明这个路由需要接收的参数定义        ) { entry ->            // entry = backStackEntry:导航栈条目,存放本次跳转携带的所有参数            // 从导航参数包里取出 devId;为空兜底返回空字符串                        val devId = entry.arguments?.getString("devId").orEmpty()            DeviceDetailScreen(                devId       = devId,//传入界面供界面和vm使用                currentUser = currentUser,                onLogout    = { Logout() },                onBack      = { navController.popBackStack() }            )        }

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料