Styles API cung cấp một phương pháp khai báo và tinh giản để quản lý các thay đổi về giao diện người dùng trong các trạng thái tương tác như hovered, focused và pressed. Với API này, bạn có thể giảm đáng kể mã nguyên mẫu thường cần thiết khi sử dụng các đối tượng sửa đổi.
Để tạo điều kiện cho việc định kiểu phản ứng, StyleState hoạt động như một giao diện ổn định, chỉ đọc, theo dõi trạng thái hoạt động của một phần tử (chẳng hạn như trạng thái đã bật, đã nhấn hoặc đã lấy tiêu điểm). Trong StyleScope, bạn có thể truy cập vào thuộc tính này thông qua thuộc tính state để triển khai logic có điều kiện ngay trong các định nghĩa Kiểu.
Tương tác dựa trên trạng thái: Di chuột, đặt tiêu điểm, nhấn, chọn, bật, chuyển đổi
Các kiểu có sẵn tính năng hỗ trợ cho các hoạt động tương tác phổ biến:
- Đã nhấn
- Được di chuột
- Đã chọn
- Đã bật
- Đã chuyển đổi
Bạn cũng có thể hỗ trợ các trạng thái tuỳ chỉnh. Hãy xem phần Tạo kiểu cho trạng thái tuỳ chỉnh bằng StyleState để biết thêm thông tin.
Xử lý các trạng thái tương tác bằng tham số Kiểu
Ví dụ sau đây minh hoạ cách sửa đổi background và borderColor để phản hồi các trạng thái tương tác, cụ thể là chuyển sang màu tím khi di chuột và màu xanh dương khi được lấy tiêu điểm:
@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) }) } ) }
Bạn cũng có thể tạo các định nghĩa trạng thái lồng ghép. Ví dụ: bạn có thể xác định một kiểu cụ thể cho trường hợp nút vừa được nhấn vừa được di chuột đồng thời:
@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) }) } ) }
Thành phần kết hợp tuỳ chỉnh có Modifier.styleable
Khi tạo các thành phần styleable của riêng mình, bạn phải kết nối một interactionSource với một styleState. Sau đó, hãy truyền trạng thái này vào Modifier.styleable để sử dụng.
Hãy xem xét trường hợp hệ thống thiết kế của bạn có chứa một GradientButton. Bạn có thể muốn tạo một LoginButton kế thừa từ GradientButton, nhưng thay đổi màu sắc của LoginButton trong các hoạt động tương tác, chẳng hạn như khi được nhấn.
- Để bật nội dung cập nhật kiểu
interactionSource, hãy thêminteractionSourcelàm tham số trong thành phần kết hợp của bạn. Sử dụng tham số được cung cấp hoặc nếu không có tham số nào được cung cấp, hãy khởi tạo mộtMutableInteractionSourcemới. - Khởi động
styleStatebằng cách cung cấpinteractionSource. Đảm bảo trạng thái đã bật củastyleStatephản ánh giá trị của thông số đã bật được cung cấp. - Chỉ định
interactionSourcecho đối tượng sửa đổifocusablevàclickable. Cuối cùng, hãy áp dụngstyleStatecho tham sốstyleablecủa đối tượng sửa đổi.
@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, ) }
Giờ đây, bạn có thể sử dụng trạng thái interactionSource để điều chỉnh kiểu bằng các lựa chọn đã nhấn, được lấy tiêu điểm và di chuột bên trong khối kiểu:
@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.Tạo ảnh động cho các thay đổi về kiểu
Các thay đổi về trạng thái kiểu đi kèm với tính năng hỗ trợ ảnh động tích hợp. Bạn có thể bao bọc thuộc tính mới trong bất kỳ khối thay đổi trạng thái nào bằng animate để tự động thêm ảnh động giữa các trạng thái. Điều này tương tự như các API animate*AsState. Ví dụ sau đây tạo hiệu ứng chuyển màu cho borderColor từ đen sang xanh dương khi trạng thái thay đổi thành trạng thái được lấy tiêu điểm:
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 chấp nhận một animationSpec để thay đổi thời lượng hoặc hình dạng của đường cong hoạt ảnh. Ví dụ sau đây minh hoạ kích thước của hộp bằng thông số 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)) }
Tạo kiểu trạng thái tuỳ chỉnh bằng StyleState
Tuỳ thuộc vào trường hợp sử dụng thành phần kết hợp, bạn có thể có nhiều kiểu được hỗ trợ bởi các trạng thái tuỳ chỉnh. Ví dụ: nếu có một ứng dụng đa phương tiện, bạn có thể muốn có kiểu riêng cho các nút trong thành phần kết hợp MediaPlayer tuỳ thuộc vào trạng thái phát của trình phát. Hãy làm theo các bước sau để tạo và sử dụng trạng thái tuỳ chỉnh của riêng bạn:
- Xác định khoá tuỳ chỉnh
- Tạo phần mở rộng
StyleState - Liên kết đến trạng thái tuỳ chỉnh
Xác định khoá tuỳ chỉnh
Để tạo kiểu tuỳ chỉnh dựa trên trạng thái, trước tiên, hãy tạo một StyleStateKey và truyền giá trị trạng thái mặc định vào. Khi ứng dụng khởi chạy, trình phát nội dung nghe nhìn sẽ ở trạng thái Stopped, vì vậy, trình phát sẽ được khởi chạy theo cách này:
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey(PlayerState.Stopped)
Tạo các hàm mở rộng StyleState
Xác định một hàm mở rộng trên StyleState để truy vấn playState hiện tại.
Sau đó, hãy tạo các hàm mở rộng trên StyleScope bằng các trạng thái tuỳ chỉnh của bạn bằng cách truyền vào playStateKey, một hàm lambda có trạng thái cụ thể và kiểu.
// 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 }) }
Liên kết đến trạng thái tuỳ chỉnh
Xác định styleState trong thành phần kết hợp và đặt styleState.playState bằng với trạng thái đến. Truyền styleState vào hàm styleable trên đối tượng sửa đổi.
@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)) { ///.. } }
Trong lambda style, bạn có thể áp dụng kiểu dựa trên trạng thái cho các trạng thái tuỳ chỉnh bằng cách sử dụng các hàm mở rộng đã xác định trước đó.
@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) }
Đoạn mã sau đây là đoạn mã đầy đủ cho ví dụ này:
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) }