Thêm Compose vào một ứng dụng dựa trên Khung hiển thị

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

Ngay từ đầu, Jetpack Compose đã được thiết kế để có khả năng tương tác với Khung hiển thị, tức là Compose và hệ thống Khung hiển thị có thể chia sẻ các tài nguyên và hoạt động bổ trợ cho nhau để hiển thị giao diện người dùng. Chức năng này cho phép bạn thêm Compose vào ứng dụng hiện có dựa trên Khung hiển thị. Tức là, Compose và Khung hiển thị có thể cùng tồn tại trong cơ sở mã của bạn cho đến khi toàn bộ ứng dụng hoàn toàn nằm trong Compose.

Trong lớp học lập trình này, bạn sẽ chuyển mục danh sách dựa trên khung hiển thị trong ứng dụng Juice Tracker (Theo dõi nước ép) sang Compose. Bạn có thể tự chuyển đổi các khung hiển thị còn lại của Juice Tracker nếu muốn.

Nếu sở hữu một ứng dụng có giao diện người dùng dựa trên Khung hiển thị, có thể bạn sẽ không muốn viết lại toàn bộ giao diện người dùng cho ứng dụng đó cùng một lúc. Lớp học lập trình này giúp bạn chuyển đổi một khung hiển thị trong giao diện người dùng dựa trên khung hiển thị thành một thành phần Compose.

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

  • Đã làm quen với giao diện người dùng dựa trên Khung hiển thị.
  • Nắm được cách tạo ứng dụng bằng giao diện người dùng dựa trên Khung hiển thị.
  • Có kinh nghiệm về cú pháp Kotlin, bao gồm cả hàm lambda.
  • Nắm được cách tạo ứng dụng trong Jetpack Compose.

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

  • Cách thêm Compose vào màn hình hiện có được tạo bằng khung hiển thị Android.
  • Cách xem trước một Thành phần kết hợp được thêm vào ứng dụng dựa trên Khung hiển thị.

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

  • Bạn sẽ chuyển đổi một mục danh sách dựa trên Khung hiển thị sang Compose trong ứng dụng Juice Tracker.

2. Tổng quan về ứng dụng khởi đầu

Lớp học lập trình này sử dụng mã giải pháp của ứng dụng Juice Tracker trong học phần Tạo ứng dụng Android bằng khung hiển thị làm mã khởi đầu. Ứng dụng khởi đầu lưu dữ liệu bằng cách sử dụng thư viện dữ liệu cố định Room. Người dùng có thể thêm thông tin về loại nước ép vào cơ sở dữ liệu của ứng dụng, chẳng hạn như tên nước ép, nội dung mô tả, màu sắc và điểm xếp hạng.

36bd5542e97fee2e.png

Trong lớp học lập trình này, bạn sẽ chuyển đổi mục danh sách dựa trên khung hiển thị sang Compose.

Mục danh sách có thông tin về nước ép

Tải mã khởi đầu cho lớp học lập trình này

Để bắt đầu, hãy tải đoạn 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-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views

Bạn có thể duyệt xem mã này trong Kho lưu trữ GitHub JuiceTracker.

3. Thêm thư viện Jetpack Compose

Recollet, Compose và các Khung hiển thị có thể cùng tồn tại trên một màn hình có sẵn; bạn có thể có một số phần tử trên giao diện người dùng trong Compose và một số phần tử khác trong hệ thống Khung hiển thị. Ví dụ: bạn có thể chỉ có danh sách trong Compose, trong khi phần còn lại của màn hình nằm trong hệ thống Khung hiển thị.

Hãy hoàn tất các bước sau để thêm thư viện Compose vào ứng dụng Juice Tracker.

  1. Mở Juice Tracker trong Android Studio.
  2. Mở build.gradle.kts cấp ứng dụng.
  3. Bên trong khối buildFeatures, hãy thêm một cờ compose = true.
buildFeatures {
    //...
    // Enable Jetpack Compose for this module
    compose = true
}

Cờ này cho phép Android Studio hoạt động với Compose. Bạn chưa thực hiện bước này trong các lớp học lập trình trước đây vì Android Studio sẽ tự động tạo mã này khi bạn tạo một dự án mẫu mới dùng Compose trên Android Studio.

  1. Bên dưới buildFeatures, hãy thêm khối composeOptions.
  2. Bên trong khối, hãy đặt kotlinCompilerExtensionVersion thành "1.5.1" để thiết lập phiên bản trình biên dịch Kotlin.
