Styles API มีแนวทางที่ประกาศได้และมีประสิทธิภาพในการจัดการการเปลี่ยนแปลง UI ระหว่างสถานะการโต้ตอบ เช่น hovered, focused และ pressed API นี้ช่วยให้คุณลดโค้ด Boilerplate ที่มักจะต้องใช้เมื่อใช้ตัวแก้ไขได้อย่างมาก
StyleState ทำหน้าที่เป็นอินเทอร์เฟซแบบอ่านอย่างเดียวที่เสถียร ซึ่งติดตามสถานะที่ใช้งานอยู่ขององค์ประกอบ (เช่น สถานะที่เปิดใช้ กด หรือโฟกัส) เพื่อช่วยในการจัดรูปแบบแบบโต้ตอบ ภายใน StyleScope คุณสามารถเข้าถึงสถานะนี้ผ่านพร็อพเพอร์ตี้ state เพื่อใช้ตรรกะแบบมีเงื่อนไขในการกำหนดสไตล์ได้โดยตรง
การโต้ตอบตามสถานะ: วางเมาส์เหนือ โฟกัส กด เลือก เปิดใช้ สลับ
สไตล์มาพร้อมกับการรองรับการโต้ตอบทั่วไปในตัว ดังนี้
- กดแล้ว
- วางเมาส์เหนือ
- เลือกแล้ว
- เปิดใช้อยู่
- สลับแล้ว
นอกจากนี้ยังรองรับสถานะที่กำหนดเองได้ด้วย ดูข้อมูลเพิ่มเติมได้ที่ส่วนการจัดรูปแบบสถานะที่กำหนดเองด้วย Custom State Styling with 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) }) } ) }
คอมโพสได้ที่กำหนดเองด้วย Modifier.styleable
เมื่อสร้างคอมโพเนนต์ styleable ของคุณเอง คุณต้องเชื่อมต่อ interactionSource กับ styleState จากนั้นส่งสถานะนี้ไปยัง Modifier.styleable เพื่อใช้งาน
พิจารณาสถานการณ์ที่ระบบการออกแบบของคุณมี GradientButton คุณอาจต้องการสร้าง LoginButton ที่รับค่ามาจาก GradientButton แต่เปลี่ยนสีระหว่างการโต้ตอบ เช่น เมื่อมีการกด
- หากต้องการเปิดใช้การอัปเดตสไตล์
interactionSourceให้ใส่interactionSourceเป็นพารามิเตอร์ภายในคอมโพสได้ ใช้พารามิเตอร์ที่ให้มา หรือหากไม่มี ให้เริ่มต้น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 = rememberUpdatedStyleState(interactionSource) { it.isEnabled = enabled } Row( modifier = modifier .clickable( onClick = onClick, enabled = enabled, interactionSource = interactionSource, indication = null, ) .styleable(styleState, baseGradientButtonStyle then style), content = content, ) }
ตอนนี้คุณสามารถใช้สถานะ interactionSource เพื่อขับเคลื่อนการแก้ไขสไตล์ด้วยตัวเลือก 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 เพื่อเพิ่มภาพเคลื่อนไหวระหว่างสถานะต่างๆ โดยอัตโนมัติ ซึ่งคล้ายกับ API animate*AsState ตัวอย่างต่อไปนี้จะเปลี่ยน 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)) { } }
API animate รับ animationSpec เพื่อเปลี่ยนระยะเวลาหรือรูปร่างของเส้นโค้งภาพเคลื่อนไหว ตัวอย่างต่อไปนี้จะเปลี่ยนขนาดของกล่องแบบเคลื่อนไหวด้วยสเปค spring
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 แตกต่างกันไปตามสถานะการเล่นของเพลเยอร์ ทำตามขั้นตอนต่อไปนี้เพื่อสร้างและใช้สถานะที่กำหนดเอง
- กำหนดคีย์ที่กำหนดเอง
- สร้างส่วนขยาย
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 ในคอมโพสได้และตั้งค่า 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) }