Material Design 2 في Compose

توفّر Jetpack Compose تنفيذًا للتصميم المتعدد الأبعاد، وهو نظام تصميم شامل لإنشاء واجهات رقمية. تستند مكوّنات التصميم المتعدد الأبعاد (مثل الأزرار والبطاقات ومفاتيح التبديل وما إلى ذلك) إلى تخصيص التصميم المتعدد الأبعاد، وهي طريقة منهجية لتخصيص التصميم المتعدد الأبعاد بما يعكس العلامة التجارية لمنتجك بشكل أفضل. يتضمّن "تصميم Material" سمات اللون وأسلوب الخط والشكل. عند تخصيص هذه السمات، تنعكس التغييرات تلقائيًا في المكوّنات التي تستخدمها لإنشاء تطبيقك.

تنفِّذ Jetpack Compose هذه المفاهيم باستخدام الدالة المركّبة MaterialTheme:

MaterialTheme(
    colors = // ...
    typography = // ...
    shapes = // ...
) {
    // app content
}

اضبط المَعلمات التي تمرّرها إلى MaterialTheme لتحديد مظهر تطبيقك.

لقطتا شاشة متناقضتان تستخدم اللقطة الأولى نمط MaterialTheme التلقائي، بينما تستخدم اللقطة الثانية نمطًا معدَّلاً.
الشكل 1. تعرض لقطة الشاشة الأولى تطبيقًا لا يضبط `MaterialTheme`، وبالتالي يستخدم النمط التلقائي. تعرض لقطة الشاشة الثانية تطبيقًا يمرِّر مَعلمات إلى `MaterialTheme` لتخصيص التصميم.

اللون

يتم تصميم الألوان في Compose باستخدام الفئة Color، وهي فئة تحتوي على بيانات.

val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)

يمكنك تنظيم هذه الألوان بالطريقة التي تريدها (كثوابت ذات مستوى أعلى أو ضمن سينغلتون أو محدّدة مضمن)، ولكننا ننصحك بشدة بتحديد الألوان في المظهر واستردادها من هناك. تتيح هذه الطريقة استخدام المظهر الداكن والمظاهر المتداخلة.

مثال على لوحة ألوان المظهر
الشكل 2. نظام ألوان Material

توفّر Compose الفئة Colors لنمذجة نظام ألوان Material. توفّر Colors دوال إنشاء لإنشاء مجموعات من الألوان الفاتحة أو الداكنة:

private val Yellow200 = Color(0xffffeb46)
private val Blue200 = Color(0xff91a4fc)
// ...

private val DarkColors = darkColors(
    primary = Yellow200,
    secondary = Blue200,
    // ...
)
private val LightColors = lightColors(
    primary = Yellow500,
    primaryVariant = Yellow400,
    secondary = Blue700,
    // ...
)

بعد تحديد Colors، يمكنك تمريرها إلى MaterialTheme:

MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors
) {
    // app content
}

استخدام ألوان المظهر

يمكنك استرداد قيمة Colors التي تم توفيرها للدالة المركّبة MaterialTheme باستخدام MaterialTheme.colors.

Text(
    text = "Hello theming",
    color = MaterialTheme.colors.primary
)

لون السطح والمحتوى

تقبل العديد من المكوّنات زوجًا من اللون ولون المحتوى:

Surface(
    color = MaterialTheme.colors.surface,
    contentColor = contentColorFor(color),
    // ...
) { /* ... */ }

TopAppBar(
    backgroundColor = MaterialTheme.colors.primarySurface,
    contentColor = contentColorFor(backgroundColor),
    // ...
) { /* ... */ }

يتيح لك ذلك ليس فقط ضبط لون عنصر قابل للإنشاء، بل أيضًا توفير لون تلقائي للمحتوى والعناصر القابلة للإنشاء التي يتضمّنها. تستخدم العديد من العناصر القابلة للإنشاء لون المحتوى هذا تلقائيًا. على سبيل المثال، يستند لون Text إلى لون محتوى العنصر الأصل، ويستخدم Icon هذا اللون لضبط درجة اللون.

