افزودن سایه در «نوشتن»

سایه‌ها ازنظر دیداری میانای کاربر شما را برجسته می‌کنند، تعامل‌پذیری را به کاربران نشان می‌دهند، و بازخورد فوری درباره کنش‌های کاربر ارائه می‌دهند. «ترکیب» چندین روش برای گنجاندن سایه‌ها در برنامه شما ارائه می‌دهد:

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

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

سایه‌ای خاکستری که دور یک شکل مستطیلی سفید افتاده است.
شکل ۱. سایه براساس ارتفاع با Modifier.shadow() ایجاد شده است.

پیاده‌سازی سایه‌های محو

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

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

  • ‫radius: میزان نرمی و پخش شدن محو کردن را تعریف می‌کند.
  • ‫color: رنگ سایه‌رنگ را تعریف می‌کند.
  • offset: هندسه سایه را در امتداد محورهای x و y قرار می‌دهد.
  • spread: گسترش یا انقباض هندسه سایه را کنترل می‌کند.

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

برای پیاده‌سازی سایه افتان پایه، اصلاح‌گر 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 با رنگ سفید پر می‌شود.
    • پس‌زمینه به همان شکل مستطیل گرد سایه برش داده می‌شود.

نتیجه

سایه خاکستری در اطراف شکل مستطیلی سفید.
شکل ۲. سایه‌ای افتان در اطراف شکل کشیده شده است.

پیاده‌سازی سایه‌های داخلی

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

ترتیب هنگام ایجاد سایه‌های داخلی اهمیت دارد. اصلاح‌گر 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
            )
        }
    }
}

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

پویانمایی سایه‌ها در تعامل کاربر

برای اینکه سایه‌ها به تعامل‌های کاربر پاسخ دهند، می‌توانید ویژگی‌های سایه را با میاناهای برنامه‌سازی کاربردی پویانمایی 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 (کوته‌نوشت میان‌گذار) را با مدت ۴۰۰ میلی‌ثانیه و منحنی EaseInOut مشخص می‌کند، که یعنی پویانمایی آهسته شروع می‌شود، در وسط سرعت می‌گیرد، و در پایان آهسته می‌شود.
  • Box را با زنجیره‌ای از توابع اصلاح‌کننده تعریف می‌کند که همه دارایی‌های پویانمایی‌شده را برای ایجاد عنصر دیداری اعمال می‌کند، ازجمله موارد زیر:
    • .clickable(): اصلاح‌گری که Box را تعاملی می‌کند.
    • ‫.dropShadow(): ابتدا دو سایه افتان بیرونی اعمال می‌شود. ویژگی‌های رنگ و آلفای آن‌ها به مقادیر پویانمایی‌شده (blueDropShadow و غیره) پیوند داده شده است و ظاهر برجسته اولیه را ایجاد می‌کند.
    • ‫.innerShadow(): دو سایه داخلی روی پس‌زمینه کشیده می‌شود. خصوصیات آن‌ها به مجموعه دیگر مقادیر پویانمایی‌شده پیوند داده شده است (innerShadowColor1، و غیره) و ظاهر فرورفته را ایجاد می‌کند.

نتیجه

شکل ۴. سایه‌ای که با فشار کاربر پویانمایی می‌شود.

ایجاد سایه‌های تدریجی

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

نتیجه

می‌توانید از قلم‌مو به‌عنوان سایه برای ایجاد گرادیان dropShadow() با پویانمایی «تنفس» استفاده کنید:

شکل ۵. سایه افکنی پویانمایی‌شده با گرادیان.

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

می‌توانید اصلاح‌کننده‌های 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)
    )
}

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

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

سبک نئوبروتالیسم چیدمان‌های بلوکی با کنتراست بالا، رنگ‌های زنده، و حاشیه‌های ضخیم را به‌نمایش می‌گذارد. برای ایجاد این جلوه، از 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
                )
            }
        }
    }
}

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

ایجاد سایه‌های واقعی

سایه‌های واقع‌گرایانه از سایه‌های دنیای فیزیکی تقلید می‌کنند— به‌نظر می‌رسد که توسط یک منبع نور اصلی روشن شده‌اند و هم سایه مستقیم و هم سایه پراکنده‌تر ایجاد می‌کنند. می‌توانید چندین نمونه 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() برای ایجاد جلوه لبه فلزی در اطراف لبه عنصر اعمال می‌شوند.

نتیجه

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

سایه واقع‌گرایانه سفید در اطراف شکل گرد سیاه.
شکل ۸. جلوه سایه واقع‌گرایانه.