שימוש בתצוגות מפורטות בכתיבה

אפשר לכלול היררכיית View של Android בממשק המשתמש של Compose. הגישה הזו שימושית במיוחד אם רוצים להשתמש ברכיבי ממשק משתמש שעדיין לא זמינים ב-Compose, כמו AdView. הגישה הזו מאפשרת גם לעשות שימוש חוזר בתצוגות בהתאמה אישית שיצרתם.

כדי לכלול רכיב תצוגה או היררכיה, משתמשים ב-composable‏ AndroidView . ל-AndroidView מועברת פונקציית lambda שמחזירה View. AndroidView מספק גם פונקציית קריאה חוזרת (callback) מסוג update שנקראת כשהתצוגה מורחבת. ה-AndroidView יתבצע מחדש בכל פעם שקריאת ה-State בתוך קריאת החזרה (callback) תשתנה. AndroidView, כמו רכיבים רבים אחרים מובנים, מקבל פרמטר Modifier שאפשר להשתמש בו, למשל, כדי להגדיר את המיקום שלו ברכיב ההורה.

@Composable
fun CustomView() {
    var selectedItem by remember { mutableStateOf(0) }

    // Adds view to Compose
    AndroidView(
        modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
        factory = { context ->
            // Creates view
            MyView(context).apply {
                // Sets up listeners for View -> Compose communication
                setOnClickListener {
                    selectedItem = 1
                }
            }
        },
        update = { view ->
            // View's been inflated or state read in this block has been updated
            // Add logic here if necessary

            // As selectedItem is read here, AndroidView will recompose
            // whenever the state changes
            // Example of Compose -> View communication
            view.selectedItem = selectedItem
        }
    )
}

@Composable
fun ContentExample() {
    Column(Modifier.fillMaxSize()) {
        Text("Look at this CustomView!")
        CustomView()
    }
}

AndroidView עם קישור תצוגה

כדי להטמיע פריסה של XML, משתמשים ב-API‏ AndroidViewBinding שמסופק על ידי הספרייה androidx.compose.ui:ui-viewbinding. כדי לעשות זאת, צריך להפעיל בפרויקט את קישור התצוגה המפורטת.

@Composable
fun AndroidViewBindingExample() {
    AndroidViewBinding(ExampleLayoutBinding::inflate) {
        exampleView.setBackgroundColor(Color.GRAY)
    }
}

AndroidView ברשימות 'לאט'

אם אתם משתמשים ב-AndroidView ברשימה עצלה (LazyColumn,‏ LazyRow,‏ Pager וכו'), מומלץ להשתמש בעלות יתר של AndroidView שנוספה בגרסה 1.4.0-rc01. עומס יתר זה מאפשר ל-Compose לעשות שימוש חוזר במכונה הבסיסית של View כשנעשה שימוש חוזר בהרכבה המכילה, כמו במקרה של רשימות Lazy.

עומס יתר זה של AndroidView מוסיף 2 פרמטרים נוספים:

  • onReset – קריאה חוזרת (callback) שמפעילים כדי לסמן שה-View עומד לשימוש חוזר. כדי לאפשר שימוש חוזר בתצוגה, הערך הזה לא יכול להיות null.
  • onRelease (אופציונלי) – קריאה חוזרת (callback) שמופיעה כדי לסמן שה-View יצא מהקומפוזיציה ולא ייעשה בו שימוש חוזר.

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun AndroidViewInLazyList() {
    LazyColumn {
        items(100) { index ->
            AndroidView(
                modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
                factory = { context ->
                    MyView(context)
                },
                update = { view ->
                    view.selectedItem = index
                },
                onReset = { view ->
                    view.clear()
                }
            )
        }
    }
}

קטעים ב-Compose

משתמשים ב-composable ‏AndroidViewBinding כדי להוסיף Fragment ב-Compose. ל-AndroidViewBinding יש טיפול ספציפי לקטעים, כמו הסרת הקטע כשהרכיב הניתן ליצירה יוצא מההרכבה.

כדי לעשות זאת, צריך לנפח קובץ XML שמכיל FragmentContainerView בתור המאגר של Fragment.

לדוגמה, אם הגדרתם את my_fragment_layout.xml, תוכלו להשתמש בקוד כזה ולהחליף את מאפיין ה-XML android:name בשם הכיתה של Fragment:

<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.example.compose.snippets.interop.MyFragment" />

מנפחים את הקטע הזה ב-Compose באופן הבא:

@Composable
fun FragmentInComposeExample() {
    AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
        val myFragment = fragmentContainerView.getFragment<MyFragment>()
        // ...
    }
}

