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

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

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

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

שכבות

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

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

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

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

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

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

שליטה

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

לדוגמה, אם רוצים להנפיש את הצבע של רכיב מסוים, אפשר להשתמש ב-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. ‫A CompositionLocalProvider שמשנה את האלפא של התוכן כשהלחצן מופעל או מושבת

  3. ‫A 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 פיתוח נייטיב, השמות הפשוטים ביותר שמורים לרכיבים ברמה הגבוהה ביותר. לדוגמה, androidx.compose.material.Text מבוסס על androidx.compose.foundation.text.BasicText. כך תוכלו לספק הטמעה משלכם עם השם הכי קל לזיהוי אם אתם רוצים להחליף רמות גבוהות יותר.

בחירת ההפשטה הנכונה

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

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

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

מידע נוסף

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