‫Material Design 2 در Compose

Jetpack Compose پیاده‌سازی طراحی مواد را ارائه می‌دهد، یک سیستم طراحی جامع برای ایجاد میاناهای دیجیتال. مؤلفه‌های طراحی مواد (دکمه‌ها، کارت‌ها، کلیدها، و غیره) براساس موضوع‌دهی Material ساخته شده‌اند که روشی نظام‌مند برای سفارشی‌سازی «طراحی مواد» است تا نمانام محصولتان را بهتر منعکس کند. یک Material Theme شامل مشخصه‌های رنگ، حروف‌چینی، و شکل است. وقتی این مشخصه‌ها را سفارشی‌سازی می‌کنید، تغییرات شما به‌طور خودکار در مؤلفه‌هایی که برای ساختن برنامه‌تان استفاده می‌کنید منعکس می‌شود.

Jetpack Compose این مفاهیم را با MaterialTheme ترکیب‌کردنی پیاده‌سازی می‌کند:

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

پارامترهایی را که به MaterialTheme ارسال می‌کنید پیکربندی کنید تا برنامه شما زمینه‌دار شود.

دو نماگرفت متضاد. اولی از سبک پیش‌فرض MaterialTheme استفاده می‌کند، نماگرفت دوم از سبک اصلاح‌شده استفاده می‌کند.
شکل ۱. نماگرفت اول برنامه‌ای را نشان می‌دهد که `MaterialTheme` را پیکربندی نمی‌کند، بنابراین از سبک پیش‌فرض استفاده می‌کند. نماگرفت دوم برنامه‌ای را نشان می‌دهد که پارامترها را به `MaterialTheme` ارسال می‌کند تا سبک را سفارشی‌سازی کند.

رنگ

رنگ‌ها در Compose با کلاس Color مدل‌سازی می‌شوند که یک کلاس نگهدارنده داده است.

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

اگرچه می‌توانید این موارد را به هر شکلی که دوست دارید سازمان‌دهی کنید (به‌عنوان ثابت‌های سطح بالا، در یک تک‌قلو، یا تعریف‌شده به‌صورت درون‌خطی)، ما به‌شدت توصیه می‌کنیم رنگ‌ها را در زمینه خود مشخص کنید و رنگ‌ها را از آنجا بازیابی کنید. این رویکرد امکان پشتیبانی از زمینه تیره و زمینه‌های تودرتو را فراهم می‌کند.

نمونه پالت رنگ زمینه
شکل ۲. سیستم رنگ 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 composable را بااستفاده از 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),
    // ...
) { /* ... */ }

این به شما امکان می‌دهد نه‌تنها رنگ یک composable را تنظیم کنید، بلکه یک رنگ پیش‌فرض برای محتوا، composableهای موجود در آن، ارائه دهید. بسیاری از ترکیب‌شدنی‌ها به‌طور پیش‌فرض از این رنگ محتوا استفاده می‌کنند. برای مثال، Text رنگ خود را براساس رنگ محتوای والد خود قرار می‌دهد و Icon از آن رنگ برای تنظیم ته‌رنگ خود استفاده می‌کند.

دو نمونه از یک برنما با رنگ‌های متفاوت
شکل ۳.‏ تنظیم رنگ‌های پس‌زمینه مختلف باعث ایجاد رنگ‌های مختلف برای نوشتار و نماد می‌شود.

روش contentColorFor() رنگ «روشن» مناسب را برای هر رنگ زمینه‌ای بازیابی می‌کند. برای مثال، اگر رنگ پس‌زمینه primary را روی Surface تنظیم کنید، از این تابع برای تنظیم onPrimary به‌عنوان رنگ محتوا استفاده می‌کند. اگر رنگ پس‌زمینه غیرزمینه تنظیم می‌کنید، باید رنگ محتوای مناسبی نیز مشخص کنید. از LocalContentColor برای بازیابی رنگ محتوای ترجیحی برای پس‌زمینه فعلی در موقعیت مشخصی در سلسله‌مراتب استفاده کنید.

آلفای محتوا

اغلب می‌خواهید میزان تأکید بر محتوا را تغییر دهید تا اهمیت آن را منتقل کنید و سلسله‌مراتب دیداری ارائه دهید. توصیه‌های خوانایی نوشتار Material Design توصیه می‌کنند که از سطوح مختلفی از کدر بودن برای انتقال سطوح مختلف اهمیت استفاده شود.

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 مراجعه کنید.

نماگرفت عنوان مقاله، که سطوح مختلف تأکید نوشتار را نشان می‌دهد
شکل ۴. برای برقراری ارتباط بصری با سلسله‌مراتب اطلاعات، سطوح مختلفی از تأکید را روی نوشتار اعمال کنید. خط اول نوشتار عنوان است و مهم‌ترین اطلاعات را دارد، بنابراین از 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 composable applies these overlays automatically when using dark colors, and so does any other Material composable which uses a surface:

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

نماگرفتی از برنامه که رنگ‌های کمی متفاوت را که برای عناصر در سطوح مختلف ارتفاع استفاده شده است نشان می‌دهد
شکل ۵. کارت‌ها و پیمایش پایین هر دو از رنگ 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 که نوار برنامه بالایی را نشان می‌دهد که از رنگ سطح به‌جای رنگ اصلی برای رنگ‌های تأکیدی محدود استفاده می‌کند
شکل ۶.‏ زمینه تیره Material با رنگ‌های تأکیدی محدود. نوار برنامه بالا از رنگ اصلی در زمینه روشن و رنگ سطح در زمینه تیره استفاده می‌کند.

برای سناریوهای سفارشی، از ویژگی افزونه primarySurface استفاده کنید:

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

نویسه‌نگاری

Material سیستم نوع را تعریف می‌کند و شما را تشویق می‌کند از تعداد کمی سبک با نام معنایی استفاده کنید.

نمونه‌ای از چند طرح حروف مختلف در سبک‌های گوناگون
شکل ۷. سیستم نوع 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
)

نماگرفتی که ترکیبی از طرح‌های حروف مختلف را برای اهداف مختلف نشان می‌دهد
شکل ۸. از مجموعه‌ای از طرح‌های حروف و سبک‌ها برای بیان نمانامتان استفاده کنید.

شکل‌ها

Material سیستم شکل را تعریف می‌کند و به شما امکان می‌دهد شکل‌هایی برای عناصر بزرگ، متوسط، و کوچک تعریف کنید.

انواع شکل‌های «طراحی مواد» را نشان می‌دهد
شکل ۹. سیستم شکل 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, /*...*/
) {
    /*...*/
}

نماگرفت برنامه‌ای که از شکل‌های Material برای انتقال وضعیت عنصر استفاده می‌کند
شکل ۱۰. از شکل‌ها برای بیان نمانام یا وضعیت استفاده کنید.

سبک‌های پیش‌فرض

مفهوم معادلی در 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
    )
}

رونهادهای زمینه

می‌توانید با تودرتو کردن composableهای MaterialTheme، به معادل هم‌پوشانی‌های زمینه‌ای از Android Views در Compose دست یابید. چون MaterialTheme رنگ‌ها، تایپوگرافی، و شکل‌ها را به‌طور پیش‌فرض روی مقدار زمینه فعلی تنظیم می‌کند، وقتی زمینه‌ای فقط یکی از این پارامترها را تنظیم می‌کند، همه پارامترهای دیگر مقادیر پیش‌فرض خود را حفظ می‌کنند.

علاوه‌براین، هنگام انتقال صفحه‌های مبتنی بر View به Compose، مراقب استفاده از مشخصه android:theme باشید. احتمالاً به MaterialTheme جدیدی در آن بخش از درخت Compose UI نیاز دارید.

در این مثال، صفحه جزئیات از PinkTheme برای بیشتر صفحه، و سپس از BlueTheme برای بخش مربوطه استفاده می‌کند. نماگرفت و کد زیر این مفهوم را نشان می‌دهد:

نماگرفتی از برنامه‌ای که زمینه‌های تودرتو را نشان می‌دهد، با زمینه صورتی برای صفحه اصلی و زمینه آبی برای بخش مرتبط
شکل ۱۱. موضوعات تودرتو.

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

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

وضعیت عناصر

اجزای Material که می‌توان با آن‌ها تعامل داشت (کلیک کرد، تغییر وضعیت داد، و غیره) می‌توانند در حالت‌های دیداری مختلفی باشند. حالت‌ها شامل فعال، غیرفعال، فشرده‌شده، و غیره است.

ترکیب‌شدنی‌ها اغلب پارامتر enabled دارند. تنظیم آن روی false از تعامل جلوگیری می‌کند و ویژگی‌هایی مانند رنگ و ارتفاع را تغییر می‌دهد تا وضعیت مؤلفه را به‌صورت بصری منتقل کند.

نماگرفتی از دو دکمه: یکی فعال و دیگری غیرفعال، که وضعیت‌های دیداری متفاوت آن‌ها را نشان می‌دهد
شکل ۱۲. دکمه با 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
    )
) { /* ... */ }

نماگرفتی از دو دکمه با رنگ و ارتفاع تنظیم‌شده برای حالت‌های فعال و غیرفعال
شکل ۱۳. دکمه با 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
    )
}

گیف پویانمایی‌شده که دکمه‌هایی با جلوه‌های موج‌دار مختلف را هنگام ضربه زدن نشان می‌دهد
شکل ۱۴. دکمه‌هایی با مقادیر موج‌دار مختلف که بااستفاده از RippleConfiguration ارائه شده‌اند.

بیشتر بدانید

برای کسب اطلاعات بیشتر درباره «موضوع‌دهی Material» در Compose، به منابع تکمیلی زیر مراجعه کنید.

Codelabs

ویدیوها