‫Material Design 2 در Compose

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

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

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

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

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

رنگ

رنگ‌ها در «نوشتن» با کلاس 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
}

استفاده از رنگ‌های زمینه

بااستفاده از MaterialTheme.colors می‌توانید Colors ارائه‌شده به عنصر ترکیبی MaterialTheme را بازیابی کنید.

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 از آن رنگ برای تنظیم رنگ‌آمیزی خود استفاده می‌کند.

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

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

نماگرفت از عنوان مقاله که سطوح مختلف تأکید نوشتار را نشان می‌دهد
شکل ۴. برای انتقال بصری سلسله‌مراتب اطلاعات، سطوح مختلفی از تأکید را روی نوشتار اعمال کنید. اولین خط نوشتار عنوان است و مهم‌ترین اطلاعات را دارد، بنابراین از ContentAlpha.high استفاده می‌کند. خط دوم حاوی فراداده‌های کم‌اهمیت‌تر است و بنابراین از ContentAlpha.medium.
استفاده می‌کند.

زمینه تیره

در «ترکیب»، با ارائه مجموعه‌های مختلفی از 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"
)

رونهادهای ارتفاع

در «ماتریال»، سطوح در زمینه‌های تیره با ارتفاع بالاتر رنگ‌های رونهاد ارتفاع دریافت می‌کنند که پس‌زمینه آن‌ها را روشن‌تر می‌کند. هرچه ارتفاع سطح بالاتر باشد (یعنی به منبع نور فرضی نزدیک‌تر باشد)، آن سطح روشن‌تر می‌شود.

ترکیب‌پذیر Surface این رونهادها را به‌طور خودکار هنگام استفاده از رنگ‌های تیره اعمال می‌کند، و هر ترکیب‌پذیر Material دیگری که از سطح استفاده می‌کند نیز همین کار را انجام می‌دهد:

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

رنگ‌های محدود

«متریال» توصیه می‌کند برای زمینه‌های تیره، با ترجیح دادن استفاده از رنگ surface نسبت‌به رنگ primary در اکثر موارد، تأکیدهای رنگی محدود اعمال کنید. عناصر ترکیبی Material مثل TopAppBar و BottomNavigation این رفتار را به‌طور پیش‌فرض پیاده‌سازی می‌کنند.

نماگرفت از زمینه تیره «متریال» که نوار برنامه بالایی را نشان می‌دهد که به‌جای رنگ اصلی از رنگ سطح برای رنگ‌های تأکیدی محدود استفاده می‌کند
شکل ۶. زمینه تیره Material با رنگ‌های محدود. نوار برنامه بالا از رنگ اصلی در زمینه روشن و از رنگ سطح در زمینه تیره استفاده می‌کند.

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

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

نویسه‌نگاری

«دستورات طراحی 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.

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

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

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

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

در این مثال، صفحه جزئیات از 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 (راست)، با مقادیر رنگ و ارتفاع اصلاح‌شده.

امواج

عناصر «طراحی مواد» از موجک برای نشان دادن تعامل با آن‌ها استفاده می‌کنند. اگر از 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

ویدیوها