مثالان للبانر نفسه بألوان مختلفة
الشكل 3. يؤدي ضبط ألوان خلفية مختلفة إلى إنتاج ألوان مختلفة للنص والرموز.

تستردّ الطريقة contentColorFor() لون "على" المناسب لأي ألوان تصميم. على سبيل المثال، إذا ضبطت لون خلفية primary على Surface، سيتم استخدام هذه الدالة لضبط onPrimary كلون المحتوى. في حال ضبط لون خلفية غير لون التصميم، يجب أيضًا تحديد لون مناسب للمحتوى. استخدِم LocalContentColor لاسترداد لون المحتوى المفضّل للخلفية الحالية، في موضع معيّن في التدرّج الهرمي.

قناة ألفا للمحتوى

في كثير من الأحيان، تحتاج إلى تغيير مستوى التركيز على المحتوى لتوضيح أهميته وتوفير تسلسل هرمي مرئي. تنصح اقتراحات إمكانية قراءة النص في التصميم المتعدد الأبعاد باستخدام مستويات مختلفة من التعتيم لنقل مستويات مختلفة من الأهمية.

تنفِّذ Jetpack Compose ذلك باستخدام LocalContentAlpha. يمكنك تحديد قيمة ألفا للمحتوى في التسلسل الهرمي من خلال توفير قيمة لهذا CompositionLocal. يمكن أن تستخدم العناصر القابلة للإنشاء المتداخلة هذه القيمة لتطبيق معالجة ألفا على محتواها. على سبيل المثال، تستخدم Text وIcon بشكل تلقائي تركيبة LocalContentColor المعدَّلة لاستخدام LocalContentAlpha. تحدّد المادة بعض قيم ألفا العادية (high وmedium وdisabled) التي يتم تصميمها باستخدام العنصر ContentAlpha.

// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
    Text(
        // ...
    )
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
    Icon(
        // ...
    )
    Text(
        // ...
    )
}

لمزيد من المعلومات حول CompositionLocal، يمكنك الاطّلاع على البيانات ذات النطاق المحلي باستخدام CompositionLocal.

لقطة شاشة لعنوان مقالة تعرض مستويات مختلفة من التركيز على النص
الشكل 4. طبِّق مستويات مختلفة من التركيز على النص لتوضيح التسلسل الهرمي للمعلومات بشكل مرئي. السطر الأول من النص هو العنوان ويتضمّن المعلومات الأكثر أهمية، وبالتالي يستخدم ContentAlpha.high. يحتوي السطر الثاني على بيانات وصفية أقل أهمية، وبالتالي يستخدم ContentAlpha.medium.

المظهر الداكن

في Compose، يمكنك تنفيذ المظهرين الفاتح والداكن من خلال توفير مجموعات مختلفة من Colors للدالة البرمجية القابلة للإنشاء MaterialTheme:

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        /*...*/
        content = content
    )
}

في هذا المثال، يتم تضمين MaterialTheme في دالة مركّبة خاصة به، تقبل مَعلمة تحدّد ما إذا كان سيتم استخدام مظهر داكن أم لا. في هذه الحالة، تحصل الدالة على القيمة التلقائية لـ darkTheme من خلال طلب البحث عن إعدادات مظهر الجهاز.

يمكنك استخدام رمز برمجي مثل هذا للتحقّق مما إذا كانت Colors الحالية فاتحة أو داكنة:

val isLightTheme = MaterialTheme.colors.isLight
Icon(
    painterResource(
        id = if (isLightTheme) {
            R.drawable.ic_sun_24
        } else {
            R.drawable.ic_moon_24
        }
    ),
    contentDescription = "Theme"
)

تراكبات الارتفاع

