Material Design 2 בכתיבה

‫Jetpack Compose מציע הטמעה של Material Design, מערכת עיצוב מקיפה ליצירת ממשקים דיגיטליים. רכיבי Material Design (כפתורים, כרטיסים, מתגים וכו') מבוססים על התאמת העיצוב של Google Material Design, שהיא שיטה שיטתית להתאמה אישית של Material Design כדי לשקף טוב יותר את המותג של המוצר. ערכת נושא של Material כוללת מאפיינים של צבע, טיפוגרפיה וצורה. כשאתם משנים את המאפיינים האלה, השינויים מתעדכנים אוטומטית ברכיבים שבהם אתם משתמשים כדי לבנות את האפליקציה.

Jetpack Compose מטמיעה את המושגים האלה באמצעות פונקציית ה-MaterialTheme:

MaterialTheme(
    colors = // ...
    typography = // ...
    shapes = // ...
) {
    // app content
}

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

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

צבע

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

val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)

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

דוגמה ללוח צבעים של עיצוב
איור 2. מערכת הצבעים של Material.

‫Compose מספקת את המחלקה Colors כדי ליצור מודל של מערכת הצבעים של Material. ‫Colors מספק פונקציות ליצירת קבוצות של צבעים בהירים או כהים:

private val Yellow200 = Color(0xffffeb46)
private val Blue200 = Color(0xff91a4fc)
// ...

private val DarkColors = darkColors(
    primary = Yellow200,
    secondary = Blue200,
    // ...
)
private val LightColors = lightColors(
    primary = Yellow500,
    primaryVariant = Yellow400,
    secondary = Blue700,
    // ...
)

אחרי שמגדירים את Colors, אפשר להעביר אותם אל MaterialTheme:

MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors
) {
    // app content
}

שימוש בצבעים של העיצוב

אפשר לאחזר את Colors שסופק לרכיב הקומפוזבילי MaterialTheme באמצעות MaterialTheme.colors.

Text(
    text = "Hello theming",
    color = MaterialTheme.colors.primary
)

צבע המשטח והתוכן

הרבה רכיבים מקבלים צבע וצבע תוכן:

Surface(
    color = MaterialTheme.colors.surface,
    contentColor = contentColorFor(color),
    // ...
) { /* ... */ }

TopAppBar(
    backgroundColor = MaterialTheme.colors.primarySurface,
    contentColor = contentColorFor(backgroundColor),
    // ...
) { /* ... */ }

כך אפשר לא רק להגדיר את הצבע של רכיב ה-Composable, אלא גם לספק צבע ברירת מחדל לתוכן,לרכיבי ה-Composable שכלולים בו. הרבה קומפוזיציות משתמשות בצבע התוכן הזה כברירת מחדל. לדוגמה, הצבע של Text מבוסס על צבע התוכן של הרכיב ברמה שמעליו, ו-Icon משתמש בצבע הזה כדי להגדיר את הגוון שלו.

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

ה-method‏ contentColorFor() מאחזר את הצבע המתאים למצב 'מופעל' לכל צבעי העיצוב. לדוגמה, אם מגדירים צבע רקע primary ב-Surface, הפונקציה הזו משמשת להגדרת onPrimary כצבע התוכן. אם מגדירים צבע רקע שאינו צבע העיצוב, צריך לציין גם צבע מתאים לתוכן. אפשר להשתמש ב-LocalContentColor כדי לאחזר את צבע התוכן המועדף עבור הרקע הנוכחי, במיקום נתון בהיררכיה.

גרסת אלפא של תוכן

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

ב-Jetpack Compose, ההטמעה מתבצעת באמצעות LocalContentAlpha. אפשר לציין ערך שקיפות של תוכן בהיררכיה על ידי הזנת ערך במאפיין CompositionLocal. רכיבים מורכבים מוטמעים יכולים להשתמש בערך הזה כדי להחיל את הטיפול באלפא על התוכן שלהם. לדוגמה, Text ו-Icon משתמשים כברירת מחדל בשילוב של LocalContentColor עם התאמה לשימוש ב-LocalContentAlpha. ב-Material מוגדרים כמה ערכי אלפא סטנדרטיים (high, ‏ medium, ‏ disabled) שמיוצגים על ידי האובייקט ContentAlpha.

// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
    Text(
        // ...
    )
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
    Icon(
        // ...
    )
    Text(
        // ...
    )
}

מידע נוסף על CompositionLocal זמין במאמר Locally scoped data with CompositionLocal.

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

