Tạo giao diện bằng Kiểu

Bạn có thể tạo ứng dụng theo nhiều cách bằng cách sử dụng Kiểu. Lựa chọn của bạn phụ thuộc vào mức độ áp dụng Material Design của ứng dụng:

  1. Hệ thống thiết kế hoàn toàn tuỳ chỉnh, không dùng Material Design
    • Đề xuất: Xác định các kiểu thành phần sử dụng giá trị từ giao diện và hiển thị các tham số kiểu trên các thành phần của hệ thống thiết kế.
  2. Sử dụng Material Design
    • Đề xuất: Chờ Material được áp dụng để tích hợp với Styles. Sử dụng kiểu trên các thành phần của riêng bạn nếu có thể.

Lớp Kiểu

Trong mô hình Compose truyền thống, hoạt động tuỳ chỉnh thường phụ thuộc nhiều vào việc ghi đè các mã thông báo chung (màu sắc và kiểu chữ) do MaterialTheme cung cấp hoặc bao bọc và ghi đè các thuộc tính của một thành phần kết hợp hệ thống thiết kế nếu có thể. Đôi khi, có những thuộc tính trong lớp Material (Vật liệu) không được hiển thị thông qua các hệ thống con hoặc tham số, nhưng là giá trị mặc định được mã hoá cứng trên chính thành phần đó.

Với Styles API, sẽ có một lớp trừu tượng mới là cầu nối giữa các hệ thống con và thành phần: Styles.

Lớp Trách nhiệm Ví dụ
Giá trị hệ thống con Giá trị được đặt tên val Primary = Color(0xFF34A85E)
Kiểu nguyên tử Kiểu chỉ thay đổi một thuộc tính val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic
Kiểu thành phần Cấu hình dành riêng cho thành phần Một nút có nền chính và khoảng đệm 16 dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
Thành phần Phần tử trên giao diện người dùng chức năng sử dụng một Kiểu. Button(style = buttonStyle) { ... }
Sơ đồ minh hoạ việc tạo giao diện bằng các kiểu với phần giới thiệu lớp mới
Hình 1. Ví dụ về một thành phần và cách thành phần đó truy cập vào các kiểu từ một giao diện.

Kiểu nguyên tử so với kiểu nguyên khối

Với Styles API, bạn có thể chia một Kiểu thành các kiểu riêng biệt. Thay vì xác định các kiểu phức tạp, dành riêng cho thành phần như baseButtonStyle, bạn cũng có thể tạo các kiểu tiện ích nhỏ, có một mục đích duy nhất. Đây sẽ là "nguyên tử" của bạn.

// 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,
                )
            )
        }
    }
}

Thành phần sử dụng "then"

Một trong những tính năng mạnh mẽ của Styles API mới là toán tử then, cho phép bạn hợp nhất nhiều đối tượng Style. Điều này cho phép bạn tạo một thành phần bằng các lớp tiện ích nguyên tử.

Truyền thống (không phải nguyên tử):

// One large monolithic style
val buttonStyle = Style {
    contentPadding(16.dp)
    shape(RoundedCornerShape(8.dp))
    background(Color.Blue)
}

Tái cấu trúc nguyên tử:

// Combine atoms to create the final appearance
val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic

Áp dụng Kiểu trong hệ thống thiết kế

Hãy cân nhắc những lựa chọn sau đây khi áp dụng Kiểu trong hệ thống thiết kế của bạn, tuỳ thuộc vào vị trí của hệ thống thiết kế trong phổ.

Hệ thống thiết kế tuỳ chỉnh bằng Kiểu

Trường hợp cần cân nhắc: Bạn đã được giao một hướng dẫn toàn diện về thương hiệu không dựa trên Material Design và bạn không có ý định sử dụng Material Design.

Chiến lược: Triển khai một hệ thống thiết kế hoàn toàn tuỳ chỉnh và hiển thị các kiểu trong thành phần của giao diện.

Đây là đường dẫn tuỳ chỉnh nếu bạn không sử dụng Material làm ngôn ngữ hệ thống thiết kế chính. Bạn hoàn toàn bỏ qua MaterialTheme để có các định nghĩa trực quan và đã tạo giao diện tuỳ chỉnh của riêng mình. Bạn tạo một CompanyTheme đóng vai trò là vùng chứa cho các Kiểu của bạn.

  • Cách hoạt động: Tạo một đối tượng CompanyTheme chứa các đối tượng Style cho mọi thành phần trong hệ thống của bạn. Các thành phần của bạn (trình bao bọc xung quanh logic Material hoặc các triển khai Box hoặc Layout tuỳ chỉnh) sẽ sử dụng trực tiếp các kiểu này và hiển thị một tham số Style cho người dùng hệ thống thiết kế của bạn.
  • Lớp Kiểu: Kiểu là định nghĩa chính của hệ thống thiết kế. Mã thông báo là các biến có tên được đưa vào những kiểu này. Điều này cho phép tuỳ chỉnh sâu, chẳng hạn như xác định các ảnh động riêng biệt cho các thay đổi về trạng thái (ví dụ: tạo ảnh động cho tỷ lệ và màu sắc khi nhấn).

Nếu bạn đang tạo giao diện tuỳ chỉnh của riêng mình mà không sử dụng Material và muốn áp dụng các kiểu, hãy thêm danh sách kiểu vào Giao diện của bạn. Nhờ đó, bạn có thể truy cập vào các kiểu cơ sở từ mọi nơi trong dự án.

  1. Tạo một lớp Styles lưu trữ nhiều kiểu trong ứng dụng của bạn và tạo các kiểu mặc định. Ví dụ: trong ứng dụng Jetsnack, lớp này có tên là JetsnackStyles:

    object 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)
        }
    }

  2. Cung cấp Styles trong giao diện tổng thể của bạn và hiển thị các hàm tiện ích mở rộng trợ giúp trên StyleScope để truy cập vào các hệ thống con:

    @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,
            )
        }
    }

  3. Truy cập vào JetsnackStyles trong thành phần kết hợp của bạn:

    @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)
        }
    }

Ngoài việc áp dụng giao diện chung, bạn có thể sử dụng các chiến lược thay thế để kết hợp Styles vào ứng dụng của mình. Bạn có thể tận dụng Styles nội tuyến cho các trang web gọi cụ thể hoặc sử dụng các định nghĩa tĩnh khi không cần đến các chức năng tạo giao diện đầy đủ. Styles không được hoán đổi có điều kiện trừ phi toàn bộ kiểu chữ khác biệt về cơ bản. Bạn nên ưu tiên truy cập vào các mã thông báo động bên trong một định nghĩa trực quan thay vì chuyển đổi giữa các đối tượng kiểu riêng biệt.