在 Compose 中使用 View

您可以在 Compose 界面中包含 Android View 层次结构。如果您想使用 Compose 中尚不可用的界面元素(例如 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 布局,请使用 androidx.compose.ui:ui-viewbinding 库提供的 AndroidViewBinding API。为此,您的项目必须启用视图绑定

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

懒加载列表中的 AndroidView

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

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 中的自动完成功能更好地发现。

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

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

有关更完整的示例,请参阅本文档末尾的案例研究:BroadcastReceivers 部分。

其他交互

如果您所需的交互没有定义实用程序,最佳实践是遵循通用 Compose 指南,“数据流向下,事件流向上”(在Compose 思维模式中有更详细的讨论)。例如,此可组合项会启动不同的 Activity

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 或在 Compose 中使用 View 时的互操作性 API,请浏览其他注意事项页面以了解更多信息。