在应用中采用 Compose 时,Compose 可以与基于视图的界面相结合。下面列出了一些可让您更轻松地过渡到 Compose 的 API、建议和提示。
Android View 中的 Compose
您可以将基于 Compose 的界面添加到采用基于视图的设计的现有应用中。
如需创建完全基于 Compose 的新屏幕,请让 activity 调用 setContent()
方法,并传递您想要使用的任何可组合函数。
class ExampleActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { // In here, we can call composables!
MaterialTheme {
Greeting(name = "compose")
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
这段代码看起来就和只含 Compose 元素的应用中的代码一样。
ComposeView 的 ViewCompositionStrategy
默认情况下,只要视图与窗口分离,Compose 就会处理组合。Compose 界面 View
类型(例如 ComposeView
和 AbstractComposeView
)使用定义此行为的 ViewCompositionStrategy
。
默认情况下,Compose 使用 DisposeOnDetachedFromWindow
策略。但是,在 Compose 界面 View
类型用于以下各项的部分情况下,您可能并不希望使用此默认值:
Fragment。组合必须遵循 fragment 的视图生命周期,Compose 界面
View
类型才能保存状态。转换。每当在转换过程中使用 Compose 界面
View
时,系统都会在转换开始(而不是转换结束)时将其与窗口分离,从而导致您的可组合项在它仍然在屏幕上时处理其状态。RecyclerView
视图持有者,或您自己的由生命周期管理的自定义View
。
在上述某些情况下,除非您手动调用 AbstractComposeView.disposeComposition
,否则应用还可能会因为组合实例缓慢泄漏内存。
如需在不再需要组合时自动处理组合,请通过调用 setViewCompositionStrategy
方法设置其他策略或创建自己的策略。例如,DisposeOnLifecycleDestroyed
策略会在 lifecycle
被销毁时处理组合。此策略适用于与已知的 LifecycleOwner
具有一对一关系的 Compose 界面 View
类型。当 LifecycleOwner
未知时,可以使用 DisposeOnViewTreeLifecycleDestroyed
。
如需了解如何使用此 API,请参阅“Fragment 中的 ComposeView”部分。
Fragment 中的 ComposeView
如果您要将 Compose 界面内容并入 fragment 或现有视图布局,请使用 ComposeView
并调用其 setContent()
方法。ComposeView
是一个 Android View
。
您可以将 ComposeView
放在 XML 布局中,就像放置其他任何 View
一样:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/hello_world"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Android!" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
在 Kotlin 源代码中,通过 XML 中定义的布局资源使布局膨胀。然后,使用 XML ID 获取 ComposeView
,设置最适合宿主 View
的组合策略,并调用 setContent()
以使用 Compose。
class ExampleFragment : Fragment() {
private var _binding: FragmentExampleBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentExampleBinding.inflate(inflater, container, false)
val view = binding.root
binding.composeView.apply {
// Dispose of the Composition when the view's LifecycleOwner
// is destroyed
setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
setContent {
// In Compose world
MaterialTheme {
Text("Hello Compose!")
}
}
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
图 1. 此图显示了在 View 界面层次结构中添加 Compose 元素的代码的输出。“Hello Android!”文本由 TextView
微件显示。“Hello Compose!”文本由 Compose 文本元素显示。
如果整个屏幕是使用 Compose 构建的,您还可以直接在 fragment 中添加 ComposeView
,这样可让您完全避免使用 XML 布局文件。
class ExampleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
// Dispose of the Composition when the view's LifecycleOwner
// is destroyed
setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme {
// In Compose world
Text("Hello Compose!")
}
}
}
}
}
如果同一布局中存在多个 ComposeView
元素,每个元素必须具有唯一的 ID 才能使 savedInstanceState
发挥作用。
class ExampleFragment : Fragment() {
override fun onCreateView(...): View = LinearLayout(...).apply {
addView(ComposeView(...).apply {
id = R.id.compose_view_x
...
})
addView(TextView(...))
addView(ComposeView(...).apply {
id = R.id.compose_view_y
...
})
}
}
}
ComposeView
ID 在 res/values/ids.xml
文件中进行定义:
<resources>
<item name="compose_view_x" type="id" />
<item name="compose_view_y" type="id" />
</resources>
Compose 中的 Android View
您可以在 Compose 界面中添加 Android View 层次结构。如果您要使用 Compose 中尚未提供的界面元素(如 AdView
),此方法特别有用。此方法还可让您重复使用您可能已设计的自定义视图。
如需添加视图元素或层次结构,请使用 AndroidView
可组合项。系统会向 AndroidView
传递一个返回 View
的 lambda。AndroidView
还提供了在视图膨胀时被调用的 update
回调。每当在该回调中读取的 State
发生变化时,AndroidView
都会重组。
@Composable
fun CustomView() {
val selectedItem = remember { mutableStateOf(0) }
// Adds view to Compose
AndroidView(
modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
factory = { context ->
// Creates custom view
CustomView(context).apply {
// Sets up listeners for View -> Compose communication
myView.setOnClickListener {
selectedItem.value = 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.coordinator.selectedItem = selectedItem.value
}
)
}
@Composable
fun ContentExample() {
Column(Modifier.fillMaxSize()) {
Text("Look at this CustomView!")
CustomView()
}
}
如需嵌入 XML 布局,请使用 androidx.compose.ui:ui-viewbinding
库提供的 AndroidViewBinding
API。为此,您的项目必须启用视图绑定。
与许多其他内置可组合项一样,AndroidView
接受 Modifier
参数,该参数可用于设置它在父级可组合项中的位置等用途。
@Composable
fun AndroidViewBindingExample() {
AndroidViewBinding(ExampleLayoutBinding::inflate) {
exampleView.setBackgroundColor(Color.GRAY)
}
}
从 Compose 调用 Android 框架
Compose 与 Android 框架类密切相关。例如,Compose 托管在 Android View 类(如 Activity
或 Fragment
)上,并且可能需要利用 Android 框架类(例如 Context
、系统资源、Service
或 BroadcastReceiver
)。
如需详细了解系统资源,请参阅 Compose 中的资源文档。
CompositionLocal
CompositionLocal
类允许通过可组合函数隐式传递数据。它们通常在界面树的某个节点具有一个值。该值可供其可组合项的后代使用,而无需在可组合函数中将 CompositionLocal
声明为参数。
CompositionLocal
用于为 Compose 中的 Android 框架类型(例如 Context
、Configuration
或 View
)传递值,其中 Compose 代码与相应的 LocalContext
、LocalConfiguration
或 LocalView
一起托管。请注意,CompositionLocal
类的前缀是 Local
,以便于 IDE 中的自动补全功能检测到这些类。
使用 CompositionLocal
的 current
属性访问前者当前值。例如,以下代码通过调用 LocalContext.current
使用 Compose 界面树相应部分中的 Context
创建自定义视图。
@Composable
fun rememberCustomView(): CustomView {
val context = LocalContext.current
return remember { CustomView(context).apply { /*...*/ } }
}
如需查看更完整的示例,请参阅本文档末尾的案例研究:BroadcastReceiver 部分。
其他交互
如果没有为您需要的交互定义实用程序,最佳实践是遵循常规 Compose 准则,即数据向下流动而事件向上流动(Compose 编程思想一文对此进行了更为详细的说明)。例如,以下可组合函数会启动一个不同的 activity:
class ExampleActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// get data from savedInstanceState
setContent {
MaterialTheme {
ExampleComposable(data, onButtonClick = {
startActivity(/*...*/)
})
}
}
}
}
@Composable
fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
Button(onClick = onButtonClick) {
Text(data.title)
}
}
案例研究:BroadcastReceiver
举一个更实际的例子,您可能想要迁移一些功能或在 Compose 中实现一些功能,以及展示 CompositionLocal
和附带效应,在这种情况下,需要通过可组合函数注册 BroadcastReceiver
。
该解决方案利用 LocalContext
来使用当前上下文,以及展示 rememberUpdatedState
和 DisposableEffect
附带效应。
@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 */
}