Styles API มีแนวทางที่ประกาศและปรับปรุงแล้วในการจัดการการเปลี่ยนแปลง UI
ในระหว่างสถานะการโต้ตอบ เช่น hovered, focused และ pressed API นี้ช่วยลดโค้ดมาตรฐานที่มักจะต้องใช้เมื่อใช้ตัวแก้ไขได้อย่างมาก
StyleState ทำหน้าที่เป็นอินเทอร์เฟซแบบอ่านอย่างเดียวที่เสถียร
ซึ่งติดตามสถานะที่ใช้งานอยู่ขององค์ประกอบ (เช่น สถานะที่เปิดใช้
กด หรือโฟกัส) เพื่ออำนวยความสะดวกในการจัดรูปแบบแบบรีแอ็กทีฟ ภายใน StyleScope คุณสามารถเข้าถึงได้ผ่านพร็อพเพอร์ตี้ state เพื่อใช้ตรรกะแบบมีเงื่อนไขในการกำหนดสไตล์โดยตรง
การโต้ตอบตามสถานะ: วางเมาส์ โฟกัส กด เลือก เปิดใช้ สลับ
สไตล์มาพร้อมกับการรองรับการโต้ตอบทั่วไปในตัว ดังนี้
- กดแล้ว
- วางเมาส์
- เลือกแล้ว
- เปิดใช้อยู่
- สลับแล้ว
นอกจากนี้ ยังรองรับสถานะที่กำหนดเองได้ด้วย ดูข้อมูลเพิ่มเติมได้ที่ส่วนการจัดรูปแบบสถานะที่กำหนดเองด้วย StyleState
จัดการสถานะการโต้ตอบด้วยพารามิเตอร์สไตล์
ตัวอย่างต่อไปนี้แสดงการแก้ไข background และ borderColor
เพื่อตอบสนองต่อสถานะการโต้ตอบ โดยเฉพาะการเปลี่ยนเป็นสีม่วงเมื่อวางเมาส์เหนือองค์ประกอบ
และสีน้ำเงินเมื่อโฟกัส:
@Preview @Composable private fun OpenButton() { BaseButton( style = outlinedButtonStyle then { background(Color.White) hovered { background(lightPurple) border(2.dp, lightPurple) } focused { background(lightBlue) } }, onClick = { }, content = { BaseText("Open in Studio", style = { contentColor(Color.Black) fontSize(26.sp) textAlign(TextAlign.Center) }) } ) }
นอกจากนี้ คุณยังสร้างคำจำกัดความสถานะที่ซ้อนกันได้ด้วย เช่น คุณสามารถกำหนด รูปแบบที่เฉพาะเจาะจงสำหรับเมื่อมีการกดปุ่มและวางเมาส์เหนือปุ่ม พร้อมกันได้ดังนี้
@Composable private fun OpenButton_CombinedStates() { BaseButton( style = outlinedButtonStyle then { background(Color.White) hovered { // light purple background(lightPurple) pressed { // When running on a device that can hover, whilst hovering and then pressing the button this would be invoked background(lightOrange) } } pressed { // when running on a device without a mouse attached, this would be invoked as you wouldn't be in a hovered state only background(lightRed) } focused { background(lightBlue) } }, onClick = { }, content = { BaseText("Open in Studio", style = { contentColor(Color.Black) fontSize(26.sp) textAlign(TextAlign.Center) }) } ) }
Composable ที่กำหนดเองด้วย Modifier.styleable
เมื่อสร้างstyleableคอมโพเนนต์ของคุณเอง คุณต้องเชื่อมต่อ interactionSource กับ styleState จากนั้นส่งสถานะนี้ไปยัง
Modifier.styleable เพื่อใช้งาน
พิจารณาสถานการณ์ที่ระบบการออกแบบมี GradientButton คุณอาจต้องการสร้าง LoginButton ที่รับค่าจาก GradientButton แต่เปลี่ยนสีระหว่างการโต้ตอบ เช่น เมื่อมีการกด
- หากต้องการเปิดใช้การอัปเดตสไตล์
interactionSourceให้ใส่interactionSourceเป็นพารามิเตอร์ภายใน Composable ใช้พารามิเตอร์ที่ระบุ หรือหากไม่มี ให้เริ่มต้นMutableInteractionSourceใหม่ - เริ่มต้น
styleStateโดยระบุinteractionSourceตรวจสอบว่าสถานะที่เปิดใช้ของstyleStateสะท้อนค่าของพารามิเตอร์ที่เปิดใช้ที่ระบุ - กำหนดตัวแก้ไข
interactionSourceให้กับตัวแก้ไขfocusableและclickableสุดท้าย ให้ใช้styleStateกับพารามิเตอร์styleableของตัวแก้ไข
@Composable private fun GradientButton( onClick: () -> Unit, modifier: Modifier = Modifier, style: Style = Style, enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, content: @Composable RowScope.() -> Unit, ) { val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } styleState.isEnabled = enabled Row( modifier = modifier .clickable( onClick = onClick, enabled = enabled, interactionSource = interactionSource, indication = null, ) .styleable(styleState, baseGradientButtonStyle then style), content = content, ) }
ตอนนี้คุณใช้interactionSource state เพื่อขับเคลื่อนการแก้ไขสไตล์ด้วย
ตัวเลือก pressed, focused และ hovered ภายในบล็อกสไตล์ได้แล้ว
@Preview @Composable fun LoginButton() { val loginButtonStyle = Style { pressed { background( Brush.linearGradient( listOf(Color.Magenta, Color.Red) ) ) } } GradientButton(onClick = { // Login logic }, style = loginButtonStyle) { BaseText("Login") } }
interactionSourceเปลี่ยนสไตล์ภาพเคลื่อนไหว
การเปลี่ยนแปลงสถานะของสไตล์มาพร้อมกับการรองรับภาพเคลื่อนไหวในตัว คุณสามารถรวมพร็อพเพอร์ตี้ใหม่
ไว้ในบล็อกการเปลี่ยนสถานะใดก็ได้ด้วย animate เพื่อเพิ่ม
ภาพเคลื่อนไหวระหว่างสถานะต่างๆ โดยอัตโนมัติ ซึ่งคล้ายกับ animate*AsState
API ตัวอย่างต่อไปนี้จะเคลื่อนไหว borderColor จากสีดำเป็นสีน้ำเงินเมื่อ
สถานะเปลี่ยนเป็นโฟกัส
val animatingStyle = Style { externalPadding(48.dp) border(3.dp, Color.Black) background(Color.White) size(100.dp) pressed { animate { borderColor(Color.Magenta) background(Color(0xFFB39DDB)) } } } @Preview @Composable private fun AnimatingStyleChanges() { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } Box(modifier = Modifier .clickable( interactionSource, enabled = true, indication = null, onClick = { } ) .styleable(styleState, animatingStyle)) { } }
animate API ยอมรับ animationSpec เพื่อเปลี่ยนระยะเวลาหรือรูปร่างของ
เส้นโค้งภาพเคลื่อนไหว ตัวอย่างต่อไปนี้จะเคลื่อนไหวขนาดของกล่องด้วยspring spec
val animatingStyleSpec = Style { externalPadding(48.dp) border(3.dp, Color.Black) background(Color.White) size(100.dp) transformOrigin(TransformOrigin.Center) pressed { animate { borderColor(Color.Magenta) background(Color(0xFFB39DDB)) } animate(spring(dampingRatio = Spring.DampingRatioMediumBouncy)) { scale(1.2f) } } } @Preview(showBackground = true) @Composable fun AnimatingStyleChangesSpec() { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } Box(modifier = Modifier .clickable( interactionSource, enabled = true, indication = null, onClick = { } ) .styleable(styleState, animatingStyleSpec)) }
การจัดรูปแบบสถานะที่กำหนดเองด้วย StyleState
คุณอาจมีสไตล์ที่แตกต่างกันซึ่ง
ได้รับการสนับสนุนโดยสถานะที่กำหนดเอง ทั้งนี้ขึ้นอยู่กับกรณีการใช้งานที่ประกอบได้ ตัวอย่างเช่น หากคุณมีแอปสื่อ คุณอาจต้องการ
ใช้สไตล์ที่แตกต่างกันสำหรับปุ่มใน MediaPlayer Composable
ทั้งนี้ขึ้นอยู่กับสถานะการเล่นของเพลเยอร์ ทำตามขั้นตอนต่อไปนี้เพื่อสร้างและ
ใช้สถานะที่กำหนดเอง
- กำหนดคีย์ที่กำหนดเอง
- สร้างส่วนขยาย
StyleState - ลิงก์ไปยังสถานะที่กำหนดเอง
กำหนดคีย์ที่กำหนดเอง
หากต้องการสร้างสไตล์ตามสถานะที่กำหนดเอง ให้สร้าง
StyleStateKeyก่อน แล้วส่งค่าสถานะเริ่มต้น เมื่อแอปเปิดขึ้น มีเดียเพลเยอร์จะอยู่ในสถานะ Stopped จึงต้องเริ่มต้นใช้งานด้วยวิธีต่อไปนี้
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey(PlayerState.Stopped)
สร้างฟังก์ชันส่วนขยาย StyleState
กำหนดฟังก์ชันส่วนขยายใน StyleState เพื่อค้นหา playState ปัจจุบัน
จากนั้นสร้างฟังก์ชันส่วนขยายใน StyleScope โดยส่งสถานะที่กำหนดเองใน playStateKey, Lambda ที่มีสถานะเฉพาะ และสไตล์
// Extension Function on MutableStyleState to query and set the current playState var MutableStyleState.playerState get() = this[playerStateKey] set(value) { this[playerStateKey] = value } fun StyleScope.playerPlaying(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) } fun StyleScope.playerPaused(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) }
ลิงก์ไปยังสถานะที่กำหนดเอง
กำหนด styleState ใน Composable และตั้งค่า styleState.playState
ให้เท่ากับสถานะขาเข้า ส่ง styleState ไปยังฟังก์ชัน styleable ใน
ตัวแก้ไข
@Composable fun MediaPlayer( url: String, modifier: Modifier = Modifier, style: Style = Style, state: PlayerState = remember { PlayerState.Paused } ) { // Hoist style state, set playstate as a parameter, val styleState = remember { MutableStyleState(null) } // Set equal to incoming state to link the two together styleState.playerState = state Box( modifier = modifier.styleable(styleState, style)) { ///.. } }
ภายใน Lambda ของ style คุณสามารถใช้การจัดรูปแบบตามสถานะสำหรับสถานะที่กำหนดเองได้
โดยใช้ฟังก์ชันส่วนขยายที่กำหนดไว้ก่อนหน้านี้
@Composable fun StyleStateKeySample() { // Using the extension function to change the border color to green while playing val style = Style { borderColor(Color.Gray) playerPlaying { animate { borderColor(Color.Green) } } playerPaused { animate { borderColor(Color.Blue) } } } val styleState = remember { MutableStyleState(null) } styleState[playerStateKey] = PlayerState.Playing // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. MediaPlayer(url = "https://example.com/media/video", style = style, state = PlayerState.Stopped) }
โค้ดต่อไปนี้คือข้อมูลโค้ดแบบเต็มสำหรับตัวอย่างนี้
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey<PlayerState>(PlayerState.Stopped) var MutableStyleState.playerState get() = this[playerStateKey] set(value) { this[playerStateKey] = value } fun StyleScope.playerPlaying(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) } fun StyleScope.playerPaused(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) } @Composable fun MediaPlayer( url: String, modifier: Modifier = Modifier, style: Style = Style, state: PlayerState = remember { PlayerState.Paused } ) { // Hoist style state, set playstate as a parameter, val styleState = remember { MutableStyleState(null) } // Set equal to incoming state to link the two together styleState.playerState = state Box( modifier = modifier.styleable(styleState, Style { size(100.dp) border(2.dp, Color.Red) }, style, )) { ///.. } } @Composable fun StyleStateKeySample() { // Using the extension function to change the border color to green while playing val style = Style { borderColor(Color.Gray) playerPlaying { animate { borderColor(Color.Green) } } playerPaused { animate { borderColor(Color.Blue) } } } val styleState = remember { MutableStyleState(null) } styleState[playerStateKey] = PlayerState.Playing // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. MediaPlayer(url = "https://example.com/media/video", style = style, state = PlayerState.Stopped) }