في Material، تتلقّى مساحات العرض في المظاهر الداكنة ذات الارتفاعات الأعلى تراكبات الارتفاع التي تخفّف من درجة لون الخلفية. كلما زاد ارتفاع السطح (أي اقترب من مصدر ضوء ضمني)، أصبح السطح أكثر سطوعًا.

تطبِّق الدالة المركّبة Surface هذه التراكبات تلقائيًا عند استخدام الألوان الداكنة، وينطبق ذلك أيضًا على أي دالة مركّبة أخرى في التصميم المتعدد الأبعاد تستخدم مساحة عرض:

Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }

لقطة شاشة لتطبيق تعرض الألوان المختلفة بشكل طفيف المستخدَمة للعناصر في مستويات ارتفاع مختلفة
الشكل 5. تستخدم البطاقات وشريط التنقّل السفلي اللون surface كخلفية. بما أنّ البطاقات وشريط التنقّل السفلي يقعان على مستويات ارتفاع مختلفة عن الخلفية، فإنّ ألوانهما تختلف قليلاً، فاللون في البطاقات أفتح من الخلفية، واللون في شريط التنقّل السفلي أفتح من البطاقات.

بالنسبة إلى السيناريوهات المخصّصة التي لا تتضمّن Surface، استخدِم LocalElevationOverlay، وهو CompositionLocal يحتوي على ElevationOverlay الذي تستخدمه مكوّنات Surface:

// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
    color, elevation
)

لإيقاف التراكبات التي تظهر عند الارتفاع، قدِّم null عند النقطة المحدّدة في التسلسل الهرمي القابل للإنشاء:

MyTheme {
    CompositionLocalProvider(LocalElevationOverlay provides null) {
        // Content without elevation overlays
    }
}

لمسات لونية محدودة

تنصح Material باستخدام لمسات ألوان محدودة للمظاهر الداكنة، وذلك من خلال تفضيل استخدام اللون surface على اللون primary في معظم الحالات. تتضمّن عناصر Material القابلة للإنشاء، مثل TopAppBar وBottomNavigation، هذا السلوك تلقائيًا.

لقطة شاشة لمظهر داكن في Material، تعرض شريط التطبيق العلوي باستخدام لون الخلفية بدلاً من اللون الأساسي لتمييز الألوان بشكل محدود
الشكل 6. مظهر داكن من Material مع لمسات ألوان محدودة يستخدم شريط التطبيق العلوي اللون الأساسي في المظهر الفاتح، ولون الخلفية في المظهر الداكن.

بالنسبة إلى السيناريوهات المخصّصة، استخدِم سمة الإضافة primarySurface:

Surface(
    // Switches between primary in light theme and surface in dark theme
    color = MaterialTheme.colors.primarySurface,
    /*...*/
) { /*...*/ }

أسلوب الخط

تحدّد Material نظام أنواع، ما يشجّعك على استخدام عدد صغير من الأنماط التي تحمل أسماء دلالية.

مثال على عدة خطوط طباعية مختلفة بأنماط متنوعة
الشكل 7. نظام أنواع المواد

تنفّذ Compose نظام الأنواع باستخدام الفئات Typography وTextStyle والمتعلّقة بالخطوط. يوفّر الدالة الإنشائية Typography قيمًا تلقائية لكل نمط، لذا يمكنك حذف أي نمط لا تريد تخصيصه:

val raleway = FontFamily(
    Font(R.font.raleway_regular),
    Font(R.font.raleway_medium, FontWeight.W500),
    Font(R.font.raleway_semibold, FontWeight.SemiBold)
)

val myTypography = Typography(
    h1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = myTypography, /*...*/) {
    /*...*/
}

إذا كنت تريد استخدام خط الكتابة نفسه في كل مكان، حدِّد المَعلمة defaultFontFamily واحذف fontFamily أي عناصر TextStyle:

val typography = Typography(defaultFontFamily = raleway)
MaterialTheme(typography = typography, /*...*/) {
    /*...*/
}

