שימוש ב'כתיבה' בתצוגות

אתם יכולים להוסיף ממשק משתמש מבוסס-Compose לאפליקציה קיימת שמשתמשת בעיצוב מבוסס-View.

כדי ליצור מסך חדש שמבוסס כולו על Compose, הפעילות צריכה לקרוא ל-method‏ 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 מגדיר מתי צריך להשמיד את הקומפוזיציה. ברירת המחדל, ViewCompositionStrategy.Default, היא שהאובייקט Composition מושמד כשהאובייקט הבסיסי ComposeView מנותק מהחלון, אלא אם הוא חלק מקונטיינר של מאגרים, כמו RecyclerView. באפליקציה עם Activity יחיד שכוללת רק קוד Compose, זוהי התנהגות ברירת המחדל הרצויה. עם זאת, אם אתם מוסיפים קוד Compose בהדרגה לבסיס הקוד שלכם, יכול להיות שהתנהגות ברירת המחדל הזו תגרום לאובדן מצב בתרחישים מסוימים.

כדי לשנות את ViewCompositionStrategy, מתקשרים לשיטה setViewCompositionStrategy() ומספקים שיטה אחרת.

בטבלה הבאה מפורטים התרחישים השונים שבהם אפשר להשתמש ב-ViewCompositionStrategy:

ViewCompositionStrategy תיאור ותרחיש פעולה הדדית
DisposeOnDetachedFromWindow האובייקט Composition יושמד כשהאובייקט ComposeView הבסיסי ינותק מהחלון. הוחלף מאז ב-DisposeOnDetachedFromWindowOrReleasedFromPool.

תרחיש פעולה הדדית:

* ComposeView אם זה הרכיב היחיד בהיררכיית התצוגה, או בהקשר של מסך מעורב של View/Compose (לא ב-Fragment).
DisposeOnDetachedFromWindowOrReleasedFromPool (ברירת מחדל) בדומה ל-DisposeOnDetachedFromWindow, כשהקומפוזיציה לא נמצאת במאגר משותף, כמו RecyclerView. אם הוא נמצא במאגר, הוא יסולק כשהמאגר עצמו ינותק מהחלון, או כשהפריט יושלך (כלומר כשהמאגר מלא).

תרחיש פעולה הדדית:

* ComposeView אם זה הרכיב היחיד בהיררכיית התצוגה, או בהקשר של מסך מעורב של תצוגה/יצירה (לא ב-Fragment).
* ‫ComposeView כפריט בקונטיינר של מאגר, כמו RecyclerView.
DisposeOnLifecycleDestroyed הקומפוזיציה תבוטל כשהרכיב Lifecycle שסופק ייהרס.

תרחיש פעולה הדדית

* ComposeView בתצוגה של Fragment.
DisposeOnViewTreeLifecycleDestroyed האובייקט Composition יושמד כשהאובייקט Lifecycle שבבעלות האובייקט LifecycleOwner שמוחזר על ידי ViewTreeLifecycleOwner.get של החלון הבא שאליו מצורף האובייקט View יושמד.

תרחיש פעולה הדדית:

* ComposeView באובייקט View של Fragment.
* ComposeView בתצוגה שבה מחזור החיים עדיין לא ידוע.

ComposeView בקטעים

אם רוצים לשלב תוכן של ממשק משתמש של Compose ב-Fragment או בפריסת View קיימת, צריך להשתמש ב-ComposeView ולהפעיל את השיטה setContent(). ‫ComposeView הוא View של Android.

אפשר להוסיף את 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. לאחר מכן מקבלים את ComposeView באמצעות מזהה ה-XML, מגדירים אסטרטגיית קומפוזיציה שהכי מתאימה למארח 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
    }
}

אפשר גם להשתמש ב-view binding כדי לקבל הפניות ל-ComposeView על ידי הפניה למחלקת ה-binding שנוצרה עבור קובץ פריסת ה-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
    }
}

שני רכיבי טקסט שונים מעט, אחד מעל השני

איור 1. התמונה מציגה את הפלט של הקוד שמוסיף רכיבי Compose בהיררכיית ממשק משתמש של View. הטקסט Hello Android! מוצג על ידי ווידג'ט TextView. הטקסט Hello 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>

תצוגה מקדימה של פונקציות composable ב-Layout Editor

אפשר גם לראות תצוגה מקדימה של רכיבים שניתנים להרכבה בתוך הכלי לעריכת פריסות עבור פריסת ה-XML שמכילה ComposeView. כך תוכלו לראות איך רכיבי ה-Composable נראים בפריסה של Views ו-Compose.

נניח שרוצים להציג את הרכיב הבא שאפשר להוסיף ל-Layout Editor. הערה: פונקציות Composable עם ההערה @Preview מתאימות לתצוגה מקדימה בכלי Layout Editor.

@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 של יכולת פעולה הדדית צריך להשתמש כדי לכתוב הודעות בתצוגות, אתם יכולים לקרוא איך משתמשים בתצוגות בכתיבת הודעות.