Jetpack Compose中通过navController传递相册图片Uri至目标页面
解决方案
1. 新增ViewModel共享选中的Uri状态
通过ViewModel实现MainActivity(或Compose组件)与导航逻辑的状态共享,存储选中的图片Uri:
class MainViewModel : ViewModel() { val selectedImageUri = MutableStateFlow<String?>(null) fun setSelectedImageUri(uri: String) { selectedImageUri.value = uri } }
2. 调整MainActivity代码
将选中的Uri编码后存入ViewModel,同时初始化ViewModel并传递给导航组件:
class MainActivity : ComponentActivity() { private lateinit var viewModel: MainViewModel @ExperimentalPermissionsApi @ExperimentalCoroutinesApi @ExperimentalCoilApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(this)[MainViewModel::class.java] setContent { val navController = rememberNavController() AppNavigation(navController = navController, viewModel = viewModel) } } }
3. 重构主页面的相册选择与导航逻辑
将相册选择器移到Composable内部(更符合Jetpack Compose最佳实践),并监听ViewModel的Uri状态触发导航:
fun firstScreen(navController: NavHostController, viewModel: MainViewModel) { // 监听选中的Uri状态 val selectedUri by viewModel.selectedImageUri.collectAsStateWithLifecycle() // 当Uri不为空时导航到SecondScreen LaunchedEffect(selectedUri) { selectedUri?.let { navController.navigate(AppScreens.SecondScreenImage.route + "/$it") // 导航后重置状态,避免重复触发导航 viewModel.setSelectedImageUri("") } } // 相册按钮区域代码 Column { Box( Modifier .weight(1f) .clickable { // 在Compose中创建相册选择器 val pickMediaLauncher = rememberLauncherForActivityResult( ActivityResultContracts.PickVisualMedia() ) { uri -> uri?.let { // 编码Uri,处理特殊字符避免导航参数错误 val encodedUri = Uri.encode(it.toString().replace('%', '|')) viewModel.setSelectedImageUri(encodedUri) } } pickMediaLauncher.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly)) }, contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { val gallery = painterResource(id = R.drawable.icons8_im_genes_de_carpetas_32__1_) Image( painter = gallery, contentDescription = "打开相册", modifier = Modifier.size(36.dp, 36.dp) ) Text(text = "Imágenes", fontFamily = Inter, fontSize = 12.sp) } } } }
4. 修正SecondScreen的MLKit调用逻辑
不能在Composable根作用域直接执行MLKit识别操作,需用LaunchedEffect包裹,确保只在Uri变化时执行一次:
@Composable fun secondScreen(navController: NavHostController, textUri: String?) { val scaffoldState = rememberScaffoldState() val context = LocalContext.current val textChanged = remember { mutableStateOf("") } val recognizer = remember { TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) } // 解码Uri,恢复原始格式 val imageUri = textUri?.let { val decodedUri = Uri.decode(it.replace('|', '%')) Uri.parse(decodedUri) } // 执行MLKit文字识别 LaunchedEffect(imageUri) { imageUri?.let { uri -> try { val image = InputImage.fromFilePath(context, uri) val result = recognizer.process(image).await() textChanged.value = result.text } catch (e: Exception) { Log.e("TEXT_REC", e.message.toString()) } } } // 页面UI展示 Scaffold(scaffoldState = scaffoldState) { padding -> Column(modifier = Modifier.padding(padding)) { imageUri?.let { AsyncImage( model = it, contentDescription = "选中的图片", modifier = Modifier.fillMaxWidth().height(200.dp) ) } Text(text = textChanged.value, modifier = Modifier.padding(16.dp)) } } }
5. 确认导航逻辑有效性
你的原有导航配置已正确设置SecondScreenImage的路由参数,保持不变即可:
fun AppNavigation(navController: NavHostController, viewModel: MainViewModel) { NavHost(navController = navController, startDestination = AppScreens.SplashScreen.route) { composable(route = AppScreens.SplashScreen.route) { splashScreen(navController) } composable(route = AppScreens.FirstScreen.route) { firstScreen(navController, viewModel) } composable(route = AppScreens.Camera.route) { CameraCapture(navController = navController) } composable( route = AppScreens.SecondScreenImage.route + "/{textUri}", arguments = listOf(navArgument("textUri") { type = NavType.StringType }) ) { backStackEntry -> secondScreen( navController = navController, textUri = backStackEntry.arguments?.getString("textUri") ) } composable( route = AppScreens.SecondScreenText.route + "/{text}", arguments = listOf(navArgument("text") { type = NavType.StringType }) ) { backStackEntry -> secondScreenText( navController = navController, text = backStackEntry.arguments?.getString("text") ) } } }
关键说明
- 状态共享:通过ViewModel的StateFlow实现跨组件的状态同步,避免局部变量无法访问的问题。
- Uri编码解码:处理Uri中的特殊字符,确保导航参数传递时不会出现解析错误。
- 生命周期安全:MLKit操作放在
LaunchedEffect中,确保符合Compose的重组机制,避免重复执行。 - 架构优化:将相册选择器移到Composable内部,避免跨层传递Launcher对象,代码更内聚。
内容的提问来源于stack exchange,提问作者Eduardo
相关产品推荐
相关产品推荐