استخدام أنماط النص

يتم الوصول إلى عناصر TextStyle باستخدام MaterialTheme.typography. استرجِع عناصر TextStyle على النحو التالي:

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

لقطة شاشة تعرض مزيجًا من خطوط مختلفة لأغراض مختلفة
الشكل 8. استخدِم مجموعة من الخطوط الطباعية والأنماط للتعبير عن علامتك التجارية.

الشكل

يحدّد التصميم المتعدد الأبعاد نظام أشكال يتيح لك تحديد أشكال للمكوّنات الكبيرة والمتوسطة والصغيرة.

تعرض هذه السمة مجموعة متنوعة من الأشكال بتصميم متعدد الأبعاد
الشكل 9. نظام الأشكال في Material Design

تنفِّذ Compose نظام الأشكال باستخدام الفئة Shapes، ما يتيح لك تحديد CornerBasedShape لكل فئة من فئات الحجم:

val shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomEnd = 0.dp,
        bottomStart = 16.dp
    )
)

MaterialTheme(shapes = shapes, /*...*/) {
    /*...*/
}

تستخدم العديد من المكوّنات هذه الأشكال تلقائيًا. على سبيل المثال، تكون القيمة التلقائية لكل من Button وTextField وFloatingActionButton هي small، وتكون القيمة التلقائية لـ AlertDialog هي medium، وتكون القيمة التلقائية لـ ModalDrawer هي large. يمكنك الاطّلاع على مرجع مخطط الأشكال للحصول على عملية الربط الكاملة.

استخدام الأشكال

يتم الوصول إلى عناصر Shape باستخدام MaterialTheme.shapes. يمكنك استرداد عناصر Shape باستخدام رمز مشابه لما يلي:

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

لقطة شاشة لتطبيق يستخدم أشكال Material لعرض حالة أحد العناصر
الشكل 10. استخدِموا الأشكال للتعبير عن العلامة التجارية أو الحالة.

الأنماط التلقائية

لا يوجد مفهوم مكافئ في Compose للأنماط التلقائية من Android Views. يمكنك توفير وظائف مشابهة من خلال إنشاء دوال overload قابلة للإنشاء خاصة بك تعمل على تضمين مكوّنات Material. على سبيل المثال، لإنشاء نمط زر، يمكنك تضمين الزر في دالة مركّبة خاصة بك، مع ضبط المَعلمات التي تريد تغييرها أو تحتاج إليها مباشرةً، وعرض المَعلمات الأخرى كمَعلمات للدالة المركّبة التي تحتوي على الزر.

@Composable
fun MyButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

نصوص مركّبة على المظهر

يمكنك تحقيق التأثير نفسه الذي توفّره تراكبات السمات من خلال Views في Android باستخدام Compose عن طريق تضمين عناصر MaterialTheme القابلة للإنشاء. بما أنّ MaterialTheme يضبط الألوان والطباعة والأشكال تلقائيًا على قيمة المظهر الحالي، تحتفظ جميع المَعلمات الأخرى بقيمها التلقائية عندما يضبط المظهر إحدى هذه المَعلمات فقط.

بالإضافة إلى ذلك، عند نقل الشاشات المستندة إلى طرق العرض إلى Compose، احرص على الانتباه إلى استخدامات السمة android:theme. من المحتمل أنّك بحاجة إلى MaterialTheme في هذا الجزء من شجرة واجهة مستخدم Compose.

في هذا المثال، تستخدم شاشة التفاصيل PinkTheme لمعظم الشاشة، ثم BlueTheme للقسم ذي الصلة. توضّح لقطة الشاشة والرمز التاليان هذا المفهوم:

لقطة شاشة لتطبيق يعرض مظاهر متداخلة، مع مظهر وردي للشاشة الرئيسية ومظهر أزرق لقسم ذي صلة
الشكل 11. المواضيع المتداخلة

