בחירת עיצוב באמצעות סגנונות

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

  1. מערכת עיצוב בהתאמה אישית מלאה, שלא מבוססת על Material Design
    • המלצה: מגדירים סגנונות של רכיבים שמשתמשים בערכים מהנושא, וחושפים פרמטרים של סגנון ברכיבים של מערכת העיצוב.
  2. שימוש ב-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) { ... }
דיאגרמה שמציגה את העיצוב עם סגנונות עם השכבה החדשה
איור 1. דוגמה לרכיב ולאופן שבו הוא ניגש לסגנונות מתוך נושא.

סגנונות אטומיים לעומת סגנונות מונוליטיים

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

  1. יוצרים מחלקה ב-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)
        }
    }

  2. צריך לספק את 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,
            )
        }
    }

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