שאילתת מידע על פריסות מותאמות באמצעות mediaQuery

כדי לעדכן את פריסת האפליקציה, אתם צריכים סוגים שונים של מידע, כמו יכולות המכשיר וסטטוס האפליקציה. המידע הנפוץ ביותר הוא רוחב וגובה החלון. בנוסף, אפשר לעיין במידע הבא:

  • מצב החלון
  • דיוק של אמצעי הצבעה
  • סוג מקלדת
  • אם המכשיר תומך במצלמה ובמיקרופון
  • המרחק בין המשתמש למסך המכשיר

המידע מתעדכן באופן דינמי, לכן צריך לעקוב אחריו ולהפעיל את ה-recomposition כשמתבצע עדכון. הפונקציה mediaQuery מסתירה את פרטי אחזור המידע ומאפשרת לכם להתמקד בהגדרת התנאי להפעלת עדכוני הפריסה.

בדוגמה הבאה, הפריסה משתנה ל-TabletopLayout כשהמכשיר המתקפל נמצא במצב "על משטח, מסך למעלה":

@Composable
fun VideoPlayer(
    // ...
) {
    // ...
            if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
                TabletopLayout()
            } else {
                FlatLayout()
            }
    // ...
}

הפעלת הפונקציה mediaQuery

כדי להפעיל את הפונקציה mediaQuery, מגדירים את המאפיין isMediaQueryIntegrationEnabled של האובייקט ComposeUiFlags לערך true:

class MyApplication : Application() {
    override fun onCreate() {
        ComposeUiFlags.isMediaQueryIntegrationEnabled = true
        super.onCreate()
    }
}

הגדרת תנאי עם פרמטרים

אפשר להגדיר תנאי כביטוי למבדה שמוערך בתוך UiMediaScope. הפונקציה mediaQuery מעריכה את התנאי בהתאם לסטטוס הנוכחי וליכולות של המכשיר. הפונקציה מחזירה ערך בוליאני, כך שאפשר לקבוע את הפריסה באמצעות הסתעפויות מותנות כמו ביטוי if. בטבלה 1 מתוארים הפרמטרים שזמינים ב-UiMediaScope.

פרמטר סוג הערך תיאור
windowWidth Dp רוחב החלון הנוכחי ב-dp.
windowHeight Dp גובה החלון הנוכחי ב-dp.
windowPosture UiMediaScope.Posture המצב הנוכחי של חלון האפליקציה.
pointerPrecision UiMediaScope.PointerPrecision הדיוק הגבוה ביותר של אמצעי ההצבעה הזמינים.
keyboardKind UiMediaScope.KeyboardKind סוג המקלדת הזמינה או המחוברת.
hasCamera Boolean אם המצלמה נתמכת במכשיר.
hasMicrophone Boolean אם המיקרופון נתמך במכשיר.
viewingDistance UiMediaScope.ViewingDistance המרחק האופייני בין המשתמש למסך המכשיר.

אובייקט UiMediaScope פותר את ערכי הפרמטרים. הפונקציה mediaQuery משתמשת ב-LocalUiMediaScope.current כדי לגשת לאובייקט UiMediaScope, שמייצג את היכולות וההקשר הנוכחיים של המכשיר. האובייקט הזה מתעדכן באופן דינמי כשמתבצעים שינויים, למשל כשהמשתמש משנה את תנוחת המכשיר. לאחר מכן, הפונקציה mediaQuery מעריכה את פונקציית הלמבדה query עם האובייקט UiMediaScope המעודכן ומחזירה ערך בוליאני. לדוגמה, קטע הקוד הבא בוחר בין TabletopLayout לבין FlatLayout על סמך ערך הפרמטר windowPosture.

@Composable
fun VideoPlayer(
    // ...
) {
    // ...
            if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
                TabletopLayout()
            } else {
                FlatLayout()
            }
    // ...
}

קבלת החלטה על סמך גודל החלון

סיווגים של גודל חלון הם קבוצה של נקודות עצירה (breakpoint) של אזור תצוגה, שעוזרות לכם לעצב, לפתח ולבדוק פריסות דינמיות. אתם יכולים להשוות בין שני הפרמטרים שמייצגים את גודל החלון הנוכחי לבין הסף שמוגדר בקטגוריות של גודל החלון. בדוגמה הבאה, מספר החלוניות משתנה בהתאם לרוחב החלון. WindowSizeClass כוללת קבועים לספי הגודל של החלונות (איור 1).

הפונקציה derivedMediaQuery מחשבת את הלמבדה query ועוטפת את התוצאה ב-derivedStateOf. מכיוון שהערכים של windowWidth ו-windowHeight יכולים להתעדכן לעיתים קרובות, צריך להפעיל את הפונקציה derivedMediaQuery במקום את הפונקציה mediaQuery כשמפנים לפרמטרים האלה ב-lambda של query.

val narrowerThanMedium by derivedMediaQuery {
    windowWidth < WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND.dp
}
val narrowerThanExpanded by derivedMediaQuery {
    windowWidth < WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND.dp
}
when {
    narrowerThanMedium -> SinglePaneLayout()
    narrowerThanExpanded -> TwoPaneLayout()
    else -> ThreePaneLayout()
}

איור 1. הפריסה מתעדכנת בהתאם לרוחב החלון.

עדכון הפריסה בהתאם למצב החלון

הפרמטר windowPosture מתאר את המצב הנוכחי של החלון כאובייקט UiMediaScope.Posture. כדי לבדוק את מצב הקיפול הנוכחי, משווים בין הפרמטר לבין הערכים שמוגדרים במחלקה UiMediaScope.Posture. בדוגמה הבאה, הפריסה משתנה בהתאם למצב החלון:

