在 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 元素的应用中的代码一样。

ComposeViewViewCompositionStrategy

ViewCompositionStrategy 用于定义应何时处置组合。默认值 ViewCompositionStrategy.Default 会在底层 ComposeView 与窗口分离时处理组合,除非它是池容器(例如 RecyclerView)的一部分。在仅使用 Compose 的单 activity 应用中,您需要这种默认行为,但如果您要在代码库中逐步添加 Compose,这种行为在某些情况下可能会导致状态丢失。

如需更改 ViewCompositionStrategy,请调用 setViewCompositionStrategy() 方法并提供其他策略。

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

ViewCompositionStrategy 说明和互操作性场景
DisposeOnDetachedFromWindow 当底层 ComposeView 与窗口分离时,系统会处置组合。已被 DisposeOnDetachedFromWindowOrReleasedFromPool 取代。

Interop 场景:

* ComposeView无论是在 View 层次结构中的唯一元素,还是在混合 View/Compose 屏幕(而非 Fragment)的上下文中。
DisposeOnDetachedFromWindowOrReleasedFromPool默认 DisposeOnDetachedFromWindow 类似,当组合不在池化容器(例如 RecyclerView)中时。如果它位于池化容器中,则会在池化容器本身从窗口分离时或项被舍弃时(即池已满时)进行处置。

Interop 场景:

* ComposeView无论它是 View 层次结构中的唯一元素,还是在混合 View/Compose 屏幕(而非 Fragment)的上下文中。
* ComposeView 作为池化容器(例如 RecyclerView)中的项。
DisposeOnLifecycleDestroyed 提供的 Lifecycle 被销毁时,系统会处理组合。

Interop 场景

* Fragment 视图中的 ComposeView
DisposeOnViewTreeLifecycleDestroyed 当视图附加到的下一个窗口的 ViewTreeLifecycleOwner.get 返回的 LifecycleOwner 拥有的 Lifecycle 被销毁时,系统会处置组合。

Interop 场景:

* Fragment 的 View 中的 ComposeView
* 在生命周期尚未知晓的 View 中使用 ComposeView

Fragment 中的 ComposeView

如果您要将 Compose 界面内容并入 fragment 或现有 View 布局,请使用 ComposeView 并调用其 setContent() 方法。ComposeView 是一个 Android View

您可以将 ComposeView 放在 XML 布局中,就像放置其他任何 View 一样:

<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 的组合策略,并调用 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
    }
}

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

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>

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

您还可以在布局编辑器中预览包含 ComposeView 的 XML 布局的可组合项。这样,您就可以查看可组合项在混合 View 和 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>

布局编辑器中显示的可组合项

后续步骤

现在,您已经了解了在 View 中使用 Compose 的互操作性 API,接下来不妨了解如何在 Compose 中使用 View