هناك عدة طرق يمكنك من خلالها إنشاء تطبيقاتك باستخدام الأنماط. يعتمد اختيارك على مكان تطبيقك من حيث اعتماده على التصميم المتعدد الأبعاد:
- نظام تصميم مخصّص بالكامل لا يستخدم التصميم المتعدد الأبعاد
- اقتراح: حدِّد أنماط المكوّنات التي تستخدِم القيم من المظهر، واعرض مَعلمات النمط في مكوّنات نظام التصميم.
- استخدام التصميم المتعدد الأبعاد
- اقتراح: انتظِر اعتماد Material للدمج مع الأنماط. استخدِم الأنماط في مكوّناتك الخاصة حيثما أمكن.
طبقة النمط
في نموذج Compose التقليدي، يعتمد التخصيص غالبًا بشكل كبير على إلغاء الرموز العامة (الألوان وأسلوب الخط) التي يوفّرها MaterialTheme، أو على تضمين خصائص دالة مركّبة لنظام تصميم وإلغائها حيثما أمكن.
في بعض الأحيان، تكون هناك خصائص ضمن طبقة Material لا يتم عرضها من خلال الأنظمة الفرعية أو المَعلمات، ولكنها تكون قيمًا تلقائية ثابتة في المكوّن نفسه.
باستخدام Styles API، هناك طبقة تجريد جديدة تمثّل جسرًا بين الأنظمة الفرعية والمكوّنات: الأنماط.
| الطبقة | المسؤولية | مثال |
|---|---|---|
| قيم النظام الفرعي | القيم المُسمّاة | val Primary = Color(0xFF34A85E) |
| الأنماط الذرية | النمط الذي يُجري تغييرًا واحدًا فقط في إحدى الخصائص | val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic |
| أنماط المكوّنات | الإعدادات الخاصة بالمكوّن | زر بخلفية أساسية و16 وحدة بكسل كمسافة بادئة للمحتوى: 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 بشكل مشروط إلا إذا كان النمط بالكامل مختلفًا بشكل أساسي. يُفضّل الوصول إلى الرموز الديناميكية داخل تعريف مرئي بدلاً من التبديل بين عناصر نمط مختلفة.