Dùng Jetpack Compose để tuỳ chỉnh giao diện Material

1. Trước khi bắt đầu

Material Design là một hệ thống thiết kế do những nhà thiết kế và nhà phát triển của Google xây dựng và hỗ trợ nhằm tạo ra trải nghiệm kỹ thuật số chất lượng cao cho Android, cũng như cho các nền tảng web và di động khác. Hệ thống này đưa ra hướng dẫn về cách xây dựng giao diện người dùng của ứng dụng sao cho dễ đọc, hấp dẫn và nhất quán.

Trong lớp học lập trình này, bạn sẽ tìm hiểu về cách Tuỳ chỉnh giao diện Material để sử dụng Material Design trong ứng dụng của mình, kèm theo hướng dẫn về cách tuỳ chỉnh màu sắc, kiểu chữ và hình dạng. Bạn có thể tuỳ chỉnh ít hoặc nhiều tuỳ thích cho ứng dụng của mình. Bạn cũng sẽ tìm hiểu cách thêm thanh ứng dụng trên cùng để cho thấy tên và biểu tượng của ứng dụng.

Điều kiện tiên quyết

  • Làm quen với ngôn ngữ Kotlin, bao gồm cú pháp, hàm và các biến.
  • Có thể tạo bố cục trong tính năng Compose, bao gồm các hàng và cột có khoảng đệm.
  • Có thể tạo danh sách đơn giản trong Compose.

Kiến thức bạn sẽ học được

  • Cách áp dụng tính năng Tuỳ chỉnh giao diện Material cho ứng dụng Compose.
  • Cách thêm bảng màu tuỳ chỉnh vào ứng dụng của bạn.
  • Cách thêm phông chữ tuỳ chỉnh vào ứng dụng của bạn.
  • Cách thêm hình dạng tuỳ chỉnh vào các thành phần trong ứng dụng của bạn.
  • Cách thêm thanh ứng dụng trên cùng vào ứng dụng của bạn.

Sản phẩm bạn sẽ tạo ra

  • Bạn sẽ xây dựng một ứng dụng đẹp mắt kết hợp các phương pháp hay nhất về Material Design.

Bạn cần có

  • Phiên bản mới nhất của Android Studio.
  • Kết nối Internet để tải mã khởi đầu và phông chữ xuống.

2. Tổng quan về ứng dụng

Trong lớp học lập trình này, bạn sẽ tạo Woof, một ứng dụng cho thấy danh sách các chú chó và dùng Material Design để mang lại một trải nghiệm đẹp mắt cho ứng dụng.

92eca92f64b029cf.png

Qua lớp học lập trình này, chúng ta sẽ tìm hiểu một số việc có thể thực hiện qua tính năng Tuỳ chỉnh giao diện Material. Hãy tham gia lớp học lập trình này để có ý tưởng về cách sử dụng tính năng Tuỳ chỉnh giao diện Material nhằm cải thiện giao diện các ứng dụng mà bạn sẽ tạo ra sau này.

Bảng màu

Dưới đây là các bảng màu cho cả giao diện sáng và tối mà chúng ta sẽ tạo.

Hình ảnh này có bảng phối màu sáng cho ứng dụng Woof.

Hình ảnh này có bảng phối màu tối cho ứng dụng Woof.

Dưới đây là ứng dụng hoàn thiện trong cả giao diện sáng lẫn giao diện tối.

Giao diện sáng

Giao diện tối

Kiểu chữ

Dưới đây là các kiểu chữ bạn sẽ sử dụng trong ứng dụng.

8ea685b3871d5ffc.png

Tệp giao diện

Tệp Theme.kt là tệp chứa toàn bộ thông tin về giao diện (theme) của ứng dụng, được xác định thông qua màu sắc, kiểu chữ và hình dạng. Đây là tệp quan trọng mà bạn cần biết. Bên trong tệp này là thành phần kết hợp WoofTheme(), giúp thiết lập màu sắc, kiểu chữ và hình dạng của ứng dụng.

@Composable
fun WoofTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = false,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColors
        else -> LightColors
    }
    val view = LocalView.current
    if (!view.isInEditMode) {
        SideEffect {
            setUpEdgeToEdge(view, darkTheme)
        }
    }

    MaterialTheme(
        colorScheme = colorScheme,
        shapes = Shapes,
        typography = Typography,
        content = content
    )
}

/**
 * Sets up edge-to-edge for the window of this [view]. The system icon colors are set to either
 * light or dark depending on whether the [darkTheme] is enabled or not.
 */
private fun setUpEdgeToEdge(view: View, darkTheme: Boolean) {
    val window = (view.context as Activity).window
    WindowCompat.setDecorFitsSystemWindows(window, false)
    window.statusBarColor = Color.Transparent.toArgb()
    val navigationBarColor = when {
        Build.VERSION.SDK_INT >= 29 -> Color.Transparent.toArgb()
        Build.VERSION.SDK_INT >= 26 -> Color(0xFF, 0xFF, 0xFF, 0x63).toArgb()
        // Min sdk version for this app is 24, this block is for SDK versions 24 and 25
        else -> Color(0x00, 0x00, 0x00, 0x50).toArgb()
    }
    window.navigationBarColor = navigationBarColor
    val controller = WindowCompat.getInsetsController(window, view)
    controller.isAppearanceLightStatusBars = !darkTheme
    controller.isAppearanceLightNavigationBars = !darkTheme
}

Trong MainActivity.kt, WoofTheme() được thêm vào để cung cấp tính năng Tuỳ chỉnh giao diện Material cho toàn bộ ứng dụng.

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           WoofTheme {
               Surface(
                   modifier = Modifier.fillMaxSize()
               ) {
                   WoofApp()
               }
           }
       }
   }
}

Hãy cùng xem WoofPreview(). WoofTheme() được thêm vào để cung cấp tính năng Tuỳ chỉnh giao diện Material mà bạn thấy trong WoofPreview().

@Preview
@Composable
fun WoofPreview() {
    WoofTheme(darkTheme = false) {
        WoofApp()
    }
}

3. Lấy đoạn mã khởi đầu

Để bắt đầu, hãy tải mã khởi đầu xuống:

Ngoài ra, bạn có thể sao chép kho lưu trữ GitHub cho mã:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git
$ cd basic-android-kotlin-compose-training-woof
$ git checkout starter

Bạn có thể xem đoạn mã này trong kho lưu trữ GitHub Woof app.

Khám phá mã khởi đầu

  1. Mở mã khởi đầu trong Android Studio.
  2. Mở tệp com.example.woof > dữ liệu > Dog.kt. Mục này chứa Dog data class sẽ được dùng để đại diện cho ảnh, tên, tuổi và sở thích của chú chó. Bảng này cũng chứa danh sách các chú chó và thông tin mà bạn sẽ sử dụng làm dữ liệu trong ứng dụng của mình.
  3. Mở res > đối tượng có thể vẽ được. Đối tượng này chứa tất cả các thành phần hình ảnh mà bạn cần cho dự án này, bao gồm biểu tượng ứng dụng, hình ảnh và biểu tượng của chó.
  4. Mở res > values > strings.xml. (tài nguyên > giá trị > strings.xml.) Tệp này chứa các chuỗi bạn sẽ sử dụng trong ứng dụng, bao gồm tên ứng dụng, tên chó, nội dung mô tả và các chuỗi khác.
  5. Mở tệp MainActivity.kt. Tệp này chứa đoạn mã để tạo một danh sách đơn giản cho thấy hình ảnh, tên và tuổi của một chú chó.
  6. WoofApp() chứa LazyColumn cho thấy các DogItem.
  7. DogItem() chứa Row cho thấy hình ảnh và thông tin về chú chó.
  8. DogIcon() hiển thị ảnh của chú chó.
  9. DogInformation() chứa tên và tuổi của chú chó.
  10. WoofPreview() cho phép bạn xem trước ứng dụng trong ngăn Design (Thiết kế).

