سایه ها را در Compose اضافه کنید

سایه ها به صورت بصری رابط کاربری شما را ارتقا می دهند، تعامل را به کاربران نشان می دهند و بازخورد فوری در مورد اقدامات کاربر ارائه می دهند. Compose چندین راه برای ترکیب سایه ها در برنامه شما ارائه می دهد:

  • Modifier.shadow() : یک سایه مبتنی بر ارتفاع در پشت یک composable ایجاد می کند که با دستورالعمل های طراحی متریال مطابقت دارد.
  • Modifier.dropShadow() : یک سایه قابل تنظیم ایجاد می کند که در پشت یک composable ظاهر می شود و آن را برجسته می کند.
  • Modifier.innerShadow() : سایه ای را در داخل مرزهای یک composable ایجاد می کند و باعث می شود که در سطح پشت آن فشرده به نظر برسد.

Modifier.shadow() برای ایجاد سایه‌های اولیه مناسب است، در حالی که اصلاح‌کننده‌های dropShadow و innerShadow کنترل و دقت بیشتری را بر روی رندر سایه ارائه می‌دهند.

این صفحه نحوه پیاده‌سازی هر یک از این اصلاح‌کننده‌ها را شرح می‌دهد، از جمله نحوه متحرک کردن سایه‌ها بر اساس تعامل کاربر و نحوه زنجیره‌سازی اصلاح‌کننده‌های innerShadow() و dropShadow() برای ایجاد سایه‌های گرادیان ، سایه‌های نومورفیک و موارد دیگر.

سایه های اساسی ایجاد کنید

Modifier.shadow() یک سایه اصلی را با پیروی از دستورالعمل های طراحی مواد ایجاد می کند که منبع نور را از بالا شبیه سازی می کند. عمق سایه بر اساس مقدار elevation است و سایه ریخته‌شده به شکل ترکیب‌پذیر بریده می‌شود.

@Composable
fun ElevationBasedShadow() {
    Box(
        modifier = Modifier.aspectRatio(1f).fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Box(
            Modifier
                .size(100.dp, 100.dp)
                .shadow(10.dp, RectangleShape)
                .background(Color.White)
        )
    }
}

یک سایه خاکستری دور یک شکل مستطیل سفید.
شکل 1. یک سایه مبتنی بر ارتفاع ایجاد شده با Modifier.shadow .

سایه های در حال اجرا را اجرا کنید

از اصلاح کننده dropShadow() برای ترسیم یک سایه دقیق در پشت محتوای خود استفاده کنید، که باعث می شود عنصر بالا به نظر برسد.

شما می توانید جنبه های کلیدی زیر را از طریق پارامتر Shadow آن کنترل کنید:

  • radius : نرمی و انتشار تاری شما را مشخص می کند.
  • color : رنگ ته رنگ را مشخص می کند.
  • offset : هندسه سایه را در امتداد محورهای x و y قرار می دهد.
  • spread : انبساط یا انقباض هندسه سایه را کنترل می کند.

علاوه بر این، پارامتر shape ، شکل کلی سایه را مشخص می کند. می‌تواند از هر هندسه‌ای از بسته androidx.compose.foundation.shape و همچنین اشکال Material Expressive استفاده کند.

برای پیاده‌سازی یک drop shadow پایه، اصلاح‌کننده dropShadow() را به زنجیره ترکیب‌پذیر خود اضافه کنید و شعاع، رنگ و گسترش را ارائه دهید. توجه داشته باشید که پس‌زمینه purpleColor که در بالای سایه ظاهر می‌شود، پس از اصلاح‌کننده dropShadow() ترسیم می‌شود:

@Composable
fun SimpleDropShadowUsage() {
    Box(Modifier.fillMaxSize()) {
        Box(
            Modifier
                .width(300.dp)
                .height(300.dp)
                .dropShadow(
                    shape = RoundedCornerShape(20.dp),
                    shadow = Shadow(
                        radius = 10.dp,
                        spread = 6.dp,
                        color = Color(0x40000000),
                        offset = DpOffset(x = 4.dp, 4.dp)
                    )
                )
                .align(Alignment.Center)
                .background(
                    color = Color.White,
                    shape = RoundedCornerShape(20.dp)
                )
        ) {
            Text(
                "Drop Shadow",
                modifier = Modifier.align(Alignment.Center),
                fontSize = 32.sp
            )
        }
    }
}

