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

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

כדי לשנות את ViewCompositionStrategy, צריך להפעיל את השיטה setViewCompositionStrategy() ולספק אסטרטגיה אחרת.

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

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

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

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

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

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

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

* ComposeView בתצוגה של קטע קוד.
DisposeOnViewTreeLifecycleDestroyed הקומפוזיציה תימחק כשה-Lifecycle שבבעלות ה-LifecycleOwner שהוחזר על ידי ViewTreeLifecycleOwner.get של החלון הבא שאליו הממשק מחובר נהרס.

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

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

ComposeView במקטעים

אם רוצים לשלב תוכן של ממשק המשתמש של Compose בחלק או בפריסה קיימת של View, משתמשים ב-ComposeView ומפעילים את השיטה setContent(). 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, מנפחים את הפריסה ממשאב הפריסה שמוגדר ב-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
    }
}

לחלופין, אפשר גם להשתמש בקישור תצוגה כדי לקבל הפניות ל-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
    }
}

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

איור 1. כאן יוצג הפלט של הקוד שמוסיף רכיבי 'כתיבה' בהיררכיה של תצוגה מפורטת של ממשק המשתמש. הטקסט "שלום Android!" מוצג על ידי ווידג'ט TextView. הטקסט 'Hello Compose!' מוצג על ידי אלמנט טקסט של Compose.

אפשר גם לכלול ComposeView ישירות בקטע (fragment) אם המסך המלא נוצר באמצעות 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>

תצוגה מקדימה של תכנים קומפוזביליים בעורך הפריסה

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

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