Đảm bảo trình mô phỏng/thiết bị ở giao diện sáng

Ở lớp học lập trình này, bạn sẽ tìm hiểu cả giao diện sáng lẫn tối, tuy nhiên, hầu hết các lớp học lập trình đều chỉ tập trung vào giao diện sáng. Trước khi bắt đầu, hãy đảm bảo thiết bị/trình mô phỏng của bạn ở giao diện sáng.

Để xem ứng dụng của bạn ở giao diện sáng, trên trình mô phỏng hoặc thiết bị thực, hãy thao tác như sau:

  1. Chuyển đến ứng dụng Cài đặt trên thiết bị.
  2. Tìm Giao diện tối rồi nhấp vào giao diện đó.
  3. Nếu Giao diện tối đang bật, hãy tắt chế độ này.

Chạy mã khởi đầu để xem bạn đang bắt đầu từ đâu; đó là một danh sách hiển thị các chú chó cùng với hình ảnh, tên và tuổi của chúng. Chức năng này hoạt động nhưng trông không ổn lắm, thế nên chúng ta sẽ khắc phục việc đó.

6d253ae50c63014d.png

4. Thêm màu sắc

Thành phần đầu tiên bạn cần sửa đổi trong ứng dụng Woof là bảng phối màu.

Bảng phối màu là tổ hợp màu mà ứng dụng của bạn sử dụng. Mỗi kiểu kết hợp màu sắc lại gợi lên tâm trạng riêng, điều này ảnh hưởng đến cảm nhận của mọi người khi họ sử dụng ứng dụng của bạn.

Màu trong hệ thống Android được biểu thị bằng giá trị màu thập lục phân (mã màu hex). Mã màu hex bắt đầu bằng ký tự dấu thăng (#), theo sau là 6 chữ cái và/hoặc số đại diện cho các thành phần màu đỏ, xanh lục và xanh dương (RGB) của màu đó. Hai chữ cái/số đầu tiên đề cập đến màu đỏ, hai chữ cái tiếp theo đề cập đến màu xanh lục và hai chữ cái cuối cùng đề cập đến màu xanh lam.

Cột này hiển thị các số thập lục phân được dùng để tạo màu.

Một màu cũng có thể bao gồm giá trị alpha — chữ cái và/hoặc số — thể hiện độ trong suốt của màu (#00 là độ mờ 0% (trong suốt hoàn toàn), #FF là độ mờ 100% (mờ hoàn toàn). Khi được đưa vào, giá trị alpha là 2 ký tự đầu tiên của mã màu hex sau ký tự dấu thăng (#). Nếu giá trị alpha không được đưa vào thì giá trị này được giả định là #FF, nghĩa là độ mờ 100% (mờ hoàn toàn).

Dưới đây là một số ví dụ về màu và giá trị hex tương ứng.

2753d8cdd396c449.png

Sử dụng Material Theme Builder để tạo bảng phối màu

Để tạo bảng phối màu tuỳ chỉnh cho ứng dụng, chúng ta sẽ sử dụng Material Theme Builder (Trình tạo giao diện Material).

  1. Nhấp vào đường liên kết này để chuyển đến Material Theme Builder (Trình tạo giao diện Material).
  2. Trên ngăn bên trái, bạn sẽ thấy Core Colors (Màu cốt lõi), hãy nhấp vào Primary (Màu chính):

Thao tác này cho thấy 4 màu cốt lõi trong Material Theme Builder (Trình tạo giao diện Material)

  1. Công cụ chọn màu HCT sẽ mở ra.

Đây là Công cụ chọn màu HCT để chọn màu tuỳ chỉnh trong Material Theme Builder (Trình tạo giao diện Material).

  1. Để tạo bảng phối màu như trong ảnh chụp màn hình ứng dụng, bạn sẽ thay đổi màu chính trong công cụ chọn màu này. Trong hộp văn bản, hãy thay thế văn bản hiện tại bằng #006C4C. Thao tác này sẽ thiết lập màu chính của ứng dụng là màu xanh lục.

Ảnh này cho thấy Công cụ chọn màu HCT được thiết lập thành màu xanh lục

Hãy lưu ý cách tính năng này cập nhật các ứng dụng trên màn hình để áp dụng bảng phối màu xanh lục.

Ảnh này cho thấy các ứng dụng trong Material Theme Builder (Trình tạo giao diện Material) tương tác với sự thay đổi về màu sắc trên Công cụ chọn màu HCT.

  1. Di chuyển xuống dưới trang là bạn sẽ thấy bảng phối màu đầy đủ cho giao diện sáng và tối được tạo từ màu mà bạn đã nhập.

Bảng phối màu giao diện sáng của Material Theme Builder (Trình tạo giao diện Material)

Giao diện tối do Material Theme Builder (Trình tạo giao diện Material) tạo ra

Có thể bạn sẽ thắc mắc về những vai trò này và cách sử dụng những vai trò này, sau đây là một số vai trò chính:

  • Màu primary (chính) dùng cho các thành phần chính trên giao diện người dùng.
  • Màu secondary (phụ) dùng cho các thành phần ít nổi bật hơn trong giao diện người dùng.
  • Màu tertiary (thứ cấp) dùng cho các điểm nhấn tương phản có thể dùng để cân bằng màu chính và màu phụ hoặc gây sự chú ý đến một thành phần cụ thê, chẳng hạn như trường nhập.
  • Các phần tử màu on xuất hiện ở trên các màu khác trong bảng màu và chủ yếu áp dụng cho văn bản, biểu tượng và nét vẽ. Trong bảng màu, chúng ta có một màu onSurface xuất hiện ở trên màu surface, còn màu onPrimary xuất hiện ở trên màu primary.

Tất cả cùng tạo ra một hệ thống thiết kế thống nhất, nơi các thành phần liên quan được tô màu tương đồng với nhau.

Vậy là đủ lý thuyết về màu sắc — đã đến lúc thêm bảng màu đẹp mắt này vào ứng dụng!

Thêm bảng màu vào giao diện

Trên trang Material Theme Builder (Trình tạo giao diện Material), bạn có thể nhấp vào nút Export (Xuất) để tải tệp Color.kt và tệp Theme.kt xuống giao diện tuỳ chỉnh đã tạo trong Trình tạo giao diện.

Thao tác này sẽ thực hiện việc thêm giao diện tuỳ chỉnh mà chúng ta tạo vào ứng dụng của bạn. Tuy nhiên, vì tệp Theme.kt đã tạo không chứa đoạn mã cho màu động mà sau đó chúng ta sẽ đề cập đến trong lớp học lập trình này, hãy sao chép các tệp đó.

  1. Mở tệp Color.kt rồi thay thế nội dung bằng đoạn mã dưới đây để sao chép trong bảng phối màu mới.
package com.example.woof.ui.theme

import androidx.compose.ui.graphics.Color

val md_theme_light_primary = Color(0xFF006C4C)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFF89F8C7)
val md_theme_light_onPrimaryContainer = Color(0xFF002114)
val md_theme_light_secondary = Color(0xFF4D6357)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFCFE9D9)
val md_theme_light_onSecondaryContainer = Color(0xFF092016)
val md_theme_light_tertiary = Color(0xFF3D6373)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFC1E8FB)
val md_theme_light_onTertiaryContainer = Color(0xFF001F29)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFBFDF9)
val md_theme_light_onBackground = Color(0xFF191C1A)
val md_theme_light_surface = Color(0xFFFBFDF9)
val md_theme_light_onSurface = Color(0xFF191C1A)
val md_theme_light_surfaceVariant = Color(0xFFDBE5DD)
val md_theme_light_onSurfaceVariant = Color(0xFF404943)
val md_theme_light_outline = Color(0xFF707973)
val md_theme_light_inverseOnSurface = Color(0xFFEFF1ED)
val md_theme_light_inverseSurface = Color(0xFF2E312F)
val md_theme_light_inversePrimary = Color(0xFF6CDBAC)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF006C4C)
val md_theme_light_outlineVariant = Color(0xFFBFC9C2)
val md_theme_light_scrim = Color(0xFF000000)

