Compose 和其他库

您可以在 Compose 中使用自己喜欢的库。本部分介绍了如何纳入一些最有用的库。

activity

如要在某个 activity 中使用 Compose,您必须使用 ComponentActivity,它是 Activity 的子类,可提供 Compose 所需的 LifecycleOwner 和组件。它还提供了可将代码与 activity 类中的替换方法分离开的附加 API。Activity Compose 会向可组合项公开这些 API,因此无需再替换可组合项之外的方法或检索显式 Activity 实例。此外,这些 API 可确保只需初始化一次,在重组后仍有效,且可在从组合中移除可组合项后妥善清理。

activity 结果

借助 rememberLauncherForActivityResult() API,您可以获取可组合项中某个 activity 的结果

@Composable
fun GetContentExample() {
    var imageUri by remember { mutableStateOf<Uri?>(null) }
    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
        imageUri = uri
    }
    Column {
        Button(onClick = { launcher.launch("image/*") }) {
            Text(text = "Load Image")
        }
        Image(
            painter = rememberAsyncImagePainter(imageUri),
            contentDescription = "My Image"
        )
    }
}

上面的示例演示了一个简单的 GetContent() 协定。点按相应按钮可启动请求。用户选择图片并返回正在启动的 activity 后,系统会调用 rememberLauncherForActivityResult() 的尾随 lambda,然后使用 Coil 的 rememberImagePainter() 函数加载所选图片。

ActivityResultContract 的任何子类都可用作 rememberLauncherForActivityResult() 的第一个参数。这意味着,您可以使用该方法从框架或在其他常见模式下请求内容。您还可以创建自己的自定义协定,并将其与该方法结合使用。

请求运行时权限

您可以使用上文所述的 Activity Result API 和 rememberLauncherForActivityResult() 请求运行时权限,如果是单项权限,则使用 RequestPermission 协定,如果是多项权限,则使用 RequestMultiplePermissions 协定。

Accompanist 权限库也可用于这些 API 之上的一层,用于将权限的当前授予状态映射到 Compose 界面可以使用的状态。

处理系统返回按钮

提供自定义返回导航并替换可组合项中系统返回按钮的默认行为,可组合项可以使用 BackHandler 来拦截相应事件:

var backHandlingEnabled by remember { mutableStateOf(true) }
BackHandler(backHandlingEnabled) {
    // Handle back press
}

第一个参数用于控制 BackHandler 是否处于启用状态,您可以根据组件的状态通过该参数暂时停用处理程序。如果用户触发系统返回事件,且 BackHandler 处于启用状态,系统将调用尾随 lambda。

ViewModel

如果您使用 Architecture Components ViewModel 库,可以通过调用 viewModel() 函数,从任何可组合项访问 ViewModel。将以下依赖项添加到 Gradle 文件中:

Groovy

dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5'
}

Kotlin

dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5")
}

然后,您可以在代码中使用 viewModel() 函数。

class MyViewModel : ViewModel() { /*...*/ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    // use viewModel here
}

viewModel() 会返回一个现有的 ViewModel,或创建一个新的 ViewModel。默认情况下,返回的 ViewModel 的作用域限定为封装的 activity、fragment 或导航目的地,只要作用域处于活动状态,该作用域就会一直保留。

例如,如果在某个 activity 中使用了可组合项,则在该 activity 完成或进程终止之前,viewModel() 会返回同一实例。

class MyViewModel : ViewModel() { /*...*/ }
// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    // Returns the same instance as long as the activity is alive,
    // just as if you grabbed the instance from an Activity or Fragment
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

@Composable
fun MyScreen2(
    viewModel: MyViewModel = viewModel() // Same instance as in MyScreen
) { /* ... */ }

使用指南

通常,您可以在屏幕级可组合项中访问 ViewModel 实例,也就是接近于从导航图的 activity、fragment 或目的地调用的根可组合项。这是因为 ViewModel 默认会限定为这些屏幕级对象。如需详细了解 ViewModel生命周期和作用域,请点击此处。

请尽量避免将 ViewModel 实例传递给其他可组合项,因为这可能会使这些可组合项更难测试,并且可能会破坏预览。请仅传递它们需要的数据和函数作为参数。

可以使用 ViewModel 实例来管理子屏幕级可组合项的状态,但请注意 ViewModel生命周期和作用域。如果可组合项是自包含的,您可以考虑使用 Hilt 注入 ViewModel,以免必须从父级可组合项传递依赖项。

如果 ViewModel 具有依赖项,则 viewModel() 会将可选的 ViewModelProvider.Factory 作为参数。

如需详细了解 Compose 中的 ViewModel 以及实例如何与 Navigation Compose 库或 activity 和 fragment 一起使用,请参阅互操作性文档

数据流

Compose 随附了一些扩展程序,它们适用于最热门的基于流的 Android 解决方案。其中每个扩展程序都由不同的工件提供:

这些工件注册为监听器,并将值表示为 State。每当发出一个新值时,Compose 都会重组界面中使用该 state.value 的部分。例如,在以下代码中,每当 exampleLiveData 发出一个新值时,ShowData 都会重组。

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val dataExample = viewModel.exampleLiveData.observeAsState()

    // Because the state is read here,
    // MyScreen recomposes whenever dataExample changes.
    dataExample.value?.let {
        ShowData(dataExample)
    }
}

Compose 中的异步操作

借助 Jetpack Compose,您可以从可组合项中使用协程执行异步操作。

如需了解详情,请参阅附带效应文档中的 LaunchedEffectproduceStaterememberCoroutineScope API。

Navigation 组件支持 Jetpack Compose 应用。如需了解详情,请参阅使用 Compose 进行导航将 Jetpack Navigation 迁移到 Navigation Compose

Hilt

建议使用 Hilt 解决方案在 Android 应用中实现依赖项注入,并且 Hilt 能与 Compose 无缝协作。

ViewModel 部分提及的 viewModel() 函数自动使用 Hilt 通过 @HiltViewModel 注解构造的 ViewModel。我们在文档中提供了有关 Hilt 的 ViewModel 集成的信息。

@HiltViewModel
class MyViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    private val repository: ExampleRepository
) : ViewModel() { /* ... */ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

Hilt 和 Navigation

Hilt 还与 Navigation Compose 库集成。请将下面这些额外的依赖项添加到 Gradle 文件中:

Groovy

dependencies {
    implementation 'androidx.hilt:hilt-navigation-compose:1.2.0'
}

Kotlin

dependencies {
    implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
}

使用 Navigation Compose 时,请始终使用 hiltViewModel 可组合函数获取带有 @HiltViewModel 注解的 ViewModel 的实例。该函数可与带有 @AndroidEntryPoint 注解的 fragment 或 activity 搭配使用。

例如,如果 ExampleScreen 是导航图中的目的地,请调用 hiltViewModel() 来获取作用域限定为该目的地的 ExampleViewModel 实例,如以下代码段所示:

// import androidx.hilt.navigation.compose.hiltViewModel

@Composable
fun MyApp() {
    val navController = rememberNavController()
    val startRoute = "example"
    NavHost(navController, startDestination = startRoute) {
        composable("example") { backStackEntry ->
            // Creates a ViewModel from the current BackStackEntry
            // Available in the androidx.hilt:hilt-navigation-compose artifact
            val viewModel = hiltViewModel<MyViewModel>()
            MyScreen(viewModel)
        }
        /* ... */
    }
}

如果您需要改为检索作用域限定为导航路线导航图ViewModel 实例,请使用 hiltViewModel 可组合函数并将相应的 backStackEntry 作为参数传递:

// import androidx.hilt.navigation.compose.hiltViewModel
// import androidx.navigation.compose.getBackStackEntry

@Composable
fun MyApp() {
    val navController = rememberNavController()
    val startRoute = "example"
    val innerStartRoute = "exampleWithRoute"
    NavHost(navController, startDestination = startRoute) {
        navigation(startDestination = innerStartRoute, route = "Parent") {
            // ...
            composable("exampleWithRoute") { backStackEntry ->
                val parentEntry = remember(backStackEntry) {
                    navController.getBackStackEntry("Parent")
                }
                val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
                ExampleWithRouteScreen(parentViewModel)
            }
        }
    }
}

Paging

使用 Paging 库,您可以更加轻松地逐步加载数据,且该库在 Compose 中也受支持。Paging 版本页面包含有关需要添加到项目及其版本的额外 paging-compose 依赖项的信息。

下面是一个关于 Paging 库的 Compose API 的示例:

@Composable
fun MyScreen(flow: Flow<PagingData<String>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it }
        ) { index ->
            val item = lazyPagingItems[index]
            Text("Item is $item")
        }
    }
}

如需详细了解如何在 Compose 中使用 Paging,请参阅列表和网格文档

Maps

您可以使用 Maps Compose 库在应用中提供 Google 地图,用法示例如下:

@Composable
fun MapsExample() {
    val singapore = LatLng(1.35, 103.87)
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(singapore, 10f)
    }
    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState
    ) {
        Marker(
            state = remember { MarkerState(position = singapore) },
            title = "Singapore",
            snippet = "Marker in Singapore"
        )
    }
}