אם אתם צריכים להשתמש בכמה קטעים באותו פריסה, חשוב לוודא שהגדרתם מזהה ייחודי לכל FragmentContainerView.

קריאה למסגרת Android מ-Compose

Compose פועל בתוך הכיתות של מסגרת Android. לדוגמה, הוא מתארח בכיתות View של Android, כמו Activity או Fragment, ויכול להשתמש בכיתות של מסגרת Android, כמו Context, משאבי המערכת, Service או BroadcastReceiver.

מידע נוסף על משאבי המערכת זמין במאמר משאבים ב-Compose.

רכיבים מקומיים של יצירה מוזיקלית

באמצעות הכיתות של CompositionLocal אפשר להעביר נתונים באופן משתמע באמצעות פונקציות שניתנות ליצירה. בדרך כלל מציינים להם ערך בצומת מסוים בעץ של ממשק המשתמש. הצאצאים הניתנים ליצירה של הפונקציה יכולים להשתמש בערך הזה בלי להצהיר על CompositionLocal כפרמטר בפונקציה הניתנת ליצירה.

CompositionLocal משמש להפצת ערכים של סוגי מסגרות של Android ב-Compose, כמו Context,‏ Configuration או View שבו מתארח קוד Compose עם LocalContext,‏ LocalConfiguration או LocalView התואמים. הערה: לכיתות CompositionLocal מצורף הקידומת Local כדי לשפר את הגילוי שלהן באמצעות ההשלמה האוטומטית בסביבת הפיתוח המשולבת (IDE).

כדי לגשת לערך הנוכחי של CompositionLocal, משתמשים במאפיין current שלו. לדוגמה, הקוד הבא מציג הודעת טוסטים על ידי העברת הערך LocalContext.current לשיטה Toast.makeToast.

@Composable
fun ToastGreetingButton(greeting: String) {
    val context = LocalContext.current
    Button(onClick = {
        Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
    }) {
        Text("Greet")
    }
}

לדוגמה מלאה יותר, אפשר לעיין בקטע Case Study: BroadcastReceivers בסוף המסמך הזה.

אינטראקציות אחרות

אם לא מוגדרת תוכנית שירות לאינטראקציה הרצויה, מומלץ לפעול לפי ההנחיה הכללית של Compose: הנתונים זורמים למטה, האירועים זורמים למעלה (הנושא הזה מוסבר בהרחבה במאמר חשיבה ב-Compose). לדוגמה, הרכיב הבא מפעיל פעילות אחרת:

class OtherInteractionsActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // get data from savedInstanceState
        setContent {
            MaterialTheme {
                ExampleComposable(data, onButtonClick = {
                    startActivity(Intent(this, MyActivity::class.java))
                })
            }
        }
    }
}

@Composable
fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
    Button(onClick = onButtonClick) {
        Text(data.title)
    }
}

מקרה לדוגמה: מקלטי שידורים

כדי להציג דוגמה ריאליסטית יותר לתכונות שאפשר להעביר או להטמיע ב-Compose, ולראות את CompositionLocal ואת האפקטים המשניים, נניח שצריך לרשום BroadcastReceiver מפונקציה שניתנת ליצירה.

הפתרון משתמש ב-LocalContext כדי להשתמש בהקשר הנוכחי, ובתופעות הלוואי של rememberUpdatedState ו-DisposableEffect.

@Composable
fun SystemBroadcastReceiver(
    systemAction: String,
    onSystemEvent: (intent: Intent?) -> Unit
) {
    // Grab the current context in this part of the UI tree
    val context = LocalContext.current

    // Safely use the latest onSystemEvent lambda passed to the function
    val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)

    // If either context or systemAction changes, unregister and register again
    DisposableEffect(context, systemAction) {
        val intentFilter = IntentFilter(systemAction)
        val broadcast = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                currentOnSystemEvent(intent)
            }
        }

        context.registerReceiver(broadcast, intentFilter)

        // When the effect leaves the Composition, remove the callback
        onDispose {
            context.unregisterReceiver(broadcast)
        }
    }
}

@Composable
fun HomeScreen() {

    SystemBroadcastReceiver(Intent.ACTION_BATTERY_CHANGED) { batteryStatus ->
        val isCharging = /* Get from batteryStatus ... */ true
        /* Do something if the device is charging */
    }

    /* Rest of the HomeScreen */
}

השלבים הבאים

עכשיו, אחרי שסיפרנו לכם על ממשקי ה-API לתאימות הדדית כשמשתמשים ב-Compose ב-Views ולהפך, כדאי לעיין בדף שיקולים נוספים כדי לקבל מידע נוסף.