יש כמה דרכים להשתמש בסגנונות כדי לפתח את האפליקציות. הבחירה שלכם תלויה במיקום של האפליקציה ביחס לאימוץ של Material Design:
- מערכת עיצוב בהתאמה אישית מלאה, שלא מבוססת על Material Design
- המלצה: מגדירים סגנונות של רכיבים שמשתמשים בערכים מהנושא, וחושפים פרמטרים של סגנון ברכיבים של מערכת העיצוב.
- שימוש ב-Material Design
- המלצה: כדאי להמתין עד שהשימוש ב-Material יתרחב כדי לשלב אותו עם סגנונות. מומלץ להשתמש בסגנונות ברכיבים שלכם ככל האפשר.
שכבת הסגנון
במודל המסורתי של כתיבת הודעות, ההתאמה האישית מסתמכת בדרך כלל על שינוי של טוקנים גלובליים (צבעים וטיפוגרפיה) שסופקו על ידי MaterialTheme, או על שינוי של מאפיינים של רכיב ניתן להרכבה במערכת עיצוב, איפה שאפשר.
לפעמים יש מאפיינים בשכבת Material שלא נחשפים דרך מערכות המשנה או הפרמטרים, אבל הם ברירות מחדל שמוגדרות מראש ברכיב עצמו.
ב-Styles API יש שכבת הפשטה חדשה שמשמשת כגשר בין מערכות משנה ורכיבים: Styles.
| שכבה | אחריות | דוגמה |
|---|---|---|
| ערכים של מערכת משנה | ערכים עם שם | val Primary = Color(0xFF34A85E) |
| Atomic Styles | סגנון שמשנה בדיוק מאפיין אחד | val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic |
| סגנונות של רכיבים | הגדרות ספציפיות לרכיב | כפתור עם רקע ראשי ומרווח פנימי של 16dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) } |
| רכיבים | רכיב בממשק המשתמש שמשתמש בסגנון. | Button(style = buttonStyle) { ... } |
סגנונות אטומיים לעומת סגנונות מונוליטיים
באמצעות Styles API, אפשר לפרק סגנון לעיצובים אטומיים נפרדים.
במקום להגדיר סגנונות מורכבים שספציפיים לרכיבים כמו baseButtonStyle, אפשר גם ליצור סגנונות קטנים ופשוטים לשימוש חוזר. הם משמשים כ"אטומים" שלכם.
// Define single-purpose "atomic" styles val paddingAtomic = Style { contentPadding(16.dp) } val roundedCornerShapeAtomic = Style { shape(RoundedCornerShape(8.dp)) } val primaryBackgroundAtomic = Style { background(Color.Blue) } val largeSizeAtomic = Style { size(100.dp, 40.dp) } val interactiveShadowAtomic = Style { hovered { animate { dropShadow( Shadow( offset = DpOffset( 0.dp, 0.dp ), radius = 2.dp, spread = 0.dp, color = Color.Blue, ) ) } } }
קומפוזיציה עם המילה 'אז'
אחת מהתכונות המתקדמות של Styles API החדש היא האופרטור then, שמאפשר למזג כמה אובייקטים מסוג Style. כך אפשר ליצור רכיב באמצעות מחלקות שירות אטומיות.
מסורתי (לא אטומי):
// One large monolithic style val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
שינוי מבנה אטומי:
// Combine atoms to create the final appearance val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic
אימוץ סגנונות במערכת העיצוב
כדאי לשקול את האפשרויות הבאות כשמשלבים סגנונות במערכת העיצוב, בהתאם למיקום של מערכת העיצוב בספקטרום.
מערכת עיצוב בהתאמה אישית עם סגנונות
כדאי לשקול מתי: קיבלתם מדריך מותג מקיף שלא מבוסס על Material Design, ואתם לא מתכננים להשתמש ב-Material Design.
אסטרטגיה: הטמעה של מערכת עיצוב בהתאמה אישית מלאה, וחשיפה של סגנונות כחלק מהעיצוב.
האפשרות הזו היא הנתיב המותאם אישית אם לא משתמשים ב-Material כשפה העיקרית של מערכת העיצוב. אתם מדלגים על MaterialTheme לחלוטין בהגדרות חזותיות וכבר יצרתם עיצוב משלכם בהתאמה אישית. אתם יוצרים CompanyTheme שמשמש כמאגר לסגנונות שלכם.
- איך זה עובד: יוצרים אובייקט
CompanyThemeשמכיל אובייקטיםStyleלכל רכיב במערכת. הרכיבים שלכם (עטיפות סביב לוגיקת Material או הטמעות מותאמות אישית שלBoxאוLayout) משתמשים בסגנונות האלה ישירות, וחושפים פרמטרStyleלמשתמשים במערכת העיצוב שלכם. - שכבת הסגנון: סגנונות הם ההגדרה העיקרית של מערכת העיצוב. טוקנים הם משתנים בעלי שם שמוזנים לסגנונות האלה. כך אפשר לבצע התאמה אישית מתקדמת, כמו הגדרת אנימציות ייחודיות לשינויים במצב (לדוגמה, אנימציה של שינוי גודל וצבע בלחיצה).
אם אתם יוצרים עיצוב מותאם אישית בלי להשתמש ב-Material, ואתם רוצים להשתמש בסגנונות, אתם צריכים להוסיף את רשימת הסגנונות לעיצוב. כך תוכלו לגשת לסגנונות הבסיס מכל מקום בפרויקט.
יוצרים מחלקה ב-
Stylesשמאחסנת את הסגנונות השונים באפליקציה ויוצרים את ברירות המחדל. לדוגמה, באפליקציית Jetsnack – שם המחלקה הואJetsnackStyles:object JetsnackStyles{ val buttonStyle: Style = Style { shape(shapes.medium) background(colors.brand) contentColor(colors.textPrimary) contentPaddingVertical(8.dp) contentPaddingHorizontal(24.dp) textStyle(typography.labelLarge) disabled { animate { background(colors.brandSecondary) } } } val cardStyle: Style = Style { shape(shapes.medium) background(colors.uiBackground) contentColor(colors.textPrimary) } }
צריך לספק את
Stylesכחלק מהעיצוב הכללי, ולחשוף פונקציות של תוסף עזר ב-StyleScopeכדי לגשת למערכות המשנה:@Immutable class JetsnackTheme( val colors: JetsnackColors = LightJetsnackColors, val typography: androidx.compose.material3.Typography = androidx.compose.material3.Typography(), val shapes: Shapes = Shapes() ) { companion object { val colors: JetsnackColors @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.colors val typography: androidx.compose.material3.Typography @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.typography val shapes: Shapes @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.shapes val styles: JetsnackStyles = JetsnackStyles val LocalJetsnackTheme: ProvidableCompositionLocal<JetsnackTheme> get() = LocalJetsnackThemeInstance } } val StyleScope.colors: JetsnackColors get() = LocalJetsnackTheme.currentValue.colors val StyleScope.typography: androidx.compose.material3.Typography get() = LocalJetsnackTheme.currentValue.typography val StyleScope.shapes: Shapes get() = LocalJetsnackTheme.currentValue.shapes internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() } @Composable fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (darkTheme) DarkJetsnackColors else LightJetsnackColors val theme = JetsnackTheme(colors = colors) CompositionLocalProvider( LocalJetsnackTheme provides theme, ) { MaterialTheme( typography = LocalJetsnackTheme.current.typography, shapes = LocalJetsnackTheme.current.shapes, content = content, ) } }
גישה ל-
JetsnackStylesבתוך רכיב ה-Composable:@Composable fun CustomButton(modifier: Modifier, style: Style = Style, text: String) { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } // Apply style to top level container in combination with incoming style from parameter. Box(modifier = modifier .clickable( interactionSource = interactionSource, indication = null, enabled = true, role = Role.Button, onClick = { }, ) .styleable(styleState, JetsnackTheme.styles.buttonStyle, style)) { Text(text) } }
בנוסף להטמעה של ערכת נושא גלובלית, יש אסטרטגיות חלופיות לשילוב של Styles באפליקציות. אפשר להשתמש ב-Styles inline לאתרים ספציפיים שבהם מתקיימות שיחות, או להשתמש בהגדרות סטטיות כשאין צורך ביכולות מלאות של הגדרת נושאים.
אין להחליף את Styles באופן מותנה, אלא אם הסגנון כולו שונה באופן מהותי. מומלץ לגשת לטוקנים דינמיים בתוך הגדרה חזותית במקום לעבור בין אובייקטים שונים של סגנון.