هناك عدة طرق يمكنك من خلالها إنشاء تطبيقاتك باستخدام الأنماط. يعتمد اختيارك على مكان تطبيقك من حيث اعتماده على التصميم المتعدد الأبعاد:
- نظام تصميم مخصّص بالكامل لا يستخدم التصميم المتعدد الأبعاد
- اقتراح: حدِّد أنماط المكوّنات التي تستخدِم قيمًا من المظهر، واعرض مَعلمات النمط على مكوّنات نظام التصميم.
- استخدام التصميم المتعدد الأبعاد
- اقتراح: انتظِر اعتماد Material لدمجه مع الأنماط. استخدِم الأنماط على مكوّناتك الخاصة حيثما أمكن ذلك.
طبقة النمط
في نموذج Compose التقليدي، يعتمد التخصيص غالبًا بشكل كبير على إلغاء الرموز العامة (الألوان وأسلوب الخط) التي يوفّرها MaterialTheme، أو على تضمين خصائص دالة مركّبة لنظام التصميم وإلغائها حيثما أمكن ذلك.
في بعض الأحيان، تكون هناك خصائص ضمن طبقة Material لا يتم عرضها من خلال الأنظمة الفرعية أو المَعلمات، ولكنها تكون قيمًا تلقائية ثابتة على المكوّن نفسه.
باستخدام Styles API، هناك طبقة تجريد جديدة تمثّل جسرًا بين الأنظمة الفرعية والمكوّنات: الأنماط.
| الطبقة | المسؤولية | مثال |
|---|---|---|
| قيم النظام الفرعي | القيم المُسمّاة | val Primary = Color(0xFF34A85E) |
| الأنماط الذرية | النمط الذي يُجري تغييرًا واحدًا فقط على إحدى الخصائص | val largeSizeAtomic = Style { size(100.dp, 40.dp) } |
| أنماط المكوّنات | الإعدادات الخاصة بالمكوّن | زر بخلفية أساسية ومساحة ترك مسافة داخلية تبلغ 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, ) ) } } }
التركيب باستخدام "then"
إحدى الميزات القوية في 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 كلغة نظام التصميم الرئيسية. يمكنك تجاوز 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 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 بشكل مشروط إلا إذا كان النمط بالكامل مختلفًا بشكل أساسي. يُفضّل الوصول إلى الرموز الديناميكية داخل تعريف مرئي بدلاً من التبديل بين عناصر نمط مختلفة.