在视图中使用 Compose

您可以将基于 Compose 的 UI 添加到使用基于 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 的应用中找到的一样。

ViewCompositionStrategy 用于 ComposeView

ViewCompositionStrategy 定义了 Composition 何时应被释放。默认值,ViewCompositionStrategy.Default,会在底层 ComposeView 从窗口分离时释放 Composition,除非它是池化容器(例如 RecyclerView)的一部分。在仅使用 Compose 的单 Activity 应用中,这种默认行为是您想要的,但是,如果您正在代码库中逐步添加 Compose,这种行为可能会在某些情况下导致状态丢失。

要更改 ViewCompositionStrategy,请调用 setViewCompositionStrategy() 方法并提供不同的策略。

下表总结了您可以使用 ViewCompositionStrategy 的不同场景

ViewCompositionStrategy 描述和互操作场景
DisposeOnDetachedFromWindow 当底层 ComposeView 与窗口分离时,Composition 将被释放。此策略已被 DisposeOnDetachedFromWindowOrReleasedFromPool 取代。

互操作场景

* ComposeView 无论它是 View 层次结构中的唯一元素,还是混合 View/Compose 屏幕(不在 Fragment 中)的上下文。
DisposeOnDetachedFromWindowOrReleasedFromPool (**默认**) DisposeOnDetachedFromWindow 类似,当 Composition 不在池化容器(例如 RecyclerView)中时。如果它在池化容器中,它将在池化容器本身与窗口分离时或项目被丢弃时(即池已满时)释放。

互操作场景

* ComposeView 无论它是 View 层次结构中的唯一元素,还是混合 View/Compose 屏幕(不在 Fragment 中)的上下文。
* ComposeView 作为池化容器(例如 RecyclerView)中的一个项目。
DisposeOnLifecycleDestroyed 当提供的 Lifecycle 被销毁时,Composition 将被释放。

互操作场景

* ComposeView 在 Fragment 的 View 中。
DisposeOnViewTreeLifecycleDestroyed 当由 ViewTreeLifecycleOwner.get 返回的 LifecycleOwner 拥有的 Lifecycle(View 附加到的下一个窗口)被销毁时,Composition 将被释放。

互操作场景

* ComposeView 在 Fragment 的 View 中。
* ComposeView 在生命周期尚未知道的 View 中。

ComposeView 在 Fragment 中

如果您想在 Fragment 或现有的 View 布局中包含 Compose UI 内容,请使用 ComposeView 并调用其 setContent() 方法。ComposeView 是一个 Android View

您可以像其他任何 View 一样将 ComposeView 放入您的 XML 布局中。

<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/text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />

  <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 的 Composition 策略,并调用 setContent() 来使用 Compose。

class ExampleFragmentXml : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val view = inflater.inflate(R.layout.fragment_example, container, false)
        val composeView = view.findViewById<ComposeView>(R.id.compose_view)
        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
    }
}

或者,您也可以使用视图绑定通过引用 XML 布局文件的生成的绑定类来获取对 ComposeView 的引用。

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
    }
}

Two slightly different text elements, one above the other

图 1. 这显示了在 View UI 层次结构中添加 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>

在布局编辑器中预览可组合项

您还可以在包含 ComposeView 的 XML 布局的布局编辑器中预览可组合项。这样做可以让您了解可组合项在混合 Views 和 Compose 布局中的外观。

假设您想在布局编辑器中显示以下可组合项。请注意,用 @Preview 注释的可组合项是在布局编辑器中预览的良好候选者。

@Preview
@Composable
fun GreetingPreview() {
    Greeting(name = "Android")
}

要显示此可组合项,请使用 tools:composableName tools 属性并将它的值设置为要预览的布局中可组合项的全限定名称。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <androidx.compose.ui.platform.ComposeView
      android:id="@+id/my_compose_view"
      tools:composableName="com.example.compose.snippets.interop.InteroperabilityAPIsSnippetsKt.GreetingPreview"
      android:layout_height="match_parent"
      android:layout_width="match_parent"/>

</LinearLayout>

Composable displayed within layout editor

后续步骤

既然您已经了解了在 Views 中使用 Compose 的互操作性 API,请学习如何使用 Views in Compose