在 Compose 中使用视图

您可以在 Compose UI 中包含 Android View 层次结构。如果您想使用 Compose 中尚不可用的 UI 元素(例如 AdView),此方法特别有用。此方法还允许您重用可能已设计的自定义视图。

要包含视图元素或层次结构,请使用 AndroidView 可组合项。 AndroidView 被传递一个返回 View 的 lambda 表达式。 AndroidView 还提供了一个 update 回调函数,该函数在视图膨胀时被调用。 AndroidView 每当回调函数中读取的 State 发生变化时都会重新组合。 AndroidView 与许多其他内置的可组合项一样,接受一个 Modifier 参数,该参数可以用于例如设置其在父级可组合项中的位置。

@Composable
fun CustomView() {
    var selectedItem by remember { mutableStateOf(0) }

    // Adds view to Compose
    AndroidView(
        modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
        factory = { context ->
            // Creates view
            MyView(context).apply {
                // Sets up listeners for View -> Compose communication
                setOnClickListener {
                    selectedItem = 1
                }
            }
        },
        update = { view ->
            // View's been inflated or state read in this block has been updated
            // Add logic here if necessary

            // As selectedItem is read here, AndroidView will recompose
            // whenever the state changes
            // Example of Compose -> View communication
            view.selectedItem = selectedItem
        }
    )
}

@Composable
fun ContentExample() {
    Column(Modifier.fillMaxSize()) {
        Text("Look at this CustomView!")
        CustomView()
    }
}

AndroidView 与视图绑定

要嵌入 XML 布局,请使用 AndroidViewBinding API,该 API 由 androidx.compose.ui:ui-viewbinding 库提供。要执行此操作,您的项目必须启用 视图绑定

@Composable
fun AndroidViewBindingExample() {
    AndroidViewBinding(ExampleLayoutBinding::inflate) {
        exampleView.setBackgroundColor(Color.GRAY)
    }
}

AndroidView 在 Lazy 列表中

如果您在 Lazy 列表(LazyColumnLazyRowPager 等)中使用 AndroidView,请考虑使用在版本 1.4.0-rc01 中引入的 AndroidView 重载。此重载允许 Compose 在包含的组合被重用时重用底层的 View 实例,就像 Lazy 列表一样。

AndroidView 重载添加了 2 个额外的参数

  • onReset - 一个回调函数,用于指示 View 即将被重用。为了启用 View 重用,它必须是非空的。
  • onRelease(可选) - 一个回调函数,用于指示 View 已退出组合并且不会再次被重用。

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun AndroidViewInLazyList() {
    LazyColumn {
        items(100) { index ->
            AndroidView(
                modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
                factory = { context ->
                    MyView(context)
                },
                update = { view ->
                    view.selectedItem = index
                },
                onReset = { view ->
                    view.clear()
                }
            )
        }
    }
}

Compose 中的片段

使用 AndroidViewBinding 可组合项在 Compose 中添加 FragmentAndroidViewBinding 具有特定于片段的处理,例如在可组合项离开组合时移除片段。

为此,请膨胀包含 FragmentContainerView 的 XML 作为您 Fragment 的容器。

例如,如果您已定义 my_fragment_layout.xml,则可以使用类似以下代码,并将 android:name XML 属性替换为您的 Fragment 的类名

<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.example.compose.snippets.interop.MyFragment" />

在 Compose 中膨胀此片段,如下所示

@Composable
fun FragmentInComposeExample() {
    AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
        val myFragment = fragmentContainerView.getFragment<MyFragment>()
        // ...
    }
}

如果您需要在同一个布局中使用多个片段,请确保为每个 FragmentContainerView 定义了唯一的 ID。

从 Compose 调用 Android 框架

Compose 在 Android 框架类中运行。例如,它托管在 Android View 类(例如 ActivityFragment)上,并且可能使用 Android 框架类,例如 Context、系统资源、ServiceBroadcastReceiver

要详细了解系统资源,请参阅 Compose 中的资源

组合局部变量

CompositionLocal 类允许通过可组合函数隐式传递数据。它们通常在 UI 树的特定节点中提供值。该值可以由其可组合的子节点使用,而无需将 CompositionLocal 声明为可组合函数的参数。

CompositionLocal 用于在 Compose 中传播 Android 框架类型的值,例如 ContextConfiguration 或托管 Compose 代码的 View,以及相应的 LocalContextLocalConfigurationLocalView。请注意,CompositionLocal 类以 Local 为前缀,以便在 IDE 中使用自动完成功能时更容易发现。

使用 CompositionLocalcurrent 属性访问其当前值。例如,以下代码通过将 LocalContext.current 提供给 Toast.makeToast 方法来显示一条提示消息。

@Composable
fun ToastGreetingButton(greeting: String) {
    val context = LocalContext.current
    Button(onClick = {
        Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
    }) {
        Text("Greet")
    }
}

有关更完整的示例,请查看本文档末尾的 案例研究:广播接收器 部分。

其他交互

如果没有为所需的交互定义实用程序,最佳做法是遵循通用的 Compose 指南,即“数据向下流动,事件向上流动”(在 Compose 思维 中进行了更详细的讨论)。例如,此可组合项启动另一个活动

class OtherInteractionsActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // get data from savedInstanceState
        setContent {
            MaterialTheme {
                ExampleComposable(data, onButtonClick = {
                    startActivity(Intent(this, MyActivity::class.java))
                })
            }
        }
    }
}

@Composable
fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
    Button(onClick = onButtonClick) {
        Text(data.title)
    }
}

案例研究:广播接收器

为了更切合实际地说明您可能想要迁移或在 Compose 中实现的功能,并展示 CompositionLocal副作用,假设需要从可组合函数中注册 BroadcastReceiver

该解决方案利用了 LocalContext 来使用当前上下文,以及 rememberUpdatedStateDisposableEffect 副作用。

@Composable
fun SystemBroadcastReceiver(
    systemAction: String,
    onSystemEvent: (intent: Intent?) -> Unit
) {
    // Grab the current context in this part of the UI tree
    val context = LocalContext.current

    // Safely use the latest onSystemEvent lambda passed to the function
    val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)

    // If either context or systemAction changes, unregister and register again
    DisposableEffect(context, systemAction) {
        val intentFilter = IntentFilter(systemAction)
        val broadcast = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                currentOnSystemEvent(intent)
            }
        }

        context.registerReceiver(broadcast, intentFilter)

        // When the effect leaves the Composition, remove the callback
        onDispose {
            context.unregisterReceiver(broadcast)
        }
    }
}

@Composable
fun HomeScreen() {

    SystemBroadcastReceiver(Intent.ACTION_BATTERY_CHANGED) { batteryStatus ->
        val isCharging = /* Get from batteryStatus ... */ true
        /* Do something if the device is charging */
    }

    /* Rest of the HomeScreen */
}

后续步骤

现在您已经了解了在 View 中使用 Compose 或反之亦然的互操作性 API,请浏览 其他注意事项 页面以了解更多信息。