在 View 中使用 Compose

你可以將Compose-based UI 加到採用view-based設計的既有App。

如要建立以 Compose 為基礎的新畫面,請讓活動呼叫 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-only App中會出現的寫法。

ComposeViewViewCompositionStrategy

ViewCompositionStrategy 會定義應處置 Composition 的時機。預設值 ViewCompositionStrategy.Default 會在基礎 ComposeView 從視窗卸離時丟棄 Composition,除非它屬於集區容器的一部分,例如 RecyclerView。在僅限單一活動 Compose 的應用程式中,這個預設行為符合您的需求;然而,如果您逐步在程式碼集中新增 Compose,這個行為可能會導致狀態遺失。

如要變更 ViewCompositionStrategy,請呼叫 setViewCompositionStrategy() 方法並提供其他策略。

下表摘要說明您可以在下列環境中使用 ViewCompositionStrategy 的不同情境:

ViewCompositionStrategy 說明和互通性情境
DisposeOnDetachedFromWindow 當基礎 ComposeView 從視窗卸離時,就會處理組合。之後已由 DisposeOnDetachedFromWindowOrReleasedFromPool 取代。

互通情境:

* ComposeView 可能是檢視區塊階層的唯一元素,或同時在混合的 View/Compose 畫面 (而非片段中) 中。
DisposeOnDetachedFromWindowOrReleasedFromPool (預設) DisposeOnDetachedFromWindow 類似,當組合不在集區容器中時 (例如 RecyclerView)。如果容器位於集區容器中,在集區容器本身卸離視窗或捨棄項目 (例如集區已滿時) 時,就會丟棄。

互通情況:

* ComposeView 是檢視區塊階層中的唯一元素,或是在混合的 View/Compose 畫面 (不在片段中) 中。
* ComposeView 以做為集區容器中的項目 (例如 RecyclerView)。
DisposeOnLifecycleDestroyed 提供的 Lifecycle 刪除時,組合就會遭到處理。

在 Fragment 檢視畫面中的互通情境

* ComposeView
DisposeOnViewTreeLifecycleDestroyed 當檢視畫面連接下一個視窗的下一個視窗,由 ViewTreeLifecycleOwner.get 傳回的 LifecycleOwner 所傳回的 Lifecycle 遭到刪除時,組成就會處理。

互通情況:

* ComposeView 在 Fragment 檢視畫面中。
* 在生命週期中還不知道的檢視畫面中 ComposeView

Fragment 中的 ComposeView

如要在片段或現有的 View 版面配置中加入 Compose UI 內容,請使用 ComposeView 並呼叫其 setContent() 方法。ComposeView 是 Android View

您可將 ComposeView 和其他的 View 一樣地放在 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
    }
}

兩個細微不同的文字元素,一個在另一個的上面

圖 1. 這個程式碼會顯示在 View UI 階層中新增 Compose 元素的程式碼輸出內容。「Hello Android!」文字會以 TextView 小工具顯示。「您好 Compose!」文字會顯示Compose 文字元素。

假如您使用 Compose 建立全螢幕畫面,可以直接在片段中加入 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
                // ...
            }
        )
    }
}

res/values/ids.xml 檔案已定義 ComposeView ID:

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

版面配置編輯器中顯示的可組合項

後續步驟

現在您已瞭解互通性 API 以在 View 中使用 Compose,接著請瞭解如何在 Compose 中使用 View