שכבות ארכיטקטוניות ב-Jetpack פיתוח נייטיב

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

‏Jetpack Compose הוא לא פרויקט מונוליתי יחיד, אלא הוא נוצר ממספר מודולים שמרכיבים יחד כדי ליצור סטאק מלא. הבנת המודולים השונים שמרכיבים את Jetpack Compose מאפשרת לכם:

  • שימוש ברמת הסתירה המתאימה ליצירת האפליקציה או הספרייה
  • מתי אפשר לרדת לרמה נמוכה יותר כדי לקבל יותר שליטה או התאמה אישית
  • צמצום יחסי התלות

שכבות

השכבות העיקריות של Jetpack פיתוח נייטיב הן:

איור 1. השכבות העיקריות של Jetpack פיתוח נייטיב.

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

זמן ריצה
המודול הזה מספק את העקרונות הבסיסיים של סביבת זמן הריצה של Compose, כמו remember,‏ mutableStateOf, ההערה @Composable ו-SideEffect. אם אתם צריכים רק את יכולות ניהול העץ של Compose, ולא את ממשק המשתמש שלו, כדאי לכם לפתח ישירות בשכבה הזו.
UI
שכבת ממשק המשתמש מורכבת מכמה מודולים (ui-text,‏ ui-graphics,‏ ui-tooling וכו'). במודולים האלה מוטמעים העקרונות הבסיסיים של ערכת הכלים לבניית ממשק משתמש, כמו LayoutNode,‏ Modifier, רכיבי טיפול בקלט, פריסות בהתאמה אישית ורישום. כדאי להשתמש בשכבה הזו אם אתם צריכים רק את המושגים הבסיסיים של ערכת כלים לממשק משתמש.
Foundation
המודול הזה מספק אבני בניין לא תלויות במערכת עיצוב לממשק המשתמש של Compose, כמו Row ו-Column, LazyColumn, זיהוי של תנועות ספציפיות וכו'. מומלץ להשתמש בשכבת הבסיס הזו כדי ליצור מערכת עיצוב משלכם.
חומר
המודול הזה מספק הטמעה של מערכת Material Design לממשק המשתמש של Compose, עם מערכת נושאים, רכיבים מעוצבים, אינדיקציות של תנודות וסמלילים. כשמשתמשים ב-Material Design באפליקציה, אפשר להשתמש בשכבה הזו.

עקרונות עיצוב

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

בקרה

רכיבים ברמה גבוהה יותר נוטים לבצע יותר משימות, אבל מגבילים את מידת הבקרה הישירה שיש לכם. אם אתם צריכים יותר שליטה, תוכלו להשתמש ברכיב ברמה נמוכה יותר.

לדוגמה, אם רוצים ליצור אנימציה של הצבע של רכיב, אפשר להשתמש ב-API‏ animateColorAsState:

val color = animateColorAsState(if (condition) Color.Green else Color.Red)

עם זאת, אם אתם צריכים שהרכיב תמיד יתחיל באפור, אי אפשר לעשות זאת באמצעות ה-API הזה. במקום זאת, אפשר לעבור לרמה התחתונה של ה-API Animatable:

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

ממשק ה-API ברמה animateColorAsState מבוסס על ממשק ה-API ברמה Animatable. השימוש ב-API ברמה נמוכה יותר הוא מורכב יותר, אבל הוא מאפשר יותר שליטה. בוחרים את רמת ההפשטה שהכי מתאימה לצרכים שלכם.

התאמה אישית

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

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

Button מורכב מ-4 רכיבים:

  1. חומר Surface שמספק את הרקע, הצורה, טיפול בלחיצות וכו'.

  2. CompositionLocalProvider שמשנה את האלפא של התוכן כשהלחצן מופעל או מושבת

  3. ProvideTextStyle מגדיר את סגנון ברירת המחדל של הטקסט

  4. Row מספק את מדיניות ברירת המחדל של הפריסה לתוכן הלחצן

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

עם זאת, אם רוצים לבצע התאמה אישית מעבר לפרמטרים של רכיב, אפשר לרדת רמה ולבצע יצירת ענף של רכיב. לדוגמה, לפי Material Design, ללחצנים צריך להיות רקע בצבע אחיד. אם אתם צריכים רקע עם שיפוע, הפרמטרים Button לא תומכים באפשרות הזו. במקרה כזה, אפשר להשתמש בהטמעה של Button ב-Material Design כמקור מידע ולפתח רכיב משלכם:

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

בהטמעה שלמעלה עדיין נעשה שימוש ברכיבים משכבת Material, כמו המושגים של Material לגבי אלפא של התוכן הנוכחי וסגנון הטקסט הנוכחי. עם זאת, הוא מחליף את החומר Surface ב-Row ומעצב אותו כדי להשיג את המראה הרצוי.

אם אתם לא רוצים להשתמש בכלל במושגי Material, למשל אם אתם יוצרים מערכת עיצוב מותאמת אישית משלכם, תוכלו להשתמש רק ברכיבים של שכבת הבסיס:

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

השמות הפשוטים ביותר שמורים לרכיבים ברמה הגבוהה ביותר ב-Jetpack Compose. לדוגמה, androidx.compose.material.Text מבוסס על androidx.compose.foundation.text.BasicText. כך תוכלו לספק הטמעה משלכם עם השם הבולט ביותר, אם תרצו להחליף רמות גבוהות יותר.

בחירת רמת האבסטרקציה המתאימה

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

לדוגמה, אם רוצים להוסיף תמיכה בתנועות למרכיב מותאם אישית, אפשר ליצור אותו מאפס באמצעות Modifier.pointerInput, אבל יש רכיבים אחרים ברמה גבוהה יותר שנוצרו על סמך המרכיב הזה, שיכולים להוות נקודת התחלה טובה יותר. לדוגמה, Modifier.draggable,‏ Modifier.scrollable או Modifier.swipeable.

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

מידע נוסף

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