val md_theme_dark_primary = Color(0xFF6CDBAC)
val md_theme_dark_onPrimary = Color(0xFF003826)
val md_theme_dark_primaryContainer = Color(0xFF005138)
val md_theme_dark_onPrimaryContainer = Color(0xFF89F8C7)
val md_theme_dark_secondary = Color(0xFFB3CCBE)
val md_theme_dark_onSecondary = Color(0xFF1F352A)
val md_theme_dark_secondaryContainer = Color(0xFF354B40)
val md_theme_dark_onSecondaryContainer = Color(0xFFCFE9D9)
val md_theme_dark_tertiary = Color(0xFFA5CCDF)
val md_theme_dark_onTertiary = Color(0xFF073543)
val md_theme_dark_tertiaryContainer = Color(0xFF244C5B)
val md_theme_dark_onTertiaryContainer = Color(0xFFC1E8FB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF191C1A)
val md_theme_dark_onBackground = Color(0xFFE1E3DF)
val md_theme_dark_surface = Color(0xFF191C1A)
val md_theme_dark_onSurface = Color(0xFFE1E3DF)
val md_theme_dark_surfaceVariant = Color(0xFF404943)
val md_theme_dark_onSurfaceVariant = Color(0xFFBFC9C2)
val md_theme_dark_outline = Color(0xFF8A938C)
val md_theme_dark_inverseOnSurface = Color(0xFF191C1A)
val md_theme_dark_inverseSurface = Color(0xFFE1E3DF)
val md_theme_dark_inversePrimary = Color(0xFF006C4C)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF6CDBAC)
val md_theme_dark_outlineVariant = Color(0xFF404943)
val md_theme_dark_scrim = Color(0xFF000000)
  1. Mở tệp Theme.kt rồi thay nội dung bằng đoạn mã dưới đây để thêm các màu mới vào giao diện.
package com.example.woof.ui.theme

import android.app.Activity
import android.os.Build
import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat

private val LightColors = lightColorScheme(
    primary = md_theme_light_primary,
    onPrimary = md_theme_light_onPrimary,
    primaryContainer = md_theme_light_primaryContainer,
    onPrimaryContainer = md_theme_light_onPrimaryContainer,
    secondary = md_theme_light_secondary,
    onSecondary = md_theme_light_onSecondary,
    secondaryContainer = md_theme_light_secondaryContainer,
    onSecondaryContainer = md_theme_light_onSecondaryContainer,
    tertiary = md_theme_light_tertiary,
    onTertiary = md_theme_light_onTertiary,
    tertiaryContainer = md_theme_light_tertiaryContainer,
    onTertiaryContainer = md_theme_light_onTertiaryContainer,
    error = md_theme_light_error,
    errorContainer = md_theme_light_errorContainer,
    onError = md_theme_light_onError,
    onErrorContainer = md_theme_light_onErrorContainer,
    background = md_theme_light_background,
    onBackground = md_theme_light_onBackground,
    surface = md_theme_light_surface,
    onSurface = md_theme_light_onSurface,
    surfaceVariant = md_theme_light_surfaceVariant,
    onSurfaceVariant = md_theme_light_onSurfaceVariant,
    outline = md_theme_light_outline,
    inverseOnSurface = md_theme_light_inverseOnSurface,
    inverseSurface = md_theme_light_inverseSurface,
    inversePrimary = md_theme_light_inversePrimary,
    surfaceTint = md_theme_light_surfaceTint,
    outlineVariant = md_theme_light_outlineVariant,
    scrim = md_theme_light_scrim,
)

private val DarkColors = darkColorScheme(
    primary = md_theme_dark_primary,
    onPrimary = md_theme_dark_onPrimary,
    primaryContainer = md_theme_dark_primaryContainer,
    onPrimaryContainer = md_theme_dark_onPrimaryContainer,
    secondary = md_theme_dark_secondary,
    onSecondary = md_theme_dark_onSecondary,
    secondaryContainer = md_theme_dark_secondaryContainer,
    onSecondaryContainer = md_theme_dark_onSecondaryContainer,
    tertiary = md_theme_dark_tertiary,
    onTertiary = md_theme_dark_onTertiary,
    tertiaryContainer = md_theme_dark_tertiaryContainer,
    onTertiaryContainer = md_theme_dark_onTertiaryContainer,
    error = md_theme_dark_error,
    errorContainer = md_theme_dark_errorContainer,
    onError = md_theme_dark_onError,
    onErrorContainer = md_theme_dark_onErrorContainer,
    background = md_theme_dark_background,
    onBackground = md_theme_dark_onBackground,
    surface = md_theme_dark_surface,
    onSurface = md_theme_dark_onSurface,
    surfaceVariant = md_theme_dark_surfaceVariant,
    onSurfaceVariant = md_theme_dark_onSurfaceVariant,
    outline = md_theme_dark_outline,
    inverseOnSurface = md_theme_dark_inverseOnSurface,
    inverseSurface = md_theme_dark_inverseSurface,
    inversePrimary = md_theme_dark_inversePrimary,
    surfaceTint = md_theme_dark_surfaceTint,
    outlineVariant = md_theme_dark_outlineVariant,
    scrim = md_theme_dark_scrim,
)

@Composable
fun WoofTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = false,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColors
        else -> LightColors
    }
    val view = LocalView.current
    if (!view.isInEditMode) {
        SideEffect {
            setUpEdgeToEdge(view, darkTheme)
        }
    }

    MaterialTheme(
        colorScheme = colorScheme,
        shapes = Shapes,
        typography = Typography,
        content = content
    )
}

/**
 * Sets up edge-to-edge for the window of this [view]. The system icon colors are set to either
 * light or dark depending on whether the [darkTheme] is enabled or not.
 */
private fun setUpEdgeToEdge(view: View, darkTheme: Boolean) {
    val window = (view.context as Activity).window
    WindowCompat.setDecorFitsSystemWindows(window, false)
    window.statusBarColor = Color.Transparent.toArgb()
    val navigationBarColor = when {
        Build.VERSION.SDK_INT >= 29 -> Color.Transparent.toArgb()
        Build.VERSION.SDK_INT >= 26 -> Color(0xFF, 0xFF, 0xFF, 0x63).toArgb()
        // Min sdk version for this app is 24, this block is for SDK versions 24 and 25
        else -> Color(0x00, 0x00, 0x00, 0x50).toArgb()
    }
    window.navigationBarColor = navigationBarColor
    val controller = WindowCompat.getInsetsController(window, view)
    controller.isAppearanceLightStatusBars = !darkTheme
    controller.isAppearanceLightNavigationBars = !darkTheme
}

Trong WoofTheme(), colorScheme val sẽ sử dụng câu lệnh when

  • Nếu giá trị dynamicColor là true và phiên bản của bản dựng là S trở lên, thì hệ thống sẽ kiểm tra xem thiết bị có đang ở darkTheme hay không.
  • Nếu thiết bị ở giao diện tối, colorScheme sẽ được thiết lập thành dynamicDarkColorScheme.
  • Nếu không ở giao diện tối thì giá trị này sẽ được thiết lập thành dynamicLightColorScheme.
  • Nếu ứng dụng không sử dụng dynamicColorScheme, hệ thống sẽ kiểm tra xem ứng dụng có đang ở darkTheme không. Nếu có, colorScheme sẽ được thiết lập thành DarkColors.
  • Nếu không có kết quả nào đúng, thì colorScheme sẽ được thiết lập thành LightColors.

Nội dung được sao chép trong tệp Theme.kt có giá trị dynamicColor được thiết lập thành false và các thiết bị mà chúng ta đang xử lý đang ở chế độ sáng, nên colorScheme sẽ được thiết lập thành LightColors.

val colorScheme = when {
       dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
           val context = LocalContext.current
           if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
       }

       darkTheme -> DarkColors
       else -> LightColors
   }
  1. Chạy lại ứng dụng, bạn có thể thấy thanh ứng dụng đã tự động thay đổi màu.

b48b3fa2ecec9b86.png

Ánh xạ màu

Các thành phần Material sẽ được tự động ánh xạ tới ô màu. Các thành phần chính khác trên giao diện người dùng như Nút hành động nổi cũng được thiết lập thành màu chính theo mặc định. Tức là bạn không cần chỉ định rõ ràng màu cho một thành phần; nó sẽ tự động được liên kết với ô màu khi bạn thiết lập chủ đề màu sắc trong ứng dụng của mình. Bạn có thể ghi đè giá trị này bằng cách thiết lập rõ ràng một màu sắc trong đoạn mã. Đọc thêm về vai trò của màu sắc tại đây.

Trong phần này, chúng ta sẽ gói Row chứa DogIcon()DogInformation() bằng Card để phân biệt màu của mục trong danh sách với màu nền.

  1. Trong hàm có khả năng kết hợp DogItem(), hãy gói Row() bằng một Card().
Card() {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(dimensionResource(id = R.dimen.padding_small))
   ) {
       DogIcon(dog.imageResourceId)
       DogInformation(dog.name, dog.age)
   }
}
  1. Do Card hiện là thành phần kết hợp con đầu tiên trong DogItem(), hãy truyền đối tượng sửa đổi từ DogItem() sang Card, rồi cập nhật đối tượng sửa đổi của Row thành một thực thể mới của Modifier.
Card(modifier = modifier) {
   Row(
       modifier = Modifier
           .fillMaxWidth()
           .padding(dimensionResource(id = R.dimen.padding_small))
   ) {
       DogIcon(dog.imageResourceId)
       DogInformation(dog.name, dog.age)
   }
}
  1. Hãy xem tệp WoofPreview(). Các mục trong danh sách hiện đã tự động thay đổi màu do các thành phần kết hợp Card. Màu sắc trông đẹp, nhưng không có khoảng cách giữa các mục trong danh sách.

6d49372a1ef49bc7.png

Tệp kích thước

Giống như việc bạn sử dụng strings.xml để lưu trữ các chuỗi trong ứng dụng, bạn cũng nên sử dụng tệp dimens.xml để lưu trữ các giá trị kích thước. Cách này rất hữu ích để bạn không phải cố định các giá trị trong đoạn mã. Vì vậy, nếu cần, bạn có thể thay đổi chúng ở một nơi duy nhất.

Chuyển đến app > res > values > dimens.xml rồi xem tệp. Tệp này lưu trữ các giá trị kích thước cho padding_small, padding_mediumimage_size. Các tham số này sẽ được sử dụng trên toàn ứng dụng.

<resources>
   <dimen name="padding_small">8dp</dimen>
   <dimen name="padding_medium">16dp</dimen>
   <dimen name="image_size">64dp</dimen>
</resources>

Để thêm giá trị từ tệp dimens.xml, hãy định dạng đúng như sau:

Cho thấy cách định dạng đúng cách các giá trị sẽ thêm từ tài nguyên kích thước

Ví dụ: để thêm padding_small, bạn sẽ truyền vào dimensionResource(id = R.dimen.padding_small).

  1. Trong WoofApp(), hãy thêm modifier cùng với padding_small trong lệnh gọi đến DogItem().
@Composable
fun WoofApp() {
    Scaffold { it ->
        LazyColumn(contentPadding = it) {
            items(dogs) {
                DogItem(
                    dog = it,
                    modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
                )
            }
        }
    }
}

Trong WoofPreview(), giờ đây sẽ có thêm định nghĩa giữa các mục danh sách.

c54f870f121fe02.png

Giao diện tối

Trong hệ thống Android, bạn có thể chuyển đổi thiết bị sang giao diện tối. Giao diện tối sử dụng màu tối hơn, dịu hơn và:

  • Có thể làm giảm đáng kể mức sử dụng pin (tuỳ vào công nghệ màn hình của thiết bị).
  • Giúp người dùng có thị lực kém và những người nhạy cảm với ánh sáng mạnh dễ nhìn hơn.
  • Giúp mọi người dễ dàng sử dụng thiết bị trong môi trường ánh sáng yếu.

Ứng dụng của bạn có thể chọn Bật nhanh giao diện tối, nghĩa là hệ thống sẽ triển khai giao diện tối cho bạn. Tuy nhiên, người dùng sẽ có trải nghiệm tốt hơn nếu bạn triển khai giao diện tối. Theo đó, bạn vẫn có toàn quyền kiểm soát đối với giao diện ứng dụng.

Khi bạn chọn giao diện tối, điều quan trọng cần lưu ý là màu sắc cần phải đáp ứng các tiêu chuẩn về độ tương phản cùng với tính năng hỗ trợ tiếp cận. Giao diện tối sử dụng một màu tối cho bề mặt, có các điểm nhấn màu sắc hạn chế.

Xem giao diện tối trong bản xem trước

Bạn đã thêm màu cho giao diện tối trong bước trước. Để xem giao diện tối đang hoạt động, bạn sẽ thêm một thành phần kết hợp Xem trước khác vào MainActivity.kt. Nhờ đó, khi thay đổi bố cục giao diện người dùng trong mã của mình, bạn có thể thử bản xem trước của giao diện sáng và giao diện tối cùng lúc.

  1. Trong phần WoofPreview(), hãy tạo một hàm mới có tên là WoofDarkThemePreview() và chú thích hàm này bằng @Preview@Composable.
@Preview
@Composable
fun WoofDarkThemePreview() {

}
  1. Bên trong DarkThemePreview(), hãy thêm WoofTheme(). Nếu không thêm WoofTheme(), bạn sẽ không thấy kiểu nào chúng tôi đã thêm trong ứng dụng. Đặt tham số darkTheme thành giá trị true.
@Preview
@Composable
fun WoofDarkThemePreview() {
   WoofTheme(darkTheme = true) {

   }
}
  1. Gọi WoofApp() bên trong WoofTheme().
@Preview
@Composable
fun WoofDarkThemePreview() {
   WoofTheme(darkTheme = true) {
       WoofApp()
   }
}

Bây giờ, hãy di chuyển xuống ngăn Design (Thiết kế) để xem ứng dụng ở giao diện tối, bao gồm cả nền ứng dụng/danh sách mục có màu tối hơn và văn bản sáng hơn. So sánh sự khác biệt giữa giao diện tối và sáng.

Giao diện tối

Giao diện sáng

Xem giao diện tối trên thiết bị hoặc trình mô phỏng

Để xem ứng dụng của bạn ở giao diện tối, trên trình mô phỏng hoặc thiết bị thực, bạn hãy thao tác như sau:

  1. Chuyển đến ứng dụng Cài đặt trên thiết bị.
  2. Tìm Giao diện tối, rồi nhấp vào giao diện đó.
  3. Bật Giao diện tối.
  4. Mở lại ứng dụng Woof và ứng dụng này sẽ ở giao diện tối.

bc31a94207265b08.png

Lớp học lập trình này tập trung nhiều hơn vào giao diện sáng. Vì vậy, trước khi tiếp tục dùng ứng dụng, hãy tắt giao diện tối.

  1. Chuyển đến ứng dụng Cài đặt trên thiết bị.
  2. Chọn Hiển thị.
  3. Tắt Giao diện tối.

So sánh giao diện hiện tại của ứng dụng so với ban đầu. Các mục và văn bản trong danh sách được xác định rõ hơn, đồng thời, bảng phối màu cũng bắt mắt hơn.

Không màu

Có màu (giao diện sáng)

Có màu (giao diện tối)

Màu động

Material 3 tập trung chủ yếu vào hoạt động cá nhân hoá của người dùng. Một tính năng mới trong Material 3 là Màu động – tạo một giao diện cho ứng dụng dựa trên hình nền của người dùng. Theo đó, nếu người dùng yêu thích màu xanh lục và có nền điện thoại màu xanh dương, thì ứng dụng Woof cũng sẽ có màu xanh dương để phản ánh điều đó. Tính năng tuỳ chỉnh giao diện động chỉ có trên một số thiết bị chạy Android 12 trở lên.

Bạn có thể dùng giao diện tuỳ chỉnh cho các ứng dụng có màu sắc mang đặc trưng thương hiệu mạnh và cũng cần triển khai giao diện này cho các thiết bị không hỗ trợ tuỳ chỉnh giao diện động để ứng dụng vẫn được tuỳ chỉnh giao diện.

  1. Để bật tính năng màu động, hãy mở tệp Theme.kt, chuyển đến thành phần kết hợp WoofTheme() rồi thiết lập tham số dynamicColor thành true.
@Composable
fun WoofTheme(
   darkTheme: Boolean = isSystemInDarkTheme(),
   dynamicColor: Boolean = true,
   content: @Composable () -> Unit
)
  1. Để thay đổi nền của thiết bị hoặc trình mô phỏng, hãy chuyển đến phần Cài đặt rồi tìm kiếm Hình nền.
  2. Thay đổi hình nền thành một màu hoặc một bộ màu.
  3. Chạy lại ứng dụng của bạn để xem giao diện động (lưu ý rằng thiết bị hoặc trình mô phỏng của bạn phải chạy Android 12 trở lên để xem được màu động) rồi thoải mái thay đổi các hình nền khác!

710bd13f6b189dc5.png

  1. Lớp học lập trình này tập trung vào việc tuỳ chỉnh giao diện, vì vậy, hãy tắt dynamicColor trước khi bạn tiếp tục.
@Composable
fun WoofTheme(
   darkTheme: Boolean = isSystemInDarkTheme(),
   dynamicColor: Boolean = false,
   content: @Composable () -> Unit
)

5. Thêm hình dạng

Việc áp dụng hình dạng có thể làm thay đổi giao diện của thành phần kết hợp rất nhiều. Hình dạng thường chi phối sự chú ý, cách xác định các thành phần, trạng thái giao tiếp và thể hiện thương hiệu.

Nhiều hình dạng được xác định bằng cách sử dụng RoundedCornerShape để mô tả một hình chữ nhật có góc được bo tròn. Số được truyền vào sẽ xác định độ tròn của các góc. Nếu bạn dùng RoundedCornerShape(0.dp), hình chữ nhật đó sẽ không có góc tròn; nếu bạn dùng RoundedCornerShape(50.dp), các góc sẽ hoàn toàn tròn.

0.dp

25.dp

50.dp

Mục danh sách ứng dụng Woof có hình dạng

Mục danh sách ứng dụng Woof có hình dạng

Mục danh sách ứng dụng Woof có hình dạng

Bạn cũng có thể tuỳ chỉnh hình dạng hơn nữa bằng cách thêm tỷ lệ bo tròn cho từng góc. Sẽ rất thú vị khi thay đổi các hình dạng!

Trên cùng bên trái: 50.dp
Dưới cùng bên trái: 25.dp
Trên cùng bên phải: 0.dp
Dưới cùng bên phải: 15.dp

Trên cùng bên trái: 15.dp
Dưới cùng bên trái: 50.dp
Trên cùng bên phải: 50.dp
Dưới cùng bên phải: 15.dp

Trên cùng bên trái: 0.dp
Dưới cùng bên trái: 50.dp
Trên cùng bên phải: 0.dp
Dưới cùng bên phải: 50.dp

Mục danh sách ứng dụng Woof có hình dạng

Mục danh sách ứng dụng Woof có hình dạng

Mục danh sách ứng dụng Woof có hình dạng

Tệp Shape.kt dùng để xác định hình dạng của các thành phần trong Compose. Có 3 loại thành phần: nhỏ, trung bình và lớn. Trong phần này, bạn sẽ sửa đổi thành phần Card, được xác định là kích thước medium. Các thành phần được nhóm thành danh mục hình dạng dựa trên kích thước của các thành phần đó.

Trong phần này, bạn sẽ định dạng hình ảnh của chú chó thành một hình tròn và sửa đổi hình dạng của mục trong danh sách.

Sửa hình ảnh chú chó thành hình tròn

  1. Hãy mở tệp Shape.kt và để ý rằng tham số nhỏ được thiết lập thành RoundedCornerShape(50.dp). Tham số này sẽ được sử dụng để tạo hình ảnh thành một vòng tròn.
val Shapes = Shapes(
   small = RoundedCornerShape(50.dp),
)
  1. Mở tệp MainActivity.kt. Trong DogIcon(), hãy thêm thuộc tính clip vào modifier của Image; thao tác này sẽ cắt hình ảnh theo một hình dạng. Truyền MaterialTheme.shapes.small vào.
import androidx.compose.ui.draw.clip

@Composable
fun DogIcon(
   @DrawableRes dogIcon: Int,
   modifier: Modifier = Modifier
) {
   Image(
       modifier = modifier
           .size(dimensionResource(id = R.dimen.image_size))
           .padding(dimensionResource(id = R.dimen.padding_small))
           .clip(MaterialTheme.shapes.small),

Khi nhìn vào WoofPreview(), bạn sẽ thấy các biểu tượng chú chó có hình tròn! Tuy nhiên, một số ảnh bị cắt bớt ở hai bên và không có dạng tròn hoàn toàn.

1d4d1e5eaaddf71e.png

  1. Để sửa tất cả ảnh thành hình tròn, hãy thêm thuộc tính ContentScaleCrop. Thao tác này sẽ cắt hình ảnh cho vừa vặn trong khung hình tròn. Xin lưu ý contentScale là một thuộc tính của Image chứ không phải của modifier.
import androidx.compose.ui.layout.ContentScale

@Composable
fun DogIcon(
   @DrawableRes dogIcon: Int,
   modifier: Modifier = Modifier
) {
   Image(
       modifier = modifier
           .size(dimensionResource(id = R.dimen.image_size))
           .padding(dimensionResource(id = R.dimen.padding_small))
           .clip(MaterialTheme.shapes.small),
       contentScale = ContentScale.Crop,

Đây là thành phần kết hợp DogIcon() đầy đủ.

@Composable
fun DogIcon(
    @DrawableRes dogIcon: Int,
    modifier: Modifier = Modifier
) {
    Image(
        modifier = modifier
            .size(dimensionResource(R.dimen.image_size))
            .padding(dimensionResource(R.dimen.padding_small))
            .clip(MaterialTheme.shapes.small),
        contentScale = ContentScale.Crop,
        painter = painterResource(dogIcon),

        // Content Description is not needed here - image is decorative, and setting a null content
        // description allows accessibility services to skip this element during navigation.

        contentDescription = null
    )
}

Giờ đây, trong WoofPreview(), các biểu tượng có hình tròn.

fc93106990f5e161.png

Thêm hình dạng vào mục trong danh sách

Trong phần này, bạn sẽ thêm một hình dạng vào mục trong danh sách. Mục này đang được hiển thị thông qua Card. Card là nơi chứa một thành phần kết hợp duy nhất và các tuỳ chọn trang trí. Bạn có thể biến tấu giao diện thông qua đường viền, hình dạng, v.v. Trong phần này, bạn sẽ sử dụng Card để thêm hình dạng vào mục trong danh sách.

Mục trong danh sách Woof với kích thước tạo hình được thêm vào

  1. Mở tệp Shape.kt. Card là thành phần trung gian, vì vậy, bạn hãy thêm tham số trung gian của đối tượng Shapes. Đối với ứng dụng này là các góc trên cùng bên phải và dưới cùng bên trái của mục danh sách, nhưng không được bo tròn hoàn toàn. Để làm được việc đó, hãy chuyển 16.dp vào thuộc tính medium.
medium = RoundedCornerShape(bottomStart = 16.dp, topEnd = 16.dp)

Vì theo mặc định, Card đã sử dụng hình dạng trung gian, nên bạn không cần phải thiết lập rõ ràng thành hình dạng trung gian. Hãy xem Bản xem trước và xem hình dạng mới của Card!

Bản xem trước ứng dụng Woof với thẻ được tạo hình

Nếu quay lại tệp Theme.kt trong WoofTheme() và nhìn vào MaterialTheme(), bạn sẽ thấy thuộc tính shapes được thiết lập thành Shapes val mà bạn vừa cập nhật.

MaterialTheme(
   colors = colors,
   typography = Typography,
   shapes = Shapes,
   content = content
)

Dưới đây là hình ảnh so sánh các mục danh sách trước và sau khi tạo hình. Vui lòng lưu ý tác động của việc thêm hình dạng đối với việc làm cho giao diện của ứng dụng trở nên bắt mắt hơn.

Không tạo hình

Có tạo hình

6. Thêm kiểu chữ

Kiểu chữ trong Material Design

Kiểu chữ là tập hợp kiểu phông chữ được sử dụng trên ứng dụng, đảm bảo tính linh hoạt nhưng nhất quán. Kiểu chữ trong Material Design bao gồm 15 kiểu phông chữ mà hệ thống hỗ trợ. Cách đặt tên và phân nhóm đã được đơn giản hoá thành: hiển thị, dòng tiêu đề, tiêu đề, nội dung và nhãn, với kích thước lớn, trung bình và nhỏ cho mỗi nhóm. Bạn chỉ cần sử dụng các lựa chọn này nếu muốn tuỳ chỉnh ứng dụng của mình. Nếu bạn không biết nên đặt kiểu nào cho mỗi danh mục kiểu chữ, hãy nhớ là bạn luôn có thể sử dụng kiểu chữ mặc định.

999a161dcd9b0ec4.png

Kiểu chữ chứa các danh mục văn bản có thể tái sử dụng, mỗi loại đều có cách ứng dụng và ý nghĩa riêng.

Màn hình

Là văn bản lớn nhất trên màn hình, kiểu hiển thị được dành riêng cho văn bản/chữ số ngắn hoặc quan trọng. Các kiểu này hoạt động tốt nhất trên màn hình lớn.

Dòng tiêu đề

Dòng tiêu đề là phù hợp nhất với văn bản ngắn, có mức độ nhấn mạnh cao trên màn hình nhỏ hơn. Có thể các kiểu này sẽ phù hợp để đánh dấu các đoạn văn bản chính hoặc vùng nội dung quan trọng.

Tiêu đề

Kiểu Tiêu đề nhỏ hơn kiểu dòng tiêu đề và nên được sử dụng cho văn bản tương đối ngắn có mức độ nhấn mạnh trung bình.

Nội dung

Kiểu Nội dung được sử dụng cho các đoạn văn bản dài hơn trong ứng dụng.

Nhãn

Kiểu Nhãn là những kiểu nhỏ hơn, thực dụng, được sử dụng cho những nội dung như văn bản bên trong thành phần hoặc cho văn bản rất nhỏ trong nội dung, chẳng hạn như chú thích.

Phông chữ

Nền tảng Android cung cấp nhiều phông chữ, nhưng bạn có thể tuỳ chỉnh ứng dụng của mình bằng các phông chữ không được cung cấp theo mặc định. Phông chữ tuỳ chỉnh có thể khiến văn bản trở nên cá tính và được dùng để thể hiện thương hiệu.

Trong phần này, bạn sẽ thêm phông chữ tuỳ chỉnh có tên là Abril Fatface, Montserrat BoldMontserrat Regular. Bạn sẽ sử dụng dòng tiêu đề displayLarge và displayMedium, và văn bản bodyLarge trong hệ thống kiểu chữ của Material, đồng thời thêm các dòng tiêu đề này vào văn bản trong ứng dụng.

Tạo thư mục tài nguyên Android cho phông chữ.

Trước khi thêm phông chữ vào ứng dụng, bạn cần thêm một thư mục phông chữ.

  1. Trong chế độ xem dự án của Android Studio, hãy nhấp chuột phải vào thư mục res.
  2. Chọn New > Android Resource Directory (Mới > Thư mục tài nguyên Android).

Hình ảnh này cho thấy cách điều hướng cấu trúc tệp đến Thư mục tài nguyên Android.

  1. Đặt tên cho Thư mục font, thiết lập kiểu Resource thành font rồi nhấp vào OK.

Hình ảnh này cho thấy cách thêm một thư mục phông chữ bằng Thư mục tài nguyên mới.

  1. Mở thư mục tài nguyên phông chữ mới nằm trong res > font (tài nguyên > phông chữ).

Tải phông chữ tuỳ chỉnh xuống

Vì đang dùng phông chữ không phải do nền tảng Android cung cấp, nên bạn cần tải phông chữ tuỳ chỉnh xuống.

  1. Truy cập vào https://fonts.google.com/.
  2. Tìm kiếm Montserrat, rồi nhấp vào Tải tập hợp xuống.
  3. Giải nén tệp zip.
  4. Mở thư mục Montserrat đã tải xuống. Trong thư mục static, hãy tìm tệp Montserrat-Bold.ttfMontserrat-Regular.ttf (ttf, là phông chữ viết tắt của TrueType và là định dạng cho tệp phông chữ. Chọn cả hai phông chữ rồi kéo vào thư mục tài nguyên phông chữ trong dự án của bạn trong Android Studio.

Hình ảnh này cho thấy nội dung của thư mục tĩnh của phông chữ Montserrat.

  1. Trong thư mục phông chữ của bạn, hãy đổi tên tệp Montserrat-Bold.ttf thành montserrat_bold.ttf và đổi tên Montserrat-Regular.ttf thành Montserrat-Regular.ttf.
  2. Tìm Abril Fatface rồi nhấp vào Download family (Tải bộ phông chữ xuống).
  3. Mở thư mục Abril_Fatface đã tải xuống. Chọn AbrilFatface-Regular.ttf rồi kéo mục này vào thư mục tài nguyên phông chữ.
  4. Trong thư mục phông chữ của bạn, hãy đổi tên Abril_Fatface_Regular.ttf thành abril_fatface_regular.ttf.

Dưới đây là giao diện của thư mục tài nguyên phông chữ tương ứng với 3 tệp phông chữ tuỳ chỉnh:

Hình ảnh này cho thấy các tệp phông chữ đang được thêm vào thư mục phông chữ.

Chạy phông chữ

  1. Trong cửa sổ dự án, hãy mở ui.theme > Type.kt. Chạy các phông chữ đã tải xuống bên dưới câu lệnh nhập và phía trên Typography val. Trước tiên, hãy khởi chạy Abril Fatface bằng cách đặt giá trị này là FontFamily rồi truyền vào Font với tệp phông chữ abril_fatface_regular.
​​import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import com.example.woof.R

val AbrilFatface = FontFamily(
   Font(R.font.abril_fatface_regular)
)
  1. Khởi tạo Montserrat bên dưới mục Abril Fatface bằng cách đặt giá trị này là FontFamily rồi truyền vào Font tệp phông chữ montserrat_regular. Đối với montserrat_bold, FontWeight.Bold cũng cần được đưa vào. Mặc dù bạn chuyển sang phiên bản in đậm của tệp phông chữ, nhưng tính năng Compose không biết tệp này được in đậm, do đó bạn cần liên kết rõ ràng tệp này với FontWeight.Bold.
import androidx.compose.ui.text.font.FontWeight

val AbrilFatface = FontFamily(
   Font(R.font.abril_fatface_regular)
)

val Montserrat = FontFamily(
   Font(R.font.montserrat_regular),
   Font(R.font.montserrat_bold, FontWeight.Bold)
)

Tiếp theo, bạn đặt các kiểu dòng tiêu đề khác nhau thành phông chữ mà bạn vừa thêm vào. Đối tượng Typography có các tham số cho 13 kiểu chữ được thảo luận ở trên. Bạn có thể xác định bao nhiêu tuỳ ý. Trong ứng dụng này, chúng ta sẽ thiết lập displayLarge, displayMediumbodyLarge. Trong phần tiếp theo của ứng dụng này, bạn sẽ sử dụng labelSmall để thêm tại đây.

Dưới đây là bảng cho bạn thấy phông chữ, độ đậm và kích thước của mỗi dòng tiêu đề mà bạn sẽ thêm.

8ea685b3871d5ffc.png

  1. Đối với thuộc tính displayLarge, hãy thiết lập giá trị bằng TextStyle rồi điền thông tin trong bảng ở trên vào fontFamily, fontWeightfontSize. Tức là tất cả văn bản đặt thành displayLarge sẽ có phông chữ Abril Fatface, với độ đậm phông chữ bình thường và fontSize36.sp.

Lặp lại quá trình này đối với displayMedium, labelSmallbodyLarge.

import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp

val Typography = Typography(
   displayLarge = TextStyle(
       fontFamily = AbrilFatface,
       fontWeight = FontWeight.Normal,
       fontSize = 36.sp
   ),
   displayMedium = TextStyle(
       fontFamily = Montserrat,
       fontWeight = FontWeight.Bold,
       fontSize = 20.sp
   ),
   labelSmall = TextStyle(
       fontFamily = Montserrat,
       fontWeight = FontWeight.Bold,
       fontSize = 14.sp
   ),
   bodyLarge = TextStyle(
       fontFamily = Montserrat,
       fontWeight = FontWeight.Normal,
       fontSize = 14.sp
   )
)

Nếu bạn chuyển đến tệp Theme.kt trong WoofTheme() và xem MaterialTheme(), thì tham số typography sẽ bằng Typography val mà bạn vừa cập nhật.

MaterialTheme(
   colors = colors,
   typography = Typography,
   shapes = Shapes,
   content = content
)

Thêm kiểu chữ vào văn bản ứng dụng

Giờ bạn sẽ thêm các kiểu dòng tiêu đề vào từng mục văn bản trong ứng dụng.

  1. Thêm displayMedium làm kiểu chữ cho dogName vì đây là một mẩu thông tin ngắn và quan trọng. Thêm bodyLarge làm kiểu chữ cho dogAge vì kiểu chữ này phù hợp với văn bản có kích thước nhỏ hơn.
@Composable
fun DogInformation(
   @StringRes dogName: Int,
   dogAge: Int,
   modifier: Modifier = Modifier
) {
   Column(modifier = modifier) {
       Text(
           text = stringResource(dogName),
           style = MaterialTheme.typography.displayMedium,
           modifier = Modifier.padding(top = dimensionResource(id = R.dimen.padding_small))
       )
       Text(
           text = stringResource(R.string.years_old, dogAge),
           style = MaterialTheme.typography.bodyLarge
       )
   }
}
  1. Giờ đây, trong WoofPreview(), tên của chú chó hiển thị bằng phông chữ Montserrat đậm trong 20.sp và tuổi của chú chó hiển thị bằng phông chữ Montserrat thông thường trong 14.sp.

Bản xem trước ứng dụng Woof đã thêm kiểu chữ

Dưới đây là hình ảnh so sánh các mục danh sách trước và sau khi thêm kiểu chữ. Hãy để ý đến sự khác biệt về phông chữ giữa tên chú chó và độ tuổi của chú chó.

Không áp dụng kiểu chữ

Có áp dụng kiểu chữ

7. Thêm thanh trên cùng

Scaffold là một bố cục cung cấp các ô cho nhiều thành phần và thành phần trên màn hình, chẳng hạn như Image, Row hoặc Column. Scaffold cũng cung cấp một ô cho TopAppBar mà bạn sẽ dùng trong phần này.

TopAppBar có thể được sử dụng cho nhiều mục đích, nhưng trong trường hợp này, bạn sẽ dùng nó để thể hiện thương hiệu cũng như bản sắc của ứng dụng. Có 4 loại TopAppBar: chính giữa, nhỏ, trung bình và lớn. Trong lớp học lập trình này, bạn sẽ triển khai thanh ứng dụng trên cùng chính giữa. Bạn sẽ tạo một thành phần kết hợp giống như ảnh chụp màn hình dưới đây rồi đặt vào phần topBar của Scaffold.

172417c7b64372f7.png

Đối với ứng dụng này, thanh trên cùng bao gồm Row có hình ảnh biểu trưng và văn bản của tên ứng dụng. Biểu trưng có hình bàn chân chuyển màu dễ thương và tên ứng dụng!

736f411f5067e0b5.png

Thêm hình ảnh và văn bản vào thanh trên cùng

  1. Trong tệp MainActivity.kt, hãy tạo một thành phần kết hợp có tên là WoofTopAppBar()modifier không bắt buộc.
@Composable
fun WoofTopAppBar(modifier: Modifier = Modifier) {

}
  1. Scaffold hỗ trợ tham số contentWindowInsets có thể giúp chỉ định các phần lồng ghép cho nội dung scaffold. WindowInsets là các phần trên màn hình nơi ứng dụng của bạn có thể giao cắt với giao diện người dùng hệ thống, các phần này sẽ được chuyển đến khung nội dung thông qua tham số PaddingValues. Hãy đọc thêm tại đây.

Giá trị contentWindowInsets được truyền cho LazyColumn dưới dạng contentPadding.

@Composable
fun WoofApp() {
    Scaffold { it ->
        LazyColumn(contentPadding = it) {
            items(dogs) {
                DogItem(
                    dog = it,
                    modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
                )
            }
        }
    }
}
  1. Trong Scaffold, hãy thêm thuộc tính topBar rồi thiết lập thuộc tính đó thành WoofTopAppBar().
Scaffold(
   topBar = {
       WoofTopAppBar()
   }
)

Dưới đây là giao diện của thành phần kết hợp WoofApp():

@Composable
fun WoofApp() {
    Scaffold(
        topBar = {
            WoofTopAppBar()
        }
    ) { it ->
        LazyColumn(contentPadding = it) {
            items(dogs) {
                DogItem(
                    dog = it,
                    modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
                )
            }
        }
    }
}

Không có gì thay đổi trong WoofPreview() do không có gì trong WoofTopAppBar(). Hãy thay đổi điều đó!

Bản xem trước ứng dụng Woof có kiểu chữ

  1. Trong WoofTopAppBar() Composable, hãy thêm một CenterAlignedTopAppBar() và thiết lập tham số đối tượng sửa đổi thành đối tượng sửa đổi được truyền vào WoofTopAppBar().
import androidx.compose.material3.CenterAlignedTopAppBar

@Composable
fun WoofTopAppBar(modifier: Modifier = Modifier) {
   CenterAlignedTopAppBar(
       modifier = modifier
   )
}
  1. Đối với tham số tiêu đề, hãy truyền Row vào để lưu giữ ImageText của CenterAlignedTopAppBar.
@Composable
fun WoofTopAppBar(modifier: Modifier = Modifier){
   CenterAlignedTopAppBar(
       title = {
           Row() {

           }
       },
       modifier = modifier
   )
}
  1. Thêm biểu trưng Image vào Row.
  • Thiết lập kích thước hình ảnh trong modifierimage_size trong tệp dimens.xml và khoảng đệm là padding_small trong tệp dimens.xml.
  • Sử dụng painter để thiết lập Image làm ic_woof_logo trong thư mục đối tượng có thể vẽ.
  • Thiết lập contentDescription thành null. Trong trường hợp này, biểu trưng của ứng dụng không thêm bất kỳ thông tin có ngữ nghĩa nào cho người dùng khiếm thị, nên chúng ta không phải thêm nội dung mô tả.
Row() {
   Image(
       modifier = Modifier
           .size(dimensionResource(id = R.dimen.image_size))
           .padding(dimensionResource(id = R.dimen.padding_small)),
       painter = painterResource(R.drawable.ic_woof_logo),
       contentDescription = null
   )
}
  1. Tiếp theo, hãy thêm thành phần kết hợp Text bên trong Row sau Image.
  • Sử dụng stringResource() để thiết lập thành giá trị app_name. Thao tác này sẽ thiết lập văn bản thành tên của ứng dụng được lưu trữ trong strings.xml.
  • Thiết lập kiểu chữ của văn bản thành displayLarge vì tên ứng dụng là văn bản ngắn và quan trọng.
Text(
   text = stringResource(R.string.app_name),
   style = MaterialTheme.typography.displayLarge
)

Bản xem trước Woof có thanh ứng dụng trên cùng

Đây là nội dung hiển thị trong WoofPreview(), có vẻ hơi lệch vì biểu tượng và văn bản chưa được căn chỉnh theo chiều dọc.

  1. Để khắc phục vấn đề này, hãy thêm tham số giá trị verticalAlignment vào Row và thiết lập tham số đó bằng Alignment.CenterVertically.
import androidx.compose.ui.Alignment

Row(
   verticalAlignment = Alignment.CenterVertically
)

Bản xem trước Woof với thanh ứng dụng nằm ở trên cùng chính giữa theo chiều dọc

Như vậy trông đẹp hơn nhiều!

Đây là thành phần kết hợp WoofTopAppBar() đầy đủ:

@Composable
fun WoofTopAppBar(modifier: Modifier = Modifier) {
   CenterAlignedTopAppBar(
       title = {
           Row(
               verticalAlignment = Alignment.CenterVertically
           ) {
               Image(
                   modifier = Modifier
                       .size(dimensionResource(id = R.dimen.image_size))
                       .padding(dimensionResource(id = R.dimen.padding_small)),
                   painter = painterResource(R.drawable.ic_woof_logo),

                   contentDescription = null
               )
               Text(
                   text = stringResource(R.string.app_name),
                   style = MaterialTheme.typography.displayLarge
               )
           }
       },
       modifier = modifier
   )
}

Chạy ứng dụng và chiêm ngưỡng cách TopAppBar liên kết các thành phần của ứng dụng lại với nhau đẹp như thế nào.

Không có thanh ứng dụng trên cùng

Có thanh ứng dụng trên cùng

Bây giờ, hãy xem ứng dụng hoàn thiện ở chế độ giao diện tối!

2776e6a45cf3434a.png

Xin chúc mừng, bạn đã đến phần cuối của lớp học lập trình này!

8. Lấy mã nguồn giải pháp

Để tải mã này xuống khi lớp học lập trình đã kết thúc, bạn có thể sử dụng các lệnh git sau:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git
$ cd basic-android-kotlin-compose-training-woof
$ git checkout material

Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp zip rồi giải nén và mở trong Android Studio.

Nếu bạn muốn xem mã giải pháp, hãy xem mã đó trên GitHub.

9. Kết luận

Đây là ứng dụng đầu tiên bạn tạo ra bằng Material! Bạn đã tạo một bảng màu tuỳ chỉnh cho cả giao diện sáng lẫn tối, tạo hình cho các thành phần, tải phông chữ xuống và thêm chúng vào ứng dụng, đồng thời tạo một thanh trên cùng tuyệt đẹp để liên kết chúng lại với nhau. Hãy dùng những kỹ năng bạn đã học được ở lớp học lập trình này để điều chỉnh màu, hình dạng và kiểu chữ nhằm tạo một ứng dụng hoàn toàn theo ý bạn!

Tóm tắt

  • Tính năng Tuỳ chỉnh giao diện Material cho phép bạn sử dụng Material Design trong ứng dụng của mình, cùng với hướng dẫn về cách tuỳ chỉnh màu sắc, kiểu chữ và hình dạng.
  • Tệp Theme.kt là nơi xác định giao diện, thông qua một thành phần kết hợp có tên là [your app name]+Theme() (trong trường hợp của ứng dụng này là WoofTheme()). Trong hàm này, đối tượng MaterialTheme thiết lập color, typography, shapescontent của ứng dụng.
  • Color.kt là nơi bạn liệt kê các màu sẽ sử dụng trong ứng dụng. Sau đó, trong tệp Theme.kt, bạn cần chỉ định màu trong LightColorPaletteDarkColorPalette cho các khung cụ thể. Bạn không cần phải chỉ định tất cả ô màu.
  • Ứng dụng của bạn có thể chọn Bật nhanh giao diện tối, nghĩa là hệ thống sẽ triển khai giao diện tối cho bạn. Tuy nhiên, để cải thiện trải nghiệm người dùng, tốt nhất bạn nên triển khai cả giao diện tối để có toàn quyền kiểm soát đối với giao diện của ứng dụng.
  • Shape.kt là nơi bạn xác định các hình dạng của ứng dụng. Có 3 kích thước hình dạng (nhỏ, trung bình, lớn) và bạn có thể chỉ định cách bo tròn các góc.
  • Hình dạng thường chi phối sự chú ý, cách xác định các thành phần, trạng thái giao tiếp và thể hiện thương hiệu.
  • Types.kt là nơi bạn khởi tạo phông chữ và gán fontFamily, fontWeightfontSize cho kiểu chữ trong Material Design.
  • Kiểu chữ trong Material Design bao gồm nhiều kiểu tương phản hỗ trợ nhu cầu của ứng dụng và nội dung của ứng dụng. Kiểu chữ là tổ hợp 15 kiểu mà hệ thống hỗ trợ.

10. Tìm hiểu thêm