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

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

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

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

שכבות

השכבות העיקריות של Jetpack Compose הן:

איור 1. השכבות העיקריות של Jetpack Compose.

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

זמן ריצה
המודול הזה מספק את העקרונות הבסיסיים של סביבת זמן הריצה של Compose, כמו remember, mutableStateOf, ההערה @Composable ו-SideEffect. אם אתם צריכים רק את יכולות ניהול העץ של Compose, ולא את ממשק המשתמש שלו, כדאי לשקול לבנות ישירות על השכבה הזו.
ממשק משתמש
שכבת ממשק המשתמש מורכבת מכמה מודולים ( 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 שסופקה על ידי שכבת Material:

@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 מציעים התאמות אישיות שצוינו במערכת Material Design, כך שקל לפעול לפי העקרונות של Material Design.

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

@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 מוסבר איך ליצור מערכת עיצוב בהתאמה אישית.