@Composable
fun DetailsScreen(/* ... */) {
    PinkTheme {
        // other content
        RelatedSection()
    }
}

@Composable
fun RelatedSection(/* ... */) {
    BlueTheme {
        // content
    }
}

حالات المكوّنات

يمكن أن تكون مكونات Material التي يمكن التفاعل معها (النقر عليها أو تبديلها أو غير ذلك) في حالات مرئية مختلفة. تشمل الحالات مفعَّل وغير مفعَّل ومضغوط وما إلى ذلك.

تحتوي العناصر القابلة للإنشاء غالبًا على المَعلمة enabled. يؤدي ضبطها على false إلى منع التفاعل، ويغيّر خصائص مثل اللون والارتفاع لنقل حالة المكوّن بصريًا.

لقطة شاشة لزرَّين: أحدهما مفعَّل والآخر غير مفعَّل، تعرض حالتهما المرئية المختلفة
الشكل 12. زر يتضمّن enabled = true (على اليمين) وenabled = false (على اليسار)

في معظم الحالات، يمكنك الاعتماد على القيم التلقائية لسمات مثل اللون والارتفاع. إذا كنت بحاجة إلى ضبط القيم المستخدَمة في حالات مختلفة، تتوفّر فئات ودوال ملائمة. إليك مثالاً على زر:

Button(
    onClick = { /* ... */ },
    enabled = true,
    // Custom colors for different states
    colors = ButtonDefaults.buttonColors(
        backgroundColor = MaterialTheme.colors.secondary,
        disabledBackgroundColor = MaterialTheme.colors.onBackground
            .copy(alpha = 0.2f)
            .compositeOver(MaterialTheme.colors.background)
        // Also contentColor and disabledContentColor
    ),
    // Custom elevation for different states
    elevation = ButtonDefaults.elevation(
        defaultElevation = 8.dp,
        disabledElevation = 2.dp,
        // Also pressedElevation
    )
) { /* ... */ }

لقطة شاشة لزرّين تم تعديل اللون والمسقط الرأسي لهما في حالتَي التفعيل وعدم التفعيل
الشكل 13. زرّ يحتوي على enabled = true (يسار) وenabled = false (يمين)، مع قيم معدَّلة للّون والارتفاع

أمواج

تستخدم مكونات Material التموّجات للإشارة إلى أنّه يتم التفاعل معها. إذا كنت تستخدم MaterialTheme في التسلسل الهرمي، سيتم استخدام Ripple كـ Indication تلقائي داخل أدوات التعديل، مثل clickable وindication.

في معظم الحالات، يمكنك الاعتماد على Ripple التلقائي. إذا كنت بحاجة إلى ضبط مظهرها، يمكنك استخدام RippleTheme لتغيير خصائص مثل اللون والشفافية.

يمكنك توسيع RippleTheme والاستفادة من دالتَي الأدوات المساعدة defaultRippleColor وdefaultRippleAlpha. يمكنك بعد ذلك تقديم مظهر التموج المخصّص في التسلسل الهرمي باستخدام LocalRippleTheme:

@Composable
fun MyApp() {
    MaterialTheme {
        CompositionLocalProvider(
            LocalRippleTheme provides SecondaryRippleTheme
        ) {
            // App content
        }
    }
}

@Immutable
private object SecondaryRippleTheme : RippleTheme {
    @Composable
    override fun defaultColor() = RippleTheme.defaultRippleColor(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )

    @Composable
    override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )
}

صورة GIF متحرّكة تعرض أزرارًا ذات تأثيرات تموّج مختلفة عند النقر عليها
الشكل 14. أزرار تتضمّن قيم تموّج مختلفة يتم توفيرها باستخدام RippleConfiguration.

مزيد من المعلومات

لمزيد من المعلومات حول تخصيص التصميم المتعدد الأبعاد في Compose، يُرجى الرجوع إلى المراجع الإضافية التالية.

اختبارات الرموز

الفيديوهات