Material Design 2 في Compose

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

تنفّذ 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 Design

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

الإصدار الأولي من المحتوى

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

تنفّذ Jetpack Compose ذلك باستخدام LocalContentAlpha. يمكنك تحديد إصدار ألفا للمحتوى في تسلسل هرمي من خلال تقديم قيمة لهذه CompositionLocal. يمكن للدوال المركّبة المُدمَجة استخدام هذه القيمة لتطبيق معالجة ألفا على محتواها. على سبيل المثال، يستخدم Text وIcon تلقائيًا مجموعة LocalContentColor المعدَّلة لاستخدام LocalContentAlpha. يحدّد Material بعض قيم ألفا العادية (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 Design بتطبيق ألوان تمييز محدودة للمظاهر الداكنة من خلال تفضيل استخدام اللون surface على اللون primary في معظم الحالات. تنفّذ الدوال المركّبة في Material Design، مثل TopAppBar وBottomNavigation، هذا السلوك تلقائيًا.

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

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

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

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

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

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

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

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

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

علاوةً على ذلك، عند نقل الشاشات المستندة إلى View إلى 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، يُرجى الاطّلاع على المراجع الإضافية التالية.

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

الفيديوهات