Material Design 2 בכתיבה

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

‫Jetpack Compose מטמיע את המושגים האלה באמצעות ה-composable‏ 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 composable באמצעות 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),
    // ...
) { /* ... */ }

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

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

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

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

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

ב-Jetpack פיתוח נייטיב, ההטמעה מתבצעת באמצעות LocalContentAlpha. כדי לציין שקיפות אלפא של תוכן בהיררכיה, מספקים ערך לCompositionLocal. רכיבים מורכבים מוטמעים יכולים להשתמש בערך הזה כדי להחיל את הטיפול באלפא על התוכן שלהם. לדוגמה, Text ו-Icon משתמשים כברירת מחדל בשילוב של LocalContentColor עם התאמה לשימוש ב-LocalContentAlpha. במאפיין החומר מוגדרים כמה ערכי אלפא סטנדרטיים (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 זמין במאמר נתונים בהיקף מקומי באמצעות CompositionLocal.

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

עיצוב כהה

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

@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, משטחים בעיצובים כהים עם גבהים גדולים יותר מקבלים שכבות-על של גובה, שמבהירות את הרקע שלהם. ככל שהגובה של משטח מסוים גדול יותר (כלומר, הוא קרוב יותר למקור אור משוער), כך הוא בהיר יותר.

רכיב ה-Composable‏ Surface מחיל את שכבות העל האלה באופן אוטומטי כשמשתמשים בצבעים כהים, וכך גם כל רכיב Composable אחר של 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. רכיבי Compose של Material כמו TopAppBar ו-BottomNavigation מטמיעים את ההתנהגות הזו כברירת מחדל.

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

לתרחישים מותאמים אישית, משתמשים במאפיין התוסף 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 מוגדרים כברירת מחדל לערך small,‏ AlertDialog מוגדר כברירת מחדל לערך medium ו-ModalDrawer מוגדר כברירת מחדל לערך large. בהפניה לערכי צורה מופיע המיפוי המלא.

שימוש בצורות

הגישה לרכיבי 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 ל-Compose, צריך לשים לב לשימושים במאפיין 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. לחצנים עם ערכי אפקט גלי שונים שסופקו באמצעות RippleTheme.

מידע נוסף

מידע נוסף על עיצוב Material ב-Compose זמין במקורות המידע הבאים.

Codelabs

סרטונים