Material Design 2 في Compose

توفّر Jetpack Compose تنفيذًا لتصميم Material Design، وهو نظام تصميم شامل لإنشاء واجهات رقمية. تستند مكوّنات Material Design (مثل الأزرار والبطاقات وأزرار التبديل وما إلى ذلك) إلى Material Theming، وهي طريقة منهجية لتخصيص Material Design بما يعكس العلامة التجارية لمنتجك بشكل أفضل. يحتوي تصميم 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 لاسترداد لون المحتوى المفضّل للخلفية الحالية، في موضع معيّن في التسلسل الهرمي.

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

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

تنفِّذ 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 هذه التراكبات تلقائيًا عند استخدام الألوان الداكنة، وينطبق ذلك أيضًا على أي دالة برمجية أخرى قابلة للإنشاء في Material تستخدم سطحًا:

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

بالنسبة إلى السيناريوهات المخصّصة، استخدِم سمة الإضافة 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. استخدِم مجموعة من الخطوط الطباعية والأنماط للتعبير عن علامتك التجارية.

الشكل

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

تعرض هذه السمة مجموعة متنوعة من أشكال التصميم المتعدد الأبعاد
الشكل 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
    )
}

تراكبات المظاهر

يمكنك تحقيق ما يعادل تراكبات السمات من عناصر Android View في 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. أزرار بقيم تموّج مختلفة يتم توفيرها باستخدام RippleTheme.

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

لمزيد من المعلومات حول استخدام السمات في Material Design في Compose، يُرجى الرجوع إلى المراجع الإضافية التالية.

الدروس التطبيقية حول الترميز

الفيديوهات