قالب‌بندی با استایل‌ها

روش‌های مختلفی برای ساخت برنامه‌هایتان با استفاده از استایل‌ها وجود دارد. انتخاب شما بستگی به این دارد که برنامه شما در رابطه با پذیرش طراحی متریال در چه جایگاهی قرار دارد:

  1. سیستم طراحی کاملاً سفارشی، بدون استفاده از طراحی متریال
    • توصیه : سبک‌های کامپوننتی را تعریف کنید که از مقادیر قالب استفاده می‌کنند و پارامترهای سبک را روی اجزای سیستم طراحی نمایش دهند.
  2. استفاده از طراحی متریال
    • توصیه : برای ادغام با Styles منتظر پذیرش متریال باشید. در صورت امکان از Styles روی کامپوننت‌های خودتان استفاده کنید.

لایه استایل

در مدل سنتی Compose، سفارشی‌سازی اغلب به شدت به بازنویسی توکن‌های سراسری (رنگ‌ها و تایپوگرافی) ارائه شده توسط MaterialTheme یا در صورت امکان، به پوشش‌دهی و بازنویسی ویژگی‌های یک سیستم طراحی قابل ترکیب وابسته است. گاهی اوقات، ویژگی‌هایی در لایه Material وجود دارند که از طریق زیرسیستم‌ها یا پارامترها در معرض دید قرار نمی‌گیرند، بلکه به صورت پیش‌فرض در خود کامپوننت کدگذاری شده‌اند.

با API استایلز، یک لایه جدید از انتزاع وجود دارد که پلی بین زیرسیستم‌ها و کامپوننت‌ها است: استایلز .

لایه مسئولیت مثال
مقادیر زیرسیستم مقادیر نامگذاری شده val Primary = Color(0xFF34A85E)
سبک‌های اتمی سبکی که دقیقاً یک ویژگی را تغییر می‌دهد val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic
سبک‌های کامپوننت پیکربندی‌های مختص به هر قطعه یک دکمه با پس‌زمینه اصلی و فاصله کانونی ۱۶dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
قطعات عنصر رابط کاربری کاربردی که از یک Style استفاده می‌کند. Button(style = buttonStyle) { ... }
نموداری که قالب‌بندی با استایل‌ها را با معرفی لایه جدید نشان می‌دهد
شکل ۱. نمونه‌ای از یک کامپوننت و نحوه‌ی دسترسی آن به استایل‌ها از یک قالب.

سبک‌های اتمی در مقابل یکپارچه

با استفاده از 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,
                )
            )
        }
    }
}

ترکیب با استفاده از «then»

یکی از ویژگی‌های قدرتمند API جدید Styles، عملگر 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 به عنوان زبان اصلی سیستم طراحی خود استفاده نمی‌کنید. شما MaterialTheme به طور کامل برای تعاریف بصری کنار می‌گذارید و از قبل تم سفارشی خود را ایجاد کرده‌اید. شما یک CompanyTheme می‌سازید که به عنوان ظرفی برای Styles شما عمل می‌کند.

  • نحوه کار : یک شیء CompanyTheme ایجاد کنید که اشیاء Style را برای هر کامپوننت در سیستم شما نگه می‌دارد. کامپوننت‌های شما (چه wrapperهای اطراف منطق Material یا پیاده‌سازی‌های سفارشی Box یا Layout ) مستقیماً از این استایل‌ها استفاده می‌کنند و یک پارامتر Style را برای مصرف‌کنندگان سیستم طراحی شما نمایش می‌دهند.
  • لایه سبک : سبک‌ها تعریف اصلی سیستم طراحی شما هستند. توکن‌ها متغیرهای نامگذاری شده‌ای هستند که به این سبک‌ها وارد می‌شوند. این امر امکان سفارشی‌سازی عمیق، مانند تعریف انیمیشن‌های منحصر به فرد برای تغییرات حالت (به عنوان مثال، متحرک‌سازی مقیاس و رنگ هنگام فشار دادن) را فراهم می‌کند.

اگر در حال ساخت قالب سفارشی خود بدون استفاده از متریال هستید و می‌خواهید سبک‌ها را به آن اضافه کنید، لیست سبک‌های خود را به قالب خود اضافه کنید. این به شما امکان می‌دهد از هر کجای پروژه به سبک‌های پایه خود دسترسی داشته باشید.

  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
    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 به صورت درون‌خطی برای سایت‌های فراخوانی خاص استفاده کنید یا وقتی قابلیت‌های کامل تم‌بندی غیرضروری هستند، از تعاریف استاتیک استفاده کنید. Styles نباید به صورت مشروط تعویض شوند، مگر اینکه کل استایل اساساً متفاوت باشد. شما باید ترجیح دهید به جای جابجایی بین اشیاء استایل مجزا، به توکن‌های پویا در داخل یک تعریف بصری دسترسی داشته باشید.