在 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 所取代。

互操作场景:

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

互操作场景:

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

互操作场景

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

互操作场景:

* Fragment 的 View 中的 ComposeView
* ComposeView(在 Lifecycle 未知的 View 中)。

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

或者,您也可以使用视图绑定,通过引用为 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
    }
}

两个略有不同的文本元素,一个在另一个之上

图 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 工具属性,并将其值设置为要在布局中预览的可组合项的完全限定名称。

<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