在 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)中时。如果它位于池化容器中,则会在池化容器本身从窗口分离时或项被舍弃时(即池已满时)进行处置。

Interop 场景:

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

Interop 场景

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

互操作场景:

* Fragment 的 View 中的 ComposeView
*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

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>

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

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