在 Compose 中使用 Views

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

要包含视图元素或层次结构,请使用 AndroidView 可组合项。 AndroidView 传递一个返回 View 的 lambda 表达式。 AndroidView 还提供了一个 update 回调,该回调在视图膨胀时调用。每当回调中读取的 State 发生更改时,AndroidView 都会重新组合。 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)
    }
}

在 Lazy 列表中使用 AndroidView

如果您在 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 中的 Fragment

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

通过膨胀包含 FragmentContainerView 作为 Fragment 容器的 XML 来实现。

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

<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 中膨胀此 Fragment,如下所示

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

如果您需要在同一个布局中使用多个 Fragment,请确保您为每个 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 方法来显示吐司消息。

@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 */
}

后续步骤

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