עיצוב כהה

ב-Compose, מטמיעים עיצובים בהירים וכהים על ידי אספקת קבוצות שונות של Colors ל-composable‏ MaterialTheme:

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        /*...*/
        content = content
    )
}

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

אפשר להשתמש בקוד כמו זה כדי לבדוק אם הצבעים הנוכחיים Colors בהירים או כהים:

val isLightTheme = MaterialTheme.colors.isLight
Icon(
    painterResource(
        id = if (isLightTheme) {
            R.drawable.ic_sun_24
        } else {
            R.drawable.ic_moon_24
        }
    ),
    contentDescription = "Theme"
)

שכבות-על של גובה

ב-Material, משטחים בעיצובים כהים עם גבהים גדולים יותר מקבלים שכבות-על של גובה, שמבהירות את הרקע שלהם. ככל שהגובה של משטח מסוים גדול יותר (כלומר, הוא קרוב יותר למקור אור משוער), כך הוא בהיר יותר.

הקומפוזבל Surface מחיל את שכבות העל האלה באופן אוטומטי כשמשתמשים בצבעים כהים, וכך גם כל קומפוזבל אחר של Material שמשתמש במשטח:

Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }

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

בתרחישים מותאמים אישית שלא כוללים Surface, משתמשים ב-LocalElevationOverlay, שהוא CompositionLocal שמכיל את ElevationOverlay שמשמש את רכיבי Surface:

// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
    color, elevation
)

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

MyTheme {
    CompositionLocalProvider(LocalElevationOverlay provides null) {
        // Content without elevation overlays
    }
}

צבעים משניים מוגבלים

ב-Material מומלץ להשתמש בהדגשות צבע מוגבלות בעיצובים כהים, ולכן ברוב המקרים עדיף להשתמש בצבע surface ולא בצבע primary. רכיבי Material כמו TopAppBar ו-BottomNavigation מטמיעים את ההתנהגות הזו כברירת מחדל.

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

בתרחישים מותאמים אישית, משתמשים במאפיין התוסף primarySurface:

Surface(
    // Switches between primary in light theme and surface in dark theme
    color = MaterialTheme.colors.primarySurface,
    /*...*/
) { /*...*/ }

טיפוגרפיה

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

דוגמה לכמה גופנים שונים בסגנונות שונים
איור 7. מערכת סוגי החומרים.

‫Compose מטמיעה את מערכת הסוגים באמצעות המחלקות Typography,‏ TextStyle וfont-related. ה-constructor‏ Typography מציע ערכי ברירת מחדל לכל סגנון, כך שאפשר להשמיט את הסגנונות שלא רוצים להתאים אישית:

val raleway = FontFamily(
    Font(R.font.raleway_regular),
    Font(R.font.raleway_medium, FontWeight.W500),
    Font(R.font.raleway_semibold, FontWeight.SemiBold)
)

val myTypography = Typography(
    h1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = myTypography, /*...*/) {
    /*...*/
}

אם רוצים להשתמש באותו גופן בכל הטקסט, צריך לציין את הפרמטר defaultFontFamily ולהשמיט את fontFamily מכל רכיבי TextStyle:

val typography = Typography(defaultFontFamily = raleway)
MaterialTheme(typography = typography, /*...*/) {
    /*...*/
}

שימוש בסגנונות טקסט

הגישה לרכיבי TextStyle מתבצעת באמצעות MaterialTheme.typography. כך מאחזרים את רכיבי TextStyle:

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

צילום מסך שבו רואים שילוב של גופנים שונים למטרות שונות
איור 8. משתמשים במבחר של גופנים וסגנונות כדי לבטא את המותג.

צורה

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

מוצג מגוון של צורות Material Design
איור 9. מערכת הצורות של Material.

‫Compose מטמיע את מערכת הצורות באמצעות המחלקה Shapes, שמאפשרת לציין CornerBasedShape לכל קטגוריית מידות:

val shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomEnd = 0.dp,
        bottomStart = 16.dp
    )
)

MaterialTheme(shapes = shapes, /*...*/) {
    /*...*/
}

הרבה רכיבים משתמשים בצורות האלה כברירת מחדל. לדוגמה, Button‏, TextField ו-FloatingActionButton מוגדרים כברירת מחדל כקטנים, AlertDialog מוגדר כברירת מחדל כבינוני ו-ModalDrawer מוגדר כברירת מחדל כגדול. בטבלת המיפוי המלאה של סכימת הצורות מפורטות כל האפשרויות.