composeOptions {
    kotlinCompilerExtensionVersion = "1.5.1"
}
  1. Trong phần dependencies, hãy thêm phần phụ thuộc Compose. Bạn cần có các phần phụ thuộc sau để thêm Compose vào ứng dụng dựa trên Khung hiển thị. Các phần phụ thuộc này giúp tích hợp Compose vào Activity (Hoạt động), thêm thư viện thành phần thiết kế Compose, hỗ trợ giao diện Compose Jetpack và cung cấp các công cụ để hỗ trợ IDE tốt hơn.
dependencies {
    implementation(platform("androidx.compose:compose-bom:2023.06.01"))
    // other dependencies
    // Compose
    implementation("androidx.activity:activity-compose:1.7.2")
    implementation("androidx.compose.material3:material3")
    implementation("com.google.accompanist:accompanist-themeadapter-material3:0.28.0")

    debugImplementation("androidx.compose.ui:ui-tooling")
}

Thêm ComposeView

ComposeView là một Khung hiển thị Android có thể lưu trữ nội dung trên giao diện người dùng Jetpack Compose. Sử dụng setContent để cung cấp hàm có khả năng kết hợp nội dung cho khung hiển thị này.

  1. Mở layout/list_item.xml và xem bản xem trước trong thẻ Split (Phân tách).

Khi kết thúc lớp học lập trình này, bạn sẽ thay thế khung hiển thị này bằng một khung hiển thị có khả năng kết hợp.

7a2df616fde1ec56.png

  1. Trong JuiceListAdapter.kt, hãy xoá ListItemBinding khỏi mọi vị trí. Trong lớp JuiceListViewHolder, hãy thay thế binding.root bằng composeView.
import androidx.compose.ui.platform.ComposeView

class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
): RecyclerView.ViewHolder(composeView)
  1. Trong thư mục onCreateViewHolder(), hãy cập nhật hàm return() sao cho khớp với đoạn mã sau:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): JuiceListViewHolder {
   return JuiceListViewHolder(
       ComposeView(parent.context),
       onEdit,
       onDelete
   )
}
  1. Trong lớp JuiceListViewHolder, hãy xoá mọi biến private và xoá toàn bộ mã khỏi hàm bind(). Giờ đây, lớp JuiceListViewHolder của bạn có dạng như đoạn mã sau:
class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
) : RecyclerView.ViewHolder(composeView) {

   fun bind(juice: Juice) {

   }
}
  1. Khi đó, bạn có thể xoá các đối tượng nhập com.example.juicetracker.databinding.ListItemBindingandroid.view.LayoutInflater.
// Delete
import com.example.juicetracker.databinding.ListItemBinding
import android.view.LayoutInflater
  1. Xoá tệp layout/list_item.xml.
  2. Chọn OK trong hộp thoại Delete (Xoá).

2954ed44c5827571.png

4. Thêm hàm có khả năng kết hợp

Tiếp theo, bạn sẽ tạo một thành phần kết hợp để hình thành nên mục danh sách. Thành phần kết hợp này nhận Juice và 2 hàm callback để chỉnh sửa và xoá mục danh sách.

  1. Trong JuiceListAdapter.kt, sau định nghĩa lớp JuiceListAdapter, hãy tạo một hàm có khả năng kết hợp tên là ListItem().
  2. Đặt hàm ListItem() chấp nhận đối tượng Juice và một hàm callback lambda để xoá.
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@Composable
fun ListItem(
    input: Juice,
    onDelete: (Juice) -> Unit,
    modifier: Modifier = Modifier
) {
}

Xem bản xem trước của mục danh sách mà bạn muốn tạo. Hãy lưu ý rằng bản xem trước đó có biểu tượng nước ép, thông tin chi tiết về nước ép và biểu tượng nút xoá. Bạn sẽ sớm triển khai các thành phần này.

4ec7f82371c6bc15.png

Tạo thành phần kết hợp cho biểu tượng Nước ép

  1. Trong JuiceListAdapter.kt, sau thành phần kết hợp ListItem(), hãy tạo một hàm có khả năng kết hợp khác có tên JuiceIcon(). Hàm này chứa colorModifier.
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {

}
  1. Bên trong hàm JuiceIcon(), hãy thêm các biến cho color và nội dung mô tả thành phần như trong đoạn mã sau:
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {
   val colorLabelMap = JuiceColor.values().associateBy { stringResource(it.label) }
   val selectedColor = colorLabelMap[color]?.let { Color(it.color) }
   val juiceIconContentDescription = stringResource(R.string.juice_color, color)

}

Khi sử dụng các biến colorLabelMapselectedColor, bạn sẽ truy xuất tài nguyên màu liên kết với lựa chọn của người dùng.

  1. Thêm bố cục Box để thể hiện 2 biểu tượng ic_juice_coloric_juice_clear chồng lên nhau. Biểu tượng ic_juice_color được phủ màu và căn giữa.
import androidx.compose.foundation.layout.Box

Box(
   modifier.semantics {
       contentDescription = juiceIconContentDescription
   }
) {
   Icon(
       painter = painterResource(R.drawable.ic_juice_color),
       contentDescription = null,
       tint = selectedColor ?: Color.Red,
       modifier = Modifier.align(Alignment.Center)
   )
   Icon(painter = painterResource(R.drawable.ic_juice_clear), contentDescription = null)
}

Vì bạn đã biết cách triển khai thành phần kết hợp, nên chúng tôi sẽ không nêu chi tiết cách triển khai thành phần này.

  1. Thêm một hàm để xem trước JuiceIcon(). Truyền màu vào dưới dạng Yellow.
import androidx.compose.ui.tooling.preview.Preview

@Preview
@Composable
fun PreviewJuiceIcon() {
    JuiceIcon("Yellow")
}

c016198f82a5d199.png

Tạo thành phần kết hợp cho thông tin chi tiết về nước ép

Trong JuiceListAdapter.kt, bạn cần thêm một hàm có khả năng kết hợp khác để hiển thị thông tin chi tiết về nước ép. Bạn cũng cần một bố cục cột để hiển thị 2 thành phần kết hợp Text cho tên và nội dung mô tả, cũng như một chỉ báo xếp hạng. Để làm được điều này, vui lòng hoàn thành các bước sau:

  1. Thêm một hàm có khả năng kết hợp có tên là JuiceDetails(). Hàm này sẽ nhận đối tượng JuiceModifier, cũng như một thành phần kết hợp văn bản cho tên nước ép và một thành phần kết hợp để mô tả nước ép như trong đoạn mã sau:
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.ui.text.font.FontWeight

@Composable
fun JuiceDetails(juice: Juice, modifier: Modifier = Modifier) {
   Column(modifier, verticalArrangement = Arrangement.Top) {
       Text(
           text = juice.name,
           style = MaterialTheme.typography.h5.copy(fontWeight = FontWeight.Bold),
       )
       Text(juice.description)
       RatingDisplay(rating = juice.rating, modifier = Modifier.padding(top = 8.dp))
   }
}
  1. Để khắc phục lỗi tham chiếu chưa được giải quyết, hãy tạo một hàm có khả năng kết hợp tên là RatingDisplay().

536030e2ecb01a4e.png

Trong hệ thống Khung hiển thị, bạn có một RatingBar để hiển thị thanh điểm xếp hạng sau đây. Compose không có thành phần kết hợp cho thanh điểm xếp hạng. Vì vậy, bạn cần triển khai thành phần này từ đầu.

  1. Xác định hàm RatingDisplay() để thể hiện các dấu sao theo điểm xếp hạng. Hàm có khả năng kết hợp này cho thấy số sao dựa trên điểm xếp hạng.

Thanh xếp hạng có 4 sao

import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource

@Composable
fun RatingDisplay(rating: Int, modifier: Modifier = Modifier) {
   val displayDescription = pluralStringResource(R.plurals.number_of_stars, count = rating)
   Row(
       // Content description is added here to support accessibility
       modifier.semantics {
           contentDescription = displayDescription
       }
   ) {
       repeat(rating) {
           // Star [contentDescription] is null as the image is for illustrative purpose
           Image(
               modifier = Modifier.size(32.dp),
               painter = painterResource(R.drawable.star),
               contentDescription = null
           )
       }
   }
}

Để tạo dấu sao có thể vẽ được trong Compose, bạn cần tạo thành phần vectơ cho dấu sao đó.

  1. Trong ngăn Project (Dự án), hãy nhấp chuột phải vào drawable > New > Vector Asset (đối tượng có thể vẽ > Tạo mới > Thành phần vectơ).

201431ca3d212113.png

  1. Trong hộp thoại Asset Studio, hãy tìm một biểu tượng dấu sao. Chọn biểu tượng dấu sao được tô kín.

9956ed24371f61ac.png

5a79bac6f3982b72.png

  1. Thay đổi giá trị màu của dấu sao thành 625B71.

44d4bdfa93bc369a.png

  1. Nhấp vào Next > Finish (Tiếp theo > Hoàn tất).
  2. Xin lưu ý rằng một đối tượng có thể vẽ sẽ xuất hiện trong thư mục res/drawable.

64bb8d9f05019229.png

  1. Thêm thành phần kết hợp xem trước để xem trước thành phần kết hợp JuiceDetails.
@Preview
@Composable
fun PreviewJuiceDetails() {
    JuiceDetails(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4))
}

có tên nước ép, nội dung mô tả về nước ép và thanh điểm xếp hạng theo sao

Tạo thành phần kết hợp cho nút xoá

  1. Trong JuiceListAdapter.kt, hãy thêm một hàm có khả năng kết hợp khác có tên DeleteButton(). Hàm này sẽ nhận một hàm callback lambda và một Đối tượng sửa đổi.
  2. Đặt hàm lambda thành đối số onClick và truyền vào Icon() như trong đoạn mã sau:
import androidx.compose.ui.res.painterResource
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

@Composable
fun DeleteButton(onDelete: () -> Unit, modifier: Modifier = Modifier) {
    IconButton(
        onClick = { onDelete() },
        modifier = modifier
    ) {
        Icon(
            painter = painterResource(R.drawable.ic_delete),
            contentDescription = stringResource(R.string.delete)
        )
    }
}
  1. Thêm hàm xem trước để xem trước nút xoá.
@Preview
@Composable
fun PreviewDeleteIcon() {
    DeleteButton({})
}

Bản xem trước biểu tượng xoá của Android Studio

5. Triển khai hàm ListItem

Giờ đây, khi đã có đủ thành phần kết hợp cần thiết để hiển thị mục danh sách, bạn có thể sắp xếp các thành phần này trong một bố cục. Hãy lưu ý hàm ListItem() mà bạn đã xác định ở bước trước.

@Composable
fun ListItem(
   input: Juice,
   onEdit: (Juice) -> Unit,
   onDelete: (Juice) -> Unit,
   modifier: Modifier = Modifier
) {
}

Trong JuiceListAdapter.kt, hãy hoàn tất các bước sau để triển khai hàm ListItem().

  1. Thêm bố cục Row bên trong hàm lambda Mdc3Theme {}.
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import com.google.accompanist.themeadapter.material3.Mdc3Theme

Mdc3Theme {
   Row(
       modifier = modifier,
       horizontalArrangement = Arrangement.SpaceBetween
   ) {

   }
}
  1. Bên trong lambda Row, hãy gọi 3 thành phần kết hợp có tên JuiceIcon, JuiceDetails, DeleteButton mà bạn tạo làm thành phần con.
JuiceIcon(input.color)
JuiceDetails(input, Modifier.weight(1f))
DeleteButton({})

Việc truyền Modifier.weight(1f) đến thành phần kết hợp JuiceDetails() đảm bảo các thông tin chi tiết về nước ép hiện trong phần không gian nằm ngang còn lại sau khi đo lường các thành phần con không tính trọng số.

  1. Truyền vào hàm lambda onDelete(input) và đối tượng sửa đổi các tham số căn chỉnh phần trên cùng cho thành phần kết hợp DeleteButton.
DeleteButton(
   onDelete = {
       onDelete(input)
   },
   modifier = Modifier.align(Alignment.Top)
)
  1. Viết một hàm xem trước để xem trước thành phần kết hợp ListItem.
@Preview
@Composable
fun PreviewListItem() {
   ListItem(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4), {})
}

bản xem trước mục danh sách trong Android Studio chứa thông tin về nước ép củ dền

  1. Liên kết thành phần kết hợp ListItem với trình lưu trữ khung hiển thị. Gọi onEdit(input) bên trong hàm lambda clickable() để mở hộp thoại chỉnh sửa khi mục trong danh sách được nhấp vào.

Bạn cần lưu trữ thành phần kết hợp trong lớp JuiceListViewHolder, bên trong hàm bind(). Bạn sẽ sử dụng ComposeView, một Khung hiển thị Android có thể lưu trữ nội dung trên giao diện người dùng Compose bằng phương thức setContent.

fun bind(input: Juice) {
    composeView.setContent {
        ListItem(
            input,
            onDelete,
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    onEdit(input)
                }
                .padding(vertical = 8.dp, horizontal = 16.dp),
       )
   }
}
  1. Chạy ứng dụng. Thêm loại nước ép bạn thích. Chiêm ngưỡng mục danh sách mới trong Compose.

aadccf32ab952d0f.png. 8aa751f4cf63bf98.png

Xin chúc mừng! Bạn vừa tạo ứng dụng đầu tiên của mình có khả năng tương tác trong Compose (có sử dụng các thành phần Compose trong một ứng dụng dựa trên khung hiển thị).

6. Lấy mã 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-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views-with-compose

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.

7. Tìm hiểu thêm

Tài liệu dành cho nhà phát triển Android

Lớp học lập trình [Trung cấp]