เงาช่วยยกระดับ UI ของคุณในเชิงภาพ บ่งบอกถึงการโต้ตอบกับผู้ใช้ และให้การตอบสนองต่อการดำเนินการของผู้ใช้ในทันที Compose มีวิธีต่างๆ ในการรวมเงาเข้ากับแอป ดังนี้
Modifier.shadow(): สร้างเงาตามระดับความสูงด้านหลัง Composable ที่เป็นไปตามหลักเกณฑ์ของ Material DesignModifier.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) ) } }
Modifier.shadow()ใช้เงาตกกระทบ
ใช้ตัวแก้ไข dropShadow() เพื่อวาดเงาที่แม่นยำด้านหลังเนื้อหา ซึ่งจะทำให้องค์ประกอบดูนูนขึ้นมา
คุณควบคุมลักษณะสำคัญต่อไปนี้ได้ผ่านพารามิเตอร์ Shadow
radius: กำหนดความนุ่มนวลและการกระจายของภาพเบลอcolor: กำหนดสีของโทนสีoffset: จัดตำแหน่งเรขาคณิตของเงาตามแกน x และ yspread: ควบคุมการขยายหรือหดตัวของเรขาคณิตของเงา
นอกจากนี้ พารามิเตอร์ 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จะเต็มไปด้วยสีขาว- พื้นหลังจะถูกตัดให้เป็นรูปสี่เหลี่ยมผืนผ้าขอบมนแบบเดียวกับเงา
ผลลัพธ์
ใช้เงาด้านใน
หากต้องการสร้างเอฟเฟกต์ตรงกันข้ามกับ dropShadow() ให้ใช้ Modifier.innerShadow() ซึ่งจะสร้างภาพลวงตาว่าองค์ประกอบถูกกดหรือฝังลงในพื้นผิวด้านล่าง
ลำดับมีความสำคัญเมื่อสร้างเงาด้านใน ตัวแก้ไข innerShadow() จะวาดด้านบนของเนื้อหา โดยปกติแล้ว คุณจะต้องทำตามขั้นตอนต่อไปนี้เพื่อให้แน่ใจว่าเงาจะปรากฏขึ้น
- วาดเนื้อหาพื้นหลัง
- ใช้ตัวปรับแต่ง
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() กับสี่เหลี่ยมผืนผ้ามุมมนแสดงภาพเคลื่อนไหวของเงาเมื่อผู้ใช้โต้ตอบ
หากต้องการให้เงาตอบสนองต่อการโต้ตอบของผู้ใช้ คุณสามารถผสานรวมพร็อพเพอร์ตี้เงากับ 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(): ระบบจะใช้เงาตกกระทบด้านนอก 2 รายการก่อน สีและพร็อพเพอร์ตี้อัลฟ่าจะลิงก์กับค่าที่เคลื่อนไหว (blueDropShadowฯลฯ) และสร้างลักษณะที่ยกขึ้นในตอนแรก.innerShadow(): มีการวาดเงาด้านใน 2 เงาที่ด้านบนของพื้นหลัง พร็อพเพอร์ตี้ขององค์ประกอบเหล่านี้จะลิงก์กับชุดค่าภาพเคลื่อนไหวอีกชุดหนึ่ง (innerShadowColor1ฯลฯ) และสร้างลักษณะที่เยื้องเข้าไป
- .
ผลลัพธ์
สร้างเงาแบบไล่ระดับสี
เงาไม่ได้จำกัดอยู่แค่สีทึบเท่านั้น 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ที่กำหนดไว้ล่วงหน้า ซึ่งจะสร้างเอฟเฟกต์คล้ายสีรุ้ง
ผลลัพธ์
คุณสามารถใช้แปรงเป็นเงาเพื่อสร้างการไล่ระดับสี dropShadow() ด้วยภาพเคลื่อนไหวแบบ "หายใจ" ได้ดังนี้
รวมเงา
คุณสามารถรวมและวางเลเยอร์ตัวแก้ไข dropShadow() และ innerShadow() เพื่อสร้างเอฟเฟกต์ที่หลากหลายได้ ส่วนต่อไปนี้จะแสดงวิธีสร้างเงาแบบนิวอมอร์ฟิก นีโอบรูทัลลิสต์ และสมจริงด้วยเทคนิคนี้
สร้างเงาแบบ Neumorphic
เงาแบบนิวอมอร์ฟิกมีลักษณะที่นุ่มนวลซึ่งปรากฏขึ้นอย่างเป็นธรรมชาติจากพื้นหลัง หากต้องการสร้างเงาแบบนิวอมอร์ฟิก ให้ทำดังนี้
- ใช้องค์ประกอบที่มีสีเดียวกับพื้นหลัง
- ใช้เงาตกกระทบจางๆ 2 เงาที่ตรงข้ามกัน โดยให้เงาสว่างอยู่ที่มุมหนึ่ง และเงาเข้มอยู่ที่มุมตรงข้าม
ข้อมูลโค้ดต่อไปนี้จะวางเลเยอร์ตัวแก้ไข dropShadow() 2 รายการเพื่อสร้างเอฟเฟกต์นิวอมอร์ฟิก
@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()แบบต่อเชื่อม 2 รายการที่มีพร็อพเพอร์ตี้ต่างกัน ตามด้วยตัวแก้ไขbackground() - ระบบจะใช้ตัวแก้ไข
innerShadow()แบบเชื่อมโยงเพื่อสร้างเอฟเฟกต์ขอบโลหะรอบขอบของคอมโพเนนต์
ผลลัพธ์
ข้อมูลโค้ดก่อนหน้าจะสร้างผลลัพธ์ต่อไปนี้