שימוש בצורות

הגישה לרכיבי Shape מתבצעת באמצעות MaterialTheme.shapes. אפשר לאחזר את רכיבי Shape באמצעות קוד כמו זה:

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

צילום מסך של אפליקציה שמשתמשת בצורות Material כדי להעביר את המצב של אלמנט
איור 10. להשתמש בצורות כדי להביע מותג או מצב.

סגנונות ברירת מחדל

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

@Composable
fun MyButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

שכבות-על של עיצוב

כדי להשיג את האפקט של שילובי נושאים מ-Android Views ב-Compose, אפשר להשתמש ב-composables של MaterialTheme. מכיוון ש-MaterialTheme מגדיר את ברירת המחדל של הצבעים, הטיפוגרפיה והצורות לערך הנושא הנוכחי, כל שאר הפרמטרים שומרים על ערכי ברירת המחדל שלהם כשנושא מגדיר רק אחד מהפרמטרים האלה.

בנוסף, כשמעבירים מסכים מבוססי-View לפיתוח נייטיב, צריך לשים לב לשימושים במאפיין android:theme. סביר להניח שצריך ליצור MaterialThemeחדש בחלק הזה של עץ ממשק המשתמש של הכתיבה.

בדוגמה הזו, במסך הפרטים נעשה שימוש ב-PinkTheme ברוב המסך, ואז ב-BlueTheme בקטע שקשור לנושא. בצילום המסך ובקוד הבאים מוצגת דוגמה למושג הזה:

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

@Composable
fun DetailsScreen(/* ... */) {
    PinkTheme {
        // other content
        RelatedSection()
    }
}

@Composable
fun RelatedSection(/* ... */) {
    BlueTheme {
        // content
    }
}

מצבי הרכיבים

רכיבי Material שאפשר לקיים איתם אינטראקציה (ללחוץ עליהם, להפעיל או להשבית אותם וכו') יכולים להיות במצבים חזותיים שונים. הסטטוסים כוללים 'מופעל', 'מושבת', 'נלחץ' וכו'.

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

צילום מסך של שני לחצנים: אחד מופעל ואחד מושבת, שמוצגים בהם מצבים חזותיים שונים
איור 12. לחצן עם enabled = true (שמאלה) ו-enabled = false (ימינה).

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

Button(
    onClick = { /* ... */ },
    enabled = true,
    // Custom colors for different states
    colors = ButtonDefaults.buttonColors(
        backgroundColor = MaterialTheme.colors.secondary,
        disabledBackgroundColor = MaterialTheme.colors.onBackground
            .copy(alpha = 0.2f)
            .compositeOver(MaterialTheme.colors.background)
        // Also contentColor and disabledContentColor
    ),
    // Custom elevation for different states
    elevation = ButtonDefaults.elevation(
        defaultElevation = 8.dp,
        disabledElevation = 2.dp,
        // Also pressedElevation
    )
) { /* ... */ }

צילום מסך של שני לחצנים עם צבע וגובה מותאמים למצבים של הפעלה והשבתה
איור 13. לחצן עם enabled = true (שמאלי) ו-enabled = false (ימני), עם ערכי צבע וגובה מותאמים.

אדוות

רכיבי Material משתמשים באפקט אדווה כדי לציין שיש אינטראקציה איתם. אם משתמשים ב-MaterialTheme בהיררכיה, נעשה שימוש ב-Ripple כIndication ברירת המחדל בתוך משנים כמו clickable ו-indication.

.

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

אפשר להרחיב את RippleTheme ולהשתמש בפונקציות השירות defaultRippleColor ו-defaultRippleAlpha. אחר כך תוכלו לספק את עיצוב האדווה המותאם אישית בהיררכיה באמצעות LocalRippleTheme:

@Composable
fun MyApp() {
    MaterialTheme {
        CompositionLocalProvider(
            LocalRippleTheme provides SecondaryRippleTheme
        ) {
            // App content
        }
    }
}

@Immutable
private object SecondaryRippleTheme : RippleTheme {
    @Composable
    override fun defaultColor() = RippleTheme.defaultRippleColor(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )

    @Composable
    override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )
}

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

מידע נוסף

כדי לקבל מידע נוסף על התאמת העיצוב של Google Material Design ב-Compose, אפשר לעיין במקורות המידע הנוספים הבאים.

Codelabs

סרטונים