在视图中使用 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 当 View 附加到的下一个窗口的 ViewTreeLifecycleOwner.get 返回的 LifecycleOwner 拥有的 Lifecycle 被销毁时,Composition 将被释放。

互操作场景

* ComposeView 在 Fragment 的 View 中。
* ComposeView 在 View 中,其中 Lifecycle 尚未知道。

ComposeView 在 Fragment 中

如果要将 Compose UI 内容合并到 Fragment 或现有的 View 布局中,请使用 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 构建的,您也可以将 ComposeView 直接包含在 Fragment 中,这样您就可以完全避免使用 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>

Composable displayed within layout editor

后续步骤

现在您已经了解了在 View 中使用 Compose 的互操作性 API,请了解如何使用 Compose 中的 View