在应用中采用 Compose 时,Compose 可以与基于 View 的界面相结合。下面列出了一些可让您更轻松地过渡到 Compose 的 API、建议和提示。
View 中的 Compose
您可以将基于 Compose 的界面添加到采用基于 View 的设计的现有应用中。
如需创建完全基于 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 使用 DisposeOnDetachedFromWindowOrReleasedFromPool
策略。但是,在 Compose 界面 View
类型用于以下各项的部分情况下,您可能并不希望使用此默认值:
Fragment。组合必须遵循 fragment 的视图生命周期,Compose 界面
View
类型才能保存状态。转换。每当在转换过程中使用 Compose 界面
View
时,系统都会在转换开始(而不是转换结束)时将其与窗口分离,从而导致您的可组合项在它仍然在屏幕上时处理其状态。您自己的由生命周期管理的自定义
View
。
在上述某些情况下,除非您手动调用 AbstractComposeView.disposeComposition
,否则应用还可能会因为组合实例缓慢泄漏内存。
如需在不再需要组合时自动处理组合,请通过调用 setViewCompositionStrategy
方法设置其他策略或创建自己的策略。例如,DisposeOnLifecycleDestroyed
策略会在 lifecycle
被销毁时处理组合。此策略适用于与已知的 LifecycleOwner
具有一对一关系的 Compose 界面 View
类型。当 LifecycleOwner
未知时,可以使用 DisposeOnViewTreeLifecycleDestroyed
。
如需了解如何使用此 API,请参阅 fragment 中的 ComposeView。
fragment 中的 ComposeView
如果您要将 Compose 界面内容并入 fragment 或现有 View 布局,请使用 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(ViewCompositionStrategy.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 ExampleFragmentNoXml : 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(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { MaterialTheme { // In Compose world Text("Hello Compose!") } } } } }
同一布局中的多个 ComposeView
如果同一布局中存在多个 ComposeView
元素,每个元素必须具有唯一的 ID 才能使 savedInstanceState
发挥作用。
class ExampleFragmentMultipleComposeView : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = LinearLayout(requireContext()).apply { addView( ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) id = R.id.compose_view_x // ... } ) addView(TextView(requireContext())) addView( ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) 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 中的 View
您可以在 Compose 界面中添加 Android View 层次结构。如果您要使用 Compose 中尚未提供的界面元素(如 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() } }
如需嵌入 XML 布局,请使用 androidx.compose.ui:ui-viewbinding
库提供的 AndroidViewBinding
API。为此,您的项目必须启用视图绑定。
@Composable fun AndroidViewBindingExample() { AndroidViewBinding(ExampleLayoutBinding::inflate) { exampleView.setBackgroundColor(Color.GRAY) } }
Compose 中的 fragment
使用 AndroidViewBinding
可组合项在 Compose 中添加 Fragment
。AndroidViewBinding
包含特定于 fragment 的处理方式,例如当可组合项退出组合时移除 fragment。
为此,请将包含 FragmentContainerView
作为您 Fragment
容器的 XML 膨胀。
例如,如果您已定义 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_height="match_parent"
android:layout_width="match_parent"
android:name="com.example.MyFragment" />
在 Compose 中膨胀此 fragment,如下所示:
@Composable fun FragmentInComposeExample() { AndroidViewBinding(MyFragmentLayoutBinding::inflate) { val myFragment = fragmentContainerView.getFragment<MyFragment>() // ... } }
如果您需要在同一布局中使用多个 fragment,请确保您已为每个 FragmentContainerView
定义唯一 ID。
从 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
属性进行访问。例如,以下代码通过向 Toast.makeToast
方法提供 LocalContext.current
来显示消息框消息。
@Composable fun ToastGreetingButton(greeting: String) { val context = LocalContext.current Button(onClick = { Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show() }) { Text("Greet") } }
如需查看更完整的示例,请参阅本文档末尾的案例研究:BroadcastReceiver 部分。
其他交互
如果没有为您需要的交互定义实用程序,最佳实践是遵循常规 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) } }
案例研究: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 */ }