تطبيق المظهر باستخدام الأنماط

هناك عدة طرق يمكنك من خلالها إنشاء تطبيقاتك باستخدام الأنماط. يعتمد اختيارك على مكان تطبيقك من حيث اعتماده على التصميم المتعدد الأبعاد:

  1. نظام تصميم مخصّص بالكامل لا يستخدم التصميم المتعدد الأبعاد
    • اقتراح: حدِّد أنماط المكوّنات التي تستخدِم قيمًا من المظهر، واعرض مَعلمات النمط على مكوّنات نظام التصميم.
  2. استخدام التصميم المتعدد الأبعاد
    • اقتراح: انتظِر اعتماد 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) { ... }
مخطّط يوضّح عملية تصميم الخرائط باستخدام الأنماط مع إضافة الطبقة الجديدة
الشكل 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,
                )
            )
        }
    }
}

التركيب باستخدام "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، و أردت اعتماد الأنماط، أضِف قائمة الأنماط إلى المظهر. يتيح لك ذلك الوصول إلى الأنماط الأساسية من أي مكان في مشروعك.

  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 بشكل مشروط إلا إذا كان النمط بالكامل مختلفًا بشكل أساسي. يُفضّل الوصول إلى الرموز الديناميكية داخل تعريف مرئي بدلاً من التبديل بين عناصر نمط مختلفة.