Material Design 2 في Compose

توفّر Jetpack Compose تنفيذًا للإصدار 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 لتصميم نموذج نظام الألوان المتعدد الأبعاد. توفر Colors دوال الإنشاء لإنشاء مجموعات من الألوان light أو الداكنة:

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() لون "on" المناسب لأي ألوان مظهر. على سبيل المثال، عند ضبط لون خلفية 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"، يمكنك الاطّلاع على البيانات ذات النطاق المحلي باستخدام دليل IndustryLocal.

لقطة شاشة لعنوان مقالة يظهر فيها مستويات مختلفة من
التوكيد على النص

الشكل 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 في معظم الحالات. تنفِّذ المواد القابلة للإنشاء مثل TopAppBar وBottomNavigation هذا السلوك تلقائيًا.

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

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

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

أسلوب الخط

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

مثال على العديد من الخطوط الطباعية المختلفة بأنماط مختلفة

الشكل 7. نظام نوع Material.

تنفِّذ ميزة 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 parameter واحذف fontFamily لأي من عناصر TextStyle:

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

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

يمكن الوصول إلى TextStyle من خلال MaterialTheme.typography. يمكنك استرداد TextStyle كما يلي:

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

لقطة شاشة تعرض مزيجًا من الخطوط الطباعية المختلفة لأغراض مختلفة

الشكل 8. استخدم مجموعة مختارة من الخطوط الطباعية والأنماط للتعبير عن علامتك التجارية.

شكل

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

تعرض مجموعة متنوعة من أشكال Material Design

الشكل 9. نظام شكل Material.

ينفِّذ 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 القيمة التلقائية هي "صغير" وAlertDialog الضبط التلقائي على "متوسط" وModalDrawer القيم التلقائية على "كبير"، يمكنك الاطّلاع على مرجع مخطّط الشكل للربط الكامل.

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

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

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

لقطة شاشة لتطبيق يستخدم الأشكال المتعددة الأبعاد لتحديد حالة العنصر

الشكل 10. استخدم الأشكال للتعبير عن العلامة التجارية أو الحالة.

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

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

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

في نموذج البوم، تستخدم شاشة التفاصيل PinkTheme لمعظم أجزاء الشاشة، ثم BlueTheme للقسم ذي الصلة. انظر لقطة الشاشة والرمز أدناه.

الشكل 11. موضوعات مدمجة في عينة Owl.

@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
    )
}

نص_alt

الشكل 14. أزرار ذات قيم تمويج مختلفة مقدَّمة من خلال RippleTheme.

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

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

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

الفيديوهات الطويلة