הוספת צללים במצב כתיבה

הצללות משפרות את המראה של ממשק המשתמש, מציינות למשתמשים אינטראקטיביות ומספקות משוב מיידי על פעולות המשתמשים. ב-Compose יש כמה דרכים להוסיף צלליות לאפליקציה:

  • ‫Modifier.shadow(): יוצרת צל שמבוסס על גובה מאחורי רכיב Composable שתואם להנחיות של Material Design.
  • ‫Modifier.dropShadow(): יוצר צל שניתן להתאמה אישית שמופיע מאחורי רכיב Composable, כך שהוא נראה מוגבה.
  • ‫Modifier.innerShadow(): יוצר צל בתוך הגבולות של רכיב ה-composable, כך שהוא נראה כאילו הוא נלחץ לתוך המשטח שמאחוריו.

המשתנה Modifier.shadow() מתאים ליצירת צללים בסיסיים, והמשתנים dropShadow() ו-innerShadow() מאפשרים שליטה ודיוק ברמה גבוהה יותר ברינדור הצללים.

בדף הזה מוסבר איך להטמיע כל אחד מהמשתנים האלה, כולל איך להוסיף אנימציה לצללים באינטראקציה של המשתמש ואיך לשרשר את המשתנים innerShadow() ו-dropShadow() כדי ליצור צללים הדרגתיים, צללים נוימורפיים ועוד.

יצירת צללים בסיסיים

Modifier.shadow() יוצר צל בסיסי בהתאם להנחיות של Material Design שמדמה מקור אור עילי. עומק הצל מבוסס על ערך elevation, והצל המוטל נחתך לפי צורת ה-composable.

@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.

כדי להטמיע הטלת צללית בסיסית, מוסיפים את המשתנה dropShadow() לשרשרת ה-composable, ומציינים את הרדיוס, הצבע והפיזור. שימו לב שהרקע של 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(), שיוצר אשליה שרכיב מסוים שקוע או נלחץ לתוך המשטח שמתחתיו.

הסדר חשוב כשיוצרים צללים פנימיים. המשתנה 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. אנימציה של צל שמופיעה כשהמשתמש לוחץ על הכפתור.

יצירת צללים הדרגתיים

הצללות לא מוגבלות לצבעים אחידים. ה-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 מוגדרים מראש, ויוצר אפקט דמוי קשת.

תוצאה

אתם יכולים להשתמש במברשת כצל כדי ליצור שיפוע dropShadow() עם אנימציה של "נשימה":

איור 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. אפקט צל ריאליסטי.