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

ViewCompositionStrategyComposeView

默认情况下,只要视图与窗口分离,Compose 就会处理组合。Compose 界面 View 类型(例如 ComposeViewAbstractComposeView)使用定义此行为的 ViewCompositionStrategy

默认情况下,Compose 使用 DisposeOnDetachedFromWindowOrReleasedFromPool 策略。但是,在 Compose 界面 View 类型用于以下各项的部分情况下,您可能并不希望使用此默认值

  • Fragment。组合必须遵循 fragment 的视图生命周期,Compose 界面 View 类型才能保存状态。

  • 转换。每当在转换过程中使用 Compose 界面 View 时,系统都会在转换开始(而不是转换结束)时将其与窗口分离,从而导致您的可组合项在它仍然在屏幕上时处理其状态。

  • 您自己的由生命周期管理的自定义 View

在上述某些情况下,除非您手动调用 AbstractComposeView.disposeComposition,否则应用还可能会因为组合实例缓慢泄漏内存。

如需在不再需要组合时自动处理组合,请通过调用 setViewCompositionStrategy 方法设置其他策略或创建自己的策略。例如,DisposeOnLifecycleDestroyed 策略会在 lifecycle 被销毁时处理组合。此策略适用于与已知的 LifecycleOwner 具有一对一关系的 Compose 界面 View 类型。当 LifecycleOwner 未知时,您可以使用 DisposeOnViewTreeLifecycleDestroyed

如需了解如何使用此 API,请参阅 fragment 中的 ComposeView

Fragment 中的 ComposeView

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

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

<?xml version="1.0" encoding="utf-8"?>
<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/hello_world"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello Android!" />

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

后续步骤

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