Compose 和其他库

您可以在 Compose 中使用您喜欢的库。本节介绍如何集成一些最常用的库。

Activity

要在 Activity 中使用 Compose,您必须使用 ComponentActivity,它是 Activity 的子类,为 Compose 提供了适当的 LifecycleOwner 和组件。它还提供了额外的 API,可将您的代码与 Activity 类中重写方法解耦。Activity Compose 将这些 API 暴露给可组合项,因此不再需要在可组合项外部重写方法或检索显式的 Activity 实例。此外,这些 API 确保它们只初始化一次,在重组后仍能保留,并且在可组合项从组合中移除时能正确清理。

Activity Result

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 Permissions 库也可以在这些 API 之上用作一个层,将权限的当前授予状态映射到您的 Compose 界面可以使用的 State 中。

处理系统返回按钮

提供自定义返回导航并从可组合项内部覆盖系统返回按钮的默认行为,您的可组合项可以使用 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 的作用域限定为封闭的 Activity、Fragment 或导航目标,并在作用域存活期间保留。

例如,如果可组合项在 Activity 中使用,则 viewModel() 返回相同的实例,直到 Activity 完成或进程被终止。

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 或 Navigation 图形的目标调用的根可组合项。这是因为 ViewModel 默认情况下作用域限定为这些屏幕级对象。在此处阅读有关 ViewModel生命周期和作用域的更多信息。

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

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

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

有关 Compose 中 ViewModel 以及实例如何与 Navigation Compose 库或 Activity 和 Fragment 配合使用的更多信息,请参阅互操作性文档

数据流

Compose 附带了 Android 最流行的基于流的解决方案的扩展。每个扩展都由不同的 artifact 提供

这些 artifact 注册为监听器,并将值表示为 State。每当发出新值时,Compose 就会重新组合 UI 中使用 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 的更多信息,请参阅附带效应文档

导航组件为 Jetpack Compose 应用提供支持。有关更多信息,请参阅使用 Compose 进行导航将 Jetpack Navigation 迁移到 Navigation Compose

Hilt

Hilt 是 Android 应用中依赖注入的推荐解决方案,并且与 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)
            }
        }
    }
}

分页

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

以下是分页库 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 中使用分页的更多信息,请查看列表和网格文档

地图

您可以使用 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"
        )
    }
}