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

אתם יכולים להוסיף ממשק משתמש שמבוסס על פיתוח נייטיב לאפליקציה קיימת שמשתמשת בעיצוב מבוסס-תצוגה.

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

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

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

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

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

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

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

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

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

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

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

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

‫ComposeView ב-Fragments (שלב מעבר)

אם אתם רוצים לשלב תוכן של ממשק משתמש בפיתוח נייטיב במקטע או בפריסת 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
    }
}

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

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

תצוגה מקדימה של רכיבים קומפוזביליים ב-Layout Editor

אפשר גם לראות תצוגה מקדימה של רכיבים קומפוזביליים ב-Layout Editor של פריסת ה-XML שמכילה ComposeView. כך תוכלו לראות איך הרכיבים הניתנים ליצירה נראים בפריסה משולבת של Views ופיתוח נייטיב.

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

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

רכיב Composable שמוצג ב-Layout Editor