نکات کلیدی در مورد کد

  • اصلاح کننده dropShadow() در Box داخلی اعمال می شود. سایه دارای ویژگی های زیر است:
    • یک شکل مستطیل گرد ( RoundedCornerShape(20.dp) )
    • شعاع تاری 10.dp که لبه ها را نرم و پراکنده می کند
    • گسترش 6.dp ، که اندازه سایه را افزایش می دهد و آن را بزرگتر از جعبه ای می کند که آن را ایجاد می کند.
    • آلفای 0.5f که سایه را نیمه شفاف می کند
  • پس از تعریف سایه، . background() اصلاح کننده اعمال می شود.
    • Box با رنگ سفید پر شده است.
    • پس زمینه به همان شکل مستطیل گرد مانند سایه بریده می شود.

نتیجه

یک سایه خاکستری در اطراف یک شکل مستطیلی سفید ایجاد شده است.
شکل 2. یک سایه در اطراف شکل کشیده شده است.

سایه های درونی را اجرا کنید

برای ایجاد یک افکت معکوس برای dropShadow ، از Modifier.innerShadow() استفاده کنید، که این توهم را ایجاد می کند که یک عنصر در سطح زیرین فرو رفته یا فشرده شده است.

نظم هنگام ایجاد سایه های درونی مهم است. سایه داخلی بالای محتوا کشیده می شود، بنابراین معمولاً باید کارهای زیر را انجام دهید:

  1. محتوای پس زمینه خود را ترسیم کنید.
  2. برای ایجاد ظاهر مقعر، اصلاح کننده innerShadow() را اعمال کنید.

اگر innerShadow() قبل از پس‌زمینه قرار گیرد، پس‌زمینه روی سایه کشیده می‌شود و آن را کاملاً پنهان می‌کند.

مثال زیر کاربرد innerShadow() را در RoundedCornerShape نشان می دهد:

@Composable
fun SimpleInnerShadowUsage() {
    Box(Modifier.fillMaxSize()) {
        Box(
            Modifier
                .width(300.dp)
                .height(200.dp)
                .align(Alignment.Center)
                // note that the background needs to be defined before defining the inner shadow
                .background(
                    color = Color.White,
                    shape = RoundedCornerShape(20.dp)
                )
                .innerShadow(
                    shape = RoundedCornerShape(20.dp),
                    shadow = Shadow(
                        radius = 10.dp,
                        spread = 2.dp,
                        color = Color(0x40000000),
                        offset = DpOffset(x = 6.dp, 7.dp)
                    )
                )

        ) {
            Text(
                "Inner Shadow",
                modifier = Modifier.align(Alignment.Center),
                fontSize = 32.sp
            )
        }
    }
}

یک سایه داخلی خاکستری در داخل یک شکل مستطیلی سفید.
شکل 3. کاربرد Modifier.innerShadow() روی یک مستطیل گوشه گرد.

متحرک سازی سایه ها در تعامل با کاربر

برای اینکه سایه‌هایتان به تعاملات کاربر پاسخ دهند، می‌توانید ویژگی‌های سایه را با APIهای انیمیشن Compose ادغام کنید. برای مثال زمانی که کاربر دکمه‌ای را فشار می‌دهد، سایه می‌تواند برای ارائه بازخورد بصری آنی تغییر کند.

کد زیر یک افکت "فشرده" با یک سایه ایجاد می کند (توهم اینکه سطح به سمت پایین به صفحه فشار داده می شود):

@Composable
fun AnimatedColoredShadows() {
    SnippetsTheme {
        Box(Modifier.fillMaxSize()) {
            val interactionSource = remember { MutableInteractionSource() }
            val isPressed by interactionSource.collectIsPressedAsState()

            // Create transition with pressed state
            val transition = updateTransition(
                targetState = isPressed,
                label = "button_press_transition"
            )

            fun <T> buttonPressAnimation() = tween<T>(
                durationMillis = 400,
                easing = EaseInOut
            )

            // Animate all properties using the transition
            val shadowAlpha by transition.animateFloat(
                label = "shadow_alpha",
                transitionSpec = { buttonPressAnimation() }
            ) { pressed ->
                if (pressed) 0f else 1f
            }
            // ...

            val blueDropShadow by transition.animateColor(
                label = "shadow_color",
                transitionSpec = { buttonPressAnimation() }
            ) { pressed ->
                if (pressed) Color.Transparent else blueDropShadowColor
            }

            // ...

            Box(
                Modifier
                    .clickable(
                        interactionSource, indication = null
                    ) {
                        // ** ...... **//
                    }
                    .width(300.dp)
                    .height(200.dp)
                    .align(Alignment.Center)
                    .dropShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 10.dp,
                            spread = 0.dp,
                            color = blueDropShadow,
                            offset = DpOffset(x = 0.dp, -(2).dp),
                            alpha = shadowAlpha
                        )
                    )
                    .dropShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 10.dp,
                            spread = 0.dp,
                            color = darkBlueDropShadow,
                            offset = DpOffset(x = 2.dp, 6.dp),
                            alpha = shadowAlpha
                        )
                    )
                    // note that the background needs to be defined before defining the inner shadow
                    .background(
                        color = Color(0xFFFFFFFF),
                        shape = RoundedCornerShape(70.dp)
                    )
                    .innerShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 8.dp,
                            spread = 4.dp,
                            color = innerShadowColor2,
                            offset = DpOffset(x = 4.dp, 0.dp)
                        )
                    )
                    .innerShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 20.dp,
                            spread = 4.dp,
                            color = innerShadowColor1,
                            offset = DpOffset(x = 4.dp, 0.dp),
                            alpha = innerShadowAlpha
                        )
                    )

            ) {
                Text(
                    "Animated Shadows",
                    // ...
                )
            }
        }
    }
}

نکات کلیدی در مورد کد

  • حالت های شروع و پایان را برای پارامترها برای متحرک سازی پس از فشار دادن با transition.animateColor و transition.animateFloat اعلام می کند.
  • از updateTransition استفاده می کند و targetState (targetState = isPressed) را برای تأیید همگام سازی همه انیمیشن ها در اختیار آن قرار می دهد. هر زمان که isPressed تغییر کند، شی انتقال به طور خودکار انیمیشن تمام ویژگی های فرزند را از مقادیر فعلی آنها به مقادیر هدف جدید مدیریت می کند.
  • مشخصات buttonPressAnimation را تعریف می کند که زمان بندی و سهولت انتقال را کنترل می کند. یک tween (مخفف in-between) با مدت زمان 400 میلی ثانیه و یک منحنی EaseInOut را مشخص می‌کند، به این معنی که انیمیشن آهسته شروع می‌شود، از وسط سرعت می‌گیرد و در پایان کاهش می‌یابد.
  • Box ای را با زنجیره ای از توابع اصلاح کننده تعریف می کند که تمام ویژگی های متحرک را برای ایجاد عنصر بصری اعمال می کند، از جمله موارد زیر:
    • . clickable() : اصلاح کننده ای که Box تعاملی می کند.
    • .dropShadow() : ابتدا دو سایه بیرونی اعمال می شود. رنگ و خواص آلفای آنها به مقادیر متحرک ( blueDropShadow و غیره) مرتبط است و ظاهر برجسته اولیه را ایجاد می کند.
    • .innerShadow() : دو سایه داخلی در بالای پس زمینه کشیده می شود. خصوصیات آنها به مجموعه دیگر مقادیر متحرک ( innerShadowColor1 و غیره) مرتبط می شود و ظاهر تورفتگی ایجاد می کند.

نتیجه

شکل 4. یک سایه متحرک با فشار کاربر.

سایه های گرادیان ایجاد کنید

سایه ها به رنگ های ثابت محدود نمی شوند. Shadow API یک Brush را می پذیرد که به شما امکان می دهد سایه های گرادیان ایجاد کنید.

Box(
    modifier = Modifier
        .width(240.dp)
        .height(200.dp)
        .dropShadow(
            shape = RoundedCornerShape(70.dp),
            shadow = Shadow(
                radius = 10.dp,
                spread = animatedSpread.dp,
                brush = Brush.sweepGradient(
                    colors
                ),
                offset = DpOffset(x = 0.dp, y = 0.dp),
                alpha = animatedAlpha
            )
        )
        .clip(RoundedCornerShape(70.dp))
        .background(Color(0xEDFFFFFF)),
    contentAlignment = Alignment.Center
) {
    Text(
        text = breathingText,
        color = Color.Black,
        style = MaterialTheme.typography.bodyLarge
    )
}

نکات کلیدی در مورد کد

  • dropShadow() یک سایه در پشت کادر اضافه می کند.
  • brush = Brush.sweepGradient(colors) سایه را با یک گرادیان رنگ می کند که در میان لیستی از colors از پیش تعریف شده می چرخد ​​و جلوه ای شبیه به رنگین کمان ایجاد می کند.

نتیجه

شما می توانید از یک براش به عنوان سایه برای ایجاد یک gradient dropShadow() با انیمیشن "breathing" استفاده کنید:

شکل 5. یک سایه شیب متحرک.

سایه ها را ترکیب کنید

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

سایه های نئومورفیک ایجاد کنید

سایه های نئومورفیک با ظاهر نرمی مشخص می شوند که به صورت ارگانیک از پس زمینه بیرون می آیند. برای ایجاد سایه های نئومورفیک، موارد زیر را انجام دهید:

  1. از عنصری استفاده کنید که رنگ های مشابهی با پس زمینه آن دارد.
  2. دو سایه کم رنگ و متضاد را اعمال کنید: یک سایه روشن در یک گوشه و یک سایه تیره در گوشه مقابل.

قطعه زیر دو اصلاح کننده dropShadow() را برای ایجاد افکت نئومورفیک لایه بندی می کند:

@Composable
fun NeumorphicRaisedButton(
    shape: RoundedCornerShape = RoundedCornerShape(30.dp)
) {
    val bgColor = Color(0xFFe0e0e0)
    val lightShadow = Color(0xFFFFFFFF)
    val darkShadow = Color(0xFFb1b1b1)
    val upperOffset = -10.dp
    val lowerOffset = 10.dp
    val radius = 15.dp
    val spread = 0.dp
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(bgColor)
            .wrapContentSize(Alignment.Center)
            .size(240.dp)
            .dropShadow(
                shape,
                shadow = Shadow(
                    radius = radius,
                    color = lightShadow,
                    spread = spread,
                    offset = DpOffset(upperOffset, upperOffset)
                ),
            )
            .dropShadow(
                shape,
                shadow = Shadow(
                    radius = radius,
                    color = darkShadow,
                    spread = spread,
                    offset = DpOffset(lowerOffset, lowerOffset)
                ),

            )
            .background(bgColor, shape)
    )
}

یک شکل مستطیلی سفید با جلوه ای نئومورفیک در پس زمینه سفید.
شکل 6. یک اثر سایه نومورفیک.

سایه های نئوبروتالیستی ایجاد کنید

سبک نئوبروتالیست، چیدمان‌هایی با کنتراست بالا، بلوک، رنگ‌های زنده و حاشیه‌های ضخیم را به نمایش می‌گذارد. برای ایجاد این افکت، همانطور که در قطعه زیر نشان داده شده است، از یک dropShadow() با تاری صفر و یک افست مشخص استفاده کنید:

@Composable
fun NeoBrutalShadows() {
    SnippetsTheme {
        val dropShadowColor = Color(0xFF007AFF)
        val borderColor = Color(0xFFFF2D55)
        Box(Modifier.fillMaxSize()) {
            Box(
                Modifier
                    .width(300.dp)
                    .height(200.dp)
                    .align(Alignment.Center)
                    .dropShadow(
                        shape = RoundedCornerShape(0.dp),
                        shadow = Shadow(
                            radius = 0.dp,
                            spread = 0.dp,
                            color = dropShadowColor,
                            offset = DpOffset(x = 8.dp, 8.dp)
                        )
                    )
                    .border(
                        8.dp, borderColor
                    )
                    .background(
                        color = Color.White,
                        shape = RoundedCornerShape(0.dp)
                    )
            ) {
                Text(
                    "Neobrutal Shadows",
                    modifier = Modifier.align(Alignment.Center),
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

حاشیه قرمز دور یک مستطیل سفید با سایه آبی در پس زمینه زرد.
شکل 7. یک اثر سایه نئوبروتالیستی.

سایه های واقع گرایانه ایجاد کنید

سایه های واقع گرایانه از سایه های موجود در دنیای فیزیکی تقلید می کنند - آنها توسط یک منبع نور اولیه روشن می شوند و در نتیجه هم سایه مستقیم و هم سایه پراکنده تر ایجاد می شود. همانطور که در قطعه زیر نشان داده شده است، می‌توانید چندین نمونه dropShadow() و innerShadow() با ویژگی‌های مختلف برای بازسازی افکت‌های سایه واقعی روی هم قرار دهید:

@Composable
fun RealisticShadows() {
    Box(Modifier.fillMaxSize()) {
        val dropShadowColor1 = Color(0xB3000000)
        val dropShadowColor2 = Color(0x66000000)

        val innerShadowColor1 = Color(0xCC000000)
        val innerShadowColor2 = Color(0xFF050505)
        val innerShadowColor3 = Color(0x40FFFFFF)
        val innerShadowColor4 = Color(0x1A050505)
        Box(
            Modifier
                .width(300.dp)
                .height(200.dp)
                .align(Alignment.Center)
                .dropShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 40.dp,
                        spread = 0.dp,
                        color = dropShadowColor1,
                        offset = DpOffset(x = 2.dp, 8.dp)
                    )
                )
                .dropShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 4.dp,
                        spread = 0.dp,
                        color = dropShadowColor2,
                        offset = DpOffset(x = 0.dp, 4.dp)
                    )
                )
                // note that the background needs to be defined before defining the inner shadow
                .background(
                    color = Color.Black,
                    shape = RoundedCornerShape(100.dp)
                )
// //
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 12.dp,
                        spread = 3.dp,
                        color = innerShadowColor1,
                        offset = DpOffset(x = 6.dp, 6.dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 4.dp,
                        spread = 1.dp,
                        color = Color.White,
                        offset = DpOffset(x = 5.dp, 5.dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 12.dp,
                        spread = 5.dp,
                        color = innerShadowColor2,
                        offset = DpOffset(x = (-3).dp, (-12).dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 3.dp,
                        spread = 10.dp,
                        color = innerShadowColor3,
                        offset = DpOffset(x = 0.dp, 0.dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 3.dp,
                        spread = 9.dp,
                        color = innerShadowColor4,
                        offset = DpOffset(x = 1.dp, 1.dp)
                    )
                )

        ) {
            Text(
                "Realistic Shadows",
                modifier = Modifier.align(Alignment.Center),
                fontSize = 24.sp,
                color = Color.White
            )
        }
    }
}

نکات کلیدی در مورد کد

  • دو اصلاح کننده زنجیره ای dropShadow() با ویژگی های متمایز اعمال می شود و به دنبال آن یک اصلاح کننده background اعمال می شود.
  • اصلاح کننده های زنجیره ای innerShadow() برای ایجاد افکت لبه فلزی در اطراف لبه کامپوننت اعمال می شوند.

نتیجه

قطعه کد قبلی موارد زیر را تولید می کند:

یک سایه واقعی سفید در اطراف یک شکل گرد سیاه.
شکل 8. یک اثر سایه واقعی.