when {
    mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
}

בדיקת הדיוק של אמצעי ההצבעה הזמין

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

הפרמטר pointerPrecision מתאר את הדיוק של אמצעי ההצבעה הזמינים, כמו עכבר ומסך מגע. יש ארבעה ערכים שמוגדרים במחלקה UiMediaScope.PointerPrecision: Fine,‏ Coarse,‏ Blunt ו-None. None פירושו שאין אמצעי הצבעה זמין. רמות הדיוק, מהגבוהה ביותר לנמוכה ביותר, הן: Fine, Coarse ו-Blunt.

אם יש כמה אמצעי הצבעה זמינים עם רמות דיוק שונות, הפרמטר יוגדר לפי הרמה הגבוהה ביותר. לדוגמה, אם יש שני אמצעי הצבעה – מכשיר עם דיוק של Fine ומכשיר עם דיוק של Blunt – הערך של הפרמטר pointerPrecision הוא Fine.

בדוגמה הבאה מוצג לחצן גדול יותר כשהמשתמש משתמש באמצעי הצבעה עם דיוק נמוך:

if (mediaQuery { pointerPrecision == UiMediaScope.PointerPrecision.Blunt }) {
    LargeSizeButton()
} else {
    NormalSizeButton()
}

בדיקת סוג המקלדת הזמינה

הפרמטר keyboardKind מייצג את סוג המקלדות הזמינות: Physical,‏ Virtual ו-None. אם מוצגת מקלדת וירטואלית וגם מקלדת פיזית זמינה, הפרמטר מקבל את הערך Physical. אם אף אחד מהם לא מזוהה, הערך של הפרמטר הוא None. בדוגמה הבאה מוצגת הודעה שמציעה למשתמשים לחבר מקלדת אם לא זוהתה מקלדת:

if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.None }) {
    SuggestKeyboardConnect()
}

בודקים אם המכשיר תומך במצלמה ובמיקרופון

יש מכשירים שלא תומכים במצלמות או במיקרופונים. אפשר לבדוק אם המכשיר תומך במצלמה ובמיקרופון באמצעות הפרמטר hasCamera והפרמטר hasMicrophone. בדוגמה הבאה מוצגים כפתורים לשימוש במצלמה ובמיקרופון כשהמכשיר תומך בהם:

Row {
    OutlinedTextField(state = rememberTextFieldState())
    // Show the MicButton when the device supports a microphone.
    if (mediaQuery { hasMicrophone }) {
        MicButton()
    }
    // Show the CameraButton when the device supports a camera.
    if (mediaQuery { hasCamera }) {
        CameraButton()
    }
}

התאמת ממשק המשתמש לפי מרחק הצפייה המשוער

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

יש שלושה ערכים שמוגדרים במחלקה UiMediaScope.ViewingDistance: Near,‏ Medium ו-Far. Near פירושו שהמסך נמצא בטווח קרוב, ו-Far פירושו שהמכשיר נצפה ממרחק. בדוגמה הבאה, גודל הגופן גדל כשמרחק הצפייה הוא Far או Medium:

val fontSize = when {
    mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Far } -> 20.sp
    mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Medium } -> 18.sp
    else -> 16.sp
}

תצוגה מקדימה של רכיב בממשק המשתמש

אפשר לקרוא לפונקציות mediaQuery ו-derivedMediaQuery בפונקציות הקומפוזביליות כדי לראות תצוגה מקדימה של רכיבי ממשק המשתמש. קטע הקוד הבא בוחר בין TabletopLayout לבין FlatLayout על סמך ערך הפרמטר windowPosture. כדי לראות תצוגה מקדימה של TabletopLayout, הפרמטר windowPosture צריך להיות UiMediaScope.Posture.Tabletop.

when {
    mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
}

הפונקציות mediaQuery ו-derivedMediaQuery מעריכות את ה-lambda הנתון של query בתוך אובייקט UiMediaScope, שסופק בתור LocalUiMediaScope.current. כדי לשנות את ההגדרה הזו:

  1. מפעילים את הפונקציה mediaQuery.
  2. מגדירים אובייקט בהתאמה אישית שמטמיע את הממשק של UiMediaScope.
  3. מגדירים את האובייקט בהתאמה אישית ל-LocalUiMediaScope באמצעות הפונקציה CompositionLocalProvider.
  4. מפעילים את הפונקציה הקומפוזבילית כדי להציג תצוגה מקדימה בביטוי הלמדא של התוכן של הפונקציה CompositionLocalProvider.

אפשר לראות תצוגה מקדימה של TabletopLayout בדוגמה הבאה:

@Preview
@Composable
fun PreviewLayoutForTabletop() {
    // Step 1: Enable the mediaQuery function
    ComposeUiFlags.isMediaQueryIntegrationEnabled = true

    val currentUiMediaScope = LocalUiMediaScope.current
    // Step 2: Define a custom object implementing the UiMediaScope interface.
    // The object overrides the windowPosture parameter.
    // The resolution of the remaining parameters is deferred to the currentUiMediaScope object.
    val uiMediaScope = remember(currentUiMediaScope) {
        object : UiMediaScope by currentUiMediaScope {
            override val windowPosture: UiMediaScope.Posture = UiMediaScope.Posture.Tabletop
        }
    }

    // Step 3: Set the object to the LocalUiMediaScope.
    CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) {
        // Step 4: Call the composable to preview.
        when {
            mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
            mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
            mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
        }
    }
}