คุณสามารถสร้างแอปได้หลายวิธีโดยใช้สไตล์ สิ่งที่คุณเลือกจะขึ้นอยู่กับตำแหน่งของแอปเมื่อเทียบกับการนำ Material Design ไปใช้ ดังนี้
- ระบบการออกแบบที่กำหนดเองทั้งหมด ไม่ได้ใช้ Material Design
- คำแนะนำ: กำหนดสไตล์คอมโพเนนต์ที่ใช้ค่าจาก ธีม และแสดงพารามิเตอร์สไตล์ในคอมโพเนนต์ระบบการออกแบบ
- ใช้ Material Design
- คำแนะนำ: รอการนำ Material ไปใช้เพื่อผสานรวมกับสไตล์ ใช้สไตล์กับคอมโพเนนต์ของคุณเองเมื่อทำได้
เลเยอร์สไตล์
ในโมเดล Compose แบบดั้งเดิม การปรับแต่งมักจะขึ้นอยู่กับการลบล้างโทเค็นส่วนกลาง (สีและตัวอักษร) ที่ MaterialTheme ให้มา หรือการห่อและลบล้างพร็อพเพอร์ตี้ของคอมโพสได้ของระบบการออกแบบเมื่อทำได้
บางครั้งอาจมีพร็อพเพอร์ตี้ภายในเลเยอร์ Material ที่ไม่ได้แสดงผ่านระบบย่อยหรือพารามิเตอร์ แต่เป็นค่าเริ่มต้นที่ฮาร์ดโค้ดในคอมโพเนนต์เอง
Styles API มีเลเยอร์การแยกย่อยใหม่ที่เป็นสะพานเชื่อมระหว่างระบบย่อยกับคอมโพเนนต์ นั่นคือ สไตล์
| เลเยอร์ | ความรับผิดชอบ | ตัวอย่าง |
|---|---|---|
| ค่าระบบย่อย | ค่าที่มีชื่อ | val Primary = Color(0xFF34A85E) |
| สไตล์ย่อย | สไตล์ที่เปลี่ยนพร็อพเพอร์ตี้เพียงรายการเดียว | val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic |
| สไตล์คอมโพเนนต์ | การกำหนดค่าเฉพาะคอมโพเนนต์ | ปุ่มที่มีพื้นหลังสีหลักและระยะห่างจากขอบ 16dp val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) } |
| คอมโพเนนต์ | องค์ประกอบ UI ที่ใช้งานได้ซึ่งใช้สไตล์ | Button(style = buttonStyle) { ... } |
สไตล์ย่อยเทียบกับสไตล์แบบรวม
Styles API ช่วยให้คุณแยกสไตล์ออกเป็นสไตล์ย่อยๆ ได้
นอกจากจะกำหนดสไตล์ที่ซับซ้อนและเฉพาะคอมโพเนนต์ เช่น baseButtonStyle แล้ว คุณยังสร้างสไตล์ยูทิลิตี้ขนาดเล็กที่มีวัตถุประสงค์เดียวได้ด้วย ซึ่งจะทำหน้าที่เป็น "อะตอม" ของคุณ
// Define single-purpose "atomic" styles val paddingAtomic = Style { contentPadding(16.dp) } val roundedCornerShapeAtomic = Style { shape(RoundedCornerShape(8.dp)) } val primaryBackgroundAtomic = Style { background(Color.Blue) } val largeSizeAtomic = Style { size(100.dp, 40.dp) } val interactiveShadowAtomic = Style { hovered { animate { dropShadow( Shadow( offset = DpOffset( 0.dp, 0.dp ), radius = 2.dp, spread = 0.dp, color = Color.Blue, ) ) } } }
การคอมโพสโดยใช้ "then"
ฟีเจอร์ที่มีประสิทธิภาพอย่างหนึ่งของ Styles API ใหม่คือโอเปอเรเตอร์ then ซึ่งช่วยให้คุณผสานออบเจ็กต์ Style หลายรายการได้ ซึ่งจะช่วยให้คุณสร้างคอมโพเนนต์โดยใช้คลาสยูทิลิตี้ย่อยได้
แบบดั้งเดิม (ไม่ใช่แบบย่อย):
// One large monolithic style val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
การปรับโครงสร้างแบบย่อย:
// Combine atoms to create the final appearance val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic
นำสไตล์ไปใช้ในระบบการออกแบบ
พิจารณาตัวเลือกต่อไปนี้เมื่อนำสไตล์ไปใช้ในระบบการออกแบบ โดยขึ้นอยู่กับตำแหน่งของระบบการออกแบบในสเปกตรัม
ระบบการออกแบบที่กำหนดเองพร้อมสไตล์
พิจารณาเมื่อ: คุณได้รับคู่มือแบรนด์ที่ครอบคลุมซึ่งไม่ได้ อิงตาม Material Design และคุณไม่ได้วางแผนที่จะใช้ Material Design
กลยุทธ์: ใช้ระบบการออกแบบที่กำหนดเองทั้งหมด และแสดงสไตล์เป็นส่วนหนึ่ง ของธีม.
ตัวเลือกนี้เป็นเส้นทางที่กำหนดเองหากคุณไม่ได้ใช้ Material เป็นภาษาหลักของระบบการออกแบบ คุณข้าม MaterialTheme ทั้งหมดสำหรับการกำหนดภาพ และ
สร้างธีมที่กำหนดเองแล้ว คุณสร้าง CompanyTheme ที่ทำหน้าที่เป็นคอนเทนเนอร์สำหรับสไตล์
- วิธีการทำงาน: สร้างออบเจ็กต์
CompanyThemeที่เก็บออบเจ็กต์Styleสำหรับทุกคอมโพเนนต์ในระบบ คอมโพเนนต์ของคุณ (ทั้ง Wrapper รอบตรรกะ Material หรือการใช้งานBoxหรือLayoutที่กำหนดเอง) ใช้สไตล์เหล่านี้โดยตรง และแสดงพารามิเตอร์Styleสำหรับผู้ใช้ระบบการออกแบบ - เลเยอร์สไตล์: สไตล์คือคำจำกัดความหลักของระบบการออกแบบ โทเค็นคือตัวแปรที่มีชื่อซึ่งป้อนลงในสไตล์เหล่านี้ ซึ่งช่วยให้ปรับแต่งได้อย่างละเอียด เช่น การกำหนดภาพเคลื่อนไหวที่ไม่ซ้ำกันสำหรับการเปลี่ยนแปลงสถานะ (เช่น การปรับขนาดและสีเมื่อกด)
หากคุณกำลังสร้างธีมที่กำหนดเองโดยไม่ได้ใช้ Material และ ต้องการนำสไตล์ไปใช้ ให้เพิ่มรายการสไตล์ลงในธีม ซึ่งจะช่วยให้คุณเข้าถึงสไตล์พื้นฐานได้จากทุกที่ในโปรเจ็กต์
สร้างคลาส
Stylesที่จัดเก็บสไตล์ต่างๆ ในแอปพลิเคชันและสร้างค่าเริ่มต้น เช่น ในแอป Jetsnack คลาสนี้ชื่อJetsnackStylesobject JetsnackStyles{ val buttonStyle: Style = Style { shape(shapes.medium) background(colors.brand) contentColor(colors.textPrimary) contentPaddingVertical(8.dp) contentPaddingHorizontal(24.dp) textStyle(typography.labelLarge) disabled { animate { background(colors.brandSecondary) } } } val cardStyle: Style = Style { shape(shapes.medium) background(colors.uiBackground) contentColor(colors.textPrimary) } }
ระบุ
Stylesเป็นส่วนหนึ่งของธีมโดยรวม และแสดงฟังก์ชันส่วนขยายตัวช่วยในStyleScopeเพื่อเข้าถึงระบบย่อย@Immutable class JetsnackTheme( val colors: JetsnackColors = LightJetsnackColors, val typography: androidx.compose.material3.Typography = androidx.compose.material3.Typography(), val shapes: Shapes = Shapes() ) { companion object { val colors: JetsnackColors @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.colors val typography: androidx.compose.material3.Typography @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.typography val shapes: Shapes @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.shapes val styles: JetsnackStyles = JetsnackStyles val LocalJetsnackTheme: ProvidableCompositionLocal<JetsnackTheme> get() = LocalJetsnackThemeInstance } } val StyleScope.colors: JetsnackColors get() = LocalJetsnackTheme.currentValue.colors val StyleScope.typography: androidx.compose.material3.Typography get() = LocalJetsnackTheme.currentValue.typography val StyleScope.shapes: Shapes get() = LocalJetsnackTheme.currentValue.shapes internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() } @Composable fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (darkTheme) DarkJetsnackColors else LightJetsnackColors val theme = JetsnackTheme(colors = colors) CompositionLocalProvider( LocalJetsnackTheme provides theme, ) { MaterialTheme( typography = LocalJetsnackTheme.current.typography, shapes = LocalJetsnackTheme.current.shapes, content = content, ) } }
เข้าถึง
JetsnackStylesภายในคอมโพสได้@Composable fun CustomButton(modifier: Modifier, style: Style = Style, text: String) { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } // Apply style to top level container in combination with incoming style from parameter. Box(modifier = modifier .clickable( interactionSource = interactionSource, indication = null, enabled = true, role = Role.Button, onClick = { }, ) .styleable(styleState, JetsnackTheme.styles.buttonStyle, style)) { Text(text) } }
นอกเหนือจากการนำธีมส่วนกลางไปใช้แล้ว ยังมีกลยุทธ์ทางเลือกอื่นๆ สำหรับการรวม Styles ไว้ในแอปด้วย คุณสามารถใช้ประโยชน์จาก Styles แบบอินไลน์สำหรับตำแหน่งที่เรียกใช้ที่เฉพาะเจาะจง หรือใช้คำจำกัดความแบบคงที่เมื่อไม่จำเป็นต้องใช้ความสามารถในการกำหนดธีมทั้งหมด
ไม่ควรสลับ Styles ตามเงื่อนไข เว้นแต่สไตล์ทั้งหมดจะแตกต่างกันโดยสิ้นเชิง คุณควรเลือกเข้าถึงโทเค็นแบบไดนามิกภายในคำจำกัดความภาพแทนการสลับระหว่างออบเจ็กต์สไตล์ที่แตกต่างกัน