การใช้ Compose ใน View

คุณสามารถเพิ่ม UI ที่ใช้ Compose ลงในแอปที่มีอยู่ซึ่งใช้การออกแบบแบบ View

หากต้องการสร้างหน้าจอใหม่ที่ใช้ 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!")
}

โค้ดนี้มีลักษณะเหมือนกับที่คุณเห็นในแอปเขียนอีเมลเท่านั้น

ViewCompositionStrategy เป็นเวลา ComposeView

ViewCompositionStrategy กำหนดว่าควรกำจัดคอมโพสิชันเมื่อใด ค่าเริ่มต้น ViewCompositionStrategy.Default จะทิ้งคอมโพสิชันเมื่อคอมโพสิชันพื้นฐาน ComposeView แยกออกจากหน้าต่าง เว้นแต่ว่าจะเป็นส่วนหนึ่งของคอนเทนเนอร์การรวมกลุ่ม เช่น RecyclerView ในแอป Compose อย่างเดียวที่มีกิจกรรมเดียว ลักษณะการทำงานเริ่มต้นนี้เป็นสิ่งที่คุณต้องการ อย่างไรก็ตาม หากคุณเพิ่ม Compose ลงในโค้ดเบสทีละส่วน ลักษณะการทำงานนี้อาจทำให้ข้อมูลสถานะสูญหายในบางสถานการณ์

หากต้องการเปลี่ยน ViewCompositionStrategy ให้เรียกใช้เมธอด setViewCompositionStrategy() แล้วระบุกลยุทธ์อื่น

ตารางด้านล่างสรุปสถานการณ์ต่างๆ ที่คุณสามารถใช้ViewCompositionStrategyได้

ViewCompositionStrategy คำอธิบายและสถานการณ์การทํางานร่วมกัน
DisposeOnDetachedFromWindow ระบบจะทิ้งการคอมโพสเมื่อมีการแยก ComposeView ที่อยู่เบื้องหลังออกจากหน้าต่าง แทนที่ด้วย DisposeOnDetachedFromWindowOrReleasedFromPool.

สถานการณ์การทํางานร่วมกัน:

* ComposeView ไม่ว่าจะองค์ประกอบเดียวในลําดับชั้นของมุมมอง หรือในบริบทของหน้าจอมุมมอง/เขียนแบบผสม (ไม่ใช่ใน FRG)
DisposeOnDetachedFromWindowOrReleasedFromPool (ค่าเริ่มต้น) คล้ายกับ DisposeOnDetachedFromWindow เมื่อการประพันธ์ไม่ได้อยู่ในคอนเทนเนอร์การรวม เช่น RecyclerView หากอยู่ในคอนเทนเนอร์การรวมกลุ่ม ระบบจะทิ้งรายการนั้นเมื่อคอนเทนเนอร์การรวมกลุ่มแยกออกจากหน้าต่าง หรือเมื่อมีการทิ้งรายการ (เมื่อพูลเต็ม)

สถานการณ์การทํางานร่วมกัน:

* ComposeView ไม่ว่าจะอยู่ในองค์ประกอบเดียวในลําดับชั้นของ View หรือในบริบทของหน้าจอ View/Compose แบบผสม (ไม่ได้อยู่ใน Fregment)
* ComposeView เป็นรายการในคอนเทนเนอร์การรวม เช่น RecyclerView
DisposeOnLifecycleDestroyed ระบบจะทิ้งคอมโพสิชันเมื่อมีการทำลาย Lifecycle ที่ระบุ

สถานการณ์การผสานรวม

* ComposeView ในมุมมองของ Fregment
DisposeOnViewTreeLifecycleDestroyed ระบบจะทิ้งคอมโพสิชันเมื่อมีการทำลาย Lifecycle ที่เป็นของ LifecycleOwner ที่ ViewTreeLifecycleOwner.get ของหน้าต่างถัดไปที่ View แนบอยู่แสดงผล

สถานการณ์การทํางานร่วมกัน:

* ComposeView ในมุมมองของ Fregment
* ComposeView ในมุมมองที่ยังไม่ทราบว่าวงจรของลูกค้าเป็นอย่างไร

ComposeView ใน "ส่วนย่อย"

หากต้องการรวมเนื้อหา UI ของ Compose ไว้ในแฟรกเมนต์หรือเลย์เอาต์ View ที่มีอยู่ ให้ใช้ ComposeView แล้วเรียกใช้เมธอด setContent() ของ Compose 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 ให้ขยายเลย์เอาต์จาก layout resource ที่กําหนดใน XML จากนั้นรับ ComposeView โดยใช้รหัส XML ตั้งค่ากลยุทธ์การคอมโพสิชันที่เหมาะกับโฮสต์ View มากที่สุด และเรียกใช้ setContent() เพื่อใช้การคอมโพสิชัน

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ก็ได้โดยอ้างอิงคลาสการเชื่อมโยงที่สร้างขึ้นสำหรับไฟล์เลย์เอาต์ XML ดังนี้

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

องค์ประกอบข้อความ 2 รายการที่ต่างกันเล็กน้อย โดยวางซ้อนกัน

รูปที่ 1 การแสดงผลนี้แสดงเอาต์พุตของโค้ดที่เพิ่มองค์ประกอบการเขียนในลําดับชั้น UI ของมุมมอง ข้อความ "สวัสดี Android" จะแสดงโดยวิดเจ็ต TextView ข้อความ "สวัสดี Compose" จะแสดงโดยองค์ประกอบข้อความ Compose

นอกจากนี้ คุณยังใส่ ComposeView ลงในข้อมูลโค้ดโดยตรงได้หากสร้างหน้าจอแบบเต็มด้วย Compose ซึ่งจะช่วยให้คุณไม่ต้องใช้ไฟล์เลย์เอาต์ 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 หลายรายการในเลย์เอาต์เดียวกัน แต่ละรายการต้องมีรหัสที่ไม่ซ้ำกันเพื่อให้ 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 ได้รับการกําหนดไว้ในไฟล์ res/values/ids.xml

<resources>
  <item name="compose_view_x" type="id" />
  <item name="compose_view_y" type="id" />
</resources>

แสดงตัวอย่างคอมโพเนนต์ที่เขียนด้วย Compose ได้ในเครื่องมือสร้างเลย์เอาต์

นอกจากนี้ คุณยังแสดงตัวอย่างคอมโพสิเบิลภายในเครื่องมือแก้ไขเลย์เอาต์สำหรับเลย์เอาต์ XML ที่มี ComposeView ได้ด้วย ซึ่งจะช่วยให้คุณเห็นว่าคอมโพสิเบิลมีลักษณะอย่างไรในเลย์เอาต์แบบผสมผสานระหว่างมุมมองและคอมโพสิเบิล

สมมติว่าคุณต้องการแสดงคอมโพสิเบิลต่อไปนี้ในเครื่องมือแก้ไขเลย์เอาต์ โปรดทราบว่าคอมโพสิชันที่มีคำอธิบายประกอบด้วย @Preview เหมาะที่จะแสดงตัวอย่างในเครื่องมือแก้ไขเลย์เอาต์

@Preview
@Composable
fun GreetingPreview() {
    Greeting(name = "Android")
}

หากต้องการแสดงคอมโพสิเบิลนี้ ให้ใช้แอตทริบิวต์tools:composableName tools และตั้งค่าเป็นชื่อแบบเต็มที่สมบูรณ์ของคอมโพสิเบิลเพื่อแสดงตัวอย่างในเลย์เอาต์

<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 การทำงานร่วมกันเพื่อใช้ Compose ใน View แล้ว โปรดดูวิธีใช้ View ใน Compose