Tính toán tiền boa tuỳ chỉnh

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

Trong lớp học lập trình này, bạn sẽ sử dụng mã nguồn giải pháp từ lớp học lập trình Giới thiệu về trạng thái trong Compose để tạo một công cụ tính tiền boa mang tính tương tác. Công cụ này có thể tự động tính toán và làm tròn số tiền boa khi bạn nhập số tiền trên hoá đơn và tỷ lệ phần trăm boa. Dưới đây là ảnh chụp màn hình khi ứng dụng đã hoàn thiện.

24370de6d667a700.png

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

  • Lớp học lập trình Trạng thái Use trong Jetpack Compose
  • Khả năng thêm thành phần kết hợp TextTextField vào một ứng dụng.
  • Kiến thức về hàm remember, trạng thái, tính năng chuyển trạng thái lên trên (state hoisting) và sự khác biệt giữa các hàm kết hợp có trạng thái và không có trạng thái

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

  • Cách thêm nút hành động vào bàn phím ảo.
  • Cách thiết lập các thao tác với bàn phím.
  • Thành phần kết hợp Switch là gì và cách sử dụng nó.
  • Layout Inspector là gì.

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

  • Một ứng dụng Tip Time tính số tiền boa dựa trên chi phí dịch vụ và tỷ lệ boa mà người dùng nhập.

Bạn cần có

  • Android Studio
  • Mã nguồn giải pháp từ lớp học lập trình Trạng thái Use trong Jetpack Compose

2. Tổng quan về ứng dụng ban đầu

Lớp học lập trình này bắt đầu bằng Ứng dụng Tip Time trong lớp học lập trình trước. Ứng dụng này cung cấp giao diện người dùng cần thiết để tính toán tiền boa dựa trên tỷ lệ phần trăm tiền boa cố định. Hộp văn bản Chi phí dịch vụ cho phép người sử dụng nhập chi phí dịch vụ. Ứng dụng sẽ tính toán và hiển thị số tiền boa trong thành phần kết hợp Text.

Lấy mã nguồn ban đầ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-tip-calculator.git
$ cd basic-android-kotlin-compose-training-tip-calculator
$ git checkout state

Bạn có thể duyệt xem mã đó trong kho lưu trữ GitHub của Tip Calculator.

Chạy ứng dụng Tip Time (Tính tiền boa)

  1. Mở dự án Tip Time trong Android Studio và chạy ứng dụng trên trình mô phỏng hoặc thiết bị.
  2. Nhập chi phí dịch vụ. Ứng dụng sẽ tự động tính toán và hiển thị số tiền boa.

761df483de663721.png

Theo cách tính hiện tại, tỷ lệ phần trăm tiền boa được nhập trực tiếp vào mã nguồn là 15%. Trong lớp học lập trình này, bạn mở rộng tính năng này bằng một trường văn bản để ứng dụng có thể tùy chỉnh tỷ lệ phần trăm tiền boa và làm tròn số tiền boa.

Thêm các tài nguyên chuỗi cần thiết

  1. Trong thẻ Project (Dự án), hãy nhấp vào res > values > strings.xml (tài nguyên > giá trị > strings.xml).
  2. Ở giữa các thẻ <resources> của tệp strings.xml, hãy thêm các tài nguyên chuỗi sau:
<string name="how_was_the_service">Tip (%)</string>
<string name="round_up_tip">Round up tip?</string>

Tệp strings.xml sẽ có dạng như đoạn mã này, bao gồm các chuỗi văn bản từ lớp học lập trình trước:

strings.xml

<resources>
   <string name="app_name">TipTime</string>
   <string name="calculate_tip">Calculate Tip</string>
   <string name="cost_of_service">Cost of Service</string>
   <string name="how_was_the_service">Tip (%)</string>
   <string name="round_up_tip">Round up tip?</string>
   <string name="tip_amount">Tip Amount: %s</string>
</resources>
  1. Thay đổi chuỗi Cost Of Service thành chuỗi Bill Amount. Ở một số quốc gia, dịch vụ có nghĩa là tiền boa, do đó, thay đổi này sẽ giúp tránh nhầm lẫn.
  2. Trong chuỗi Cost of Service, hãy nhấp chuột phải vào thuộc tính cost_of_service của name, rồi chọn Refactor > Rename(Tái cấu trúc > Đổi tên). Một hộp thoại Rename (Đổi tên) sẽ mở ra.

a2f301b95a8c0e3f.png

  1. Trong hộp thoại Rename (Đổi tên), hãy thay thế cost_of _service bằng bill_amount và nhấp vào Refactor (Tái cấu trúc). Thao tác này sẽ cập nhật tất cả các lần xuất hiện của chuỗi cost_of_service trong dự án, nhờ đó, bạn không cần phải thay đổi mã Compose theo cách thủ công.

f525a371c2851d08.png

  1. Trong tệp strings.xml hãy thay đổi giá trị chuỗi từ Cost of Service thành Bill Amount:
<string name="bill_amount">Bill Amount</string>
  1. Chuyển đến tệp MainActivity.kt rồi chạy ứng dụng. Nhãn được cập nhật trong hộp văn bản như trong hình sau:

trường văn bản hiển thị bill amount (số tiền trên hoá đơn) thay vì chi phí dịch vụ

3. Thêm trường văn bản tip-percentage

Khách hàng có thể muốn boa nhiều hoặc ít hơn tuỳ vào chất lượng dịch vụ họ nhận được và các lý do khác. Để đáp ứng yêu cầu này, ứng dụng phải cho phép người dùng tùy chỉnh số tiền boa. Trong phần này, bạn sẽ thêm một trường văn bản để người dùng nhập phần trăm tiền boa tuỳ chỉnh như trong hình sau:

47b5e8543e5eb754.png

Bạn đã có trường văn bản Bill Amount (Số tiền trên hoá đơn) trong ứng dụng, đây là hàm có khả năng kết hợp EditNumberField() không có trạng thái. Trong lớp học lập trình trước, bạn đã đưa trạng thái amountInput từ thành phần kết hợp EditNumberField() vào hàm TipTimeScreen(), điều này khiến thành phần kết hợp EditNumberField() không có trạng thái.

Để thêm một trường văn bản, bạn có thể sử dụng lại thành phần kết hợp EditNumberField(), nhưng với nhãn khác. Để thay đổi như vậy, bạn cần truyền nhãn dưới dạng một tham số, thay vì cố định giá trị trong mã của nhãn này vào trong hàm có khả năng kết hợp EditNumberField().

Giúp hàm kết hợp EditNumberField() có thể được tái sử dụng:

  1. Ở tệp MainActivity.kt trong tham số của hàm kết hợp EditNumberField(), hãy thêm tài nguyên chuỗi label thuộc kiểu Int:
@Composable
fun EditNumberField(
   label: Int,
   value: String,
   onValueChange: (String) -> Unit
)
  1. Thêm một đối số modifier thuộc kiểu Modifier vào hàm kết hợp EditNumberField():
@Composable
fun EditNumberField(
   label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
)
  1. Trong phần thân hàm, hãy thay thế ID của tài nguyên chuỗi đã được nhập trực tiếp vào đoạn mã bằng tham số label:
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       label = { Text(stringResource(label)) },
       //...
   )
}
  1. Để biểu thị tham số label được kỳ vọng là tham chiếu tài nguyên chuỗi, hãy chú giải tham số hàm bằng chú thích @StringRes:
@Composable
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
)
  1. Nhập các mục sau đây:
import androidx.annotation.StringRes
  1. Trong bảng thành phần kết hợp TextField của hàm EditNumberField(), hãy truyền tham số label vào hàm stringResource().
@Composable
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
) {
   TextField(
       //...
       label = { Text(stringResource(label)) },
       //...
   )
}
  1. Trong lệnh gọi hàm EditNumberField() của hàm TipTimeScreen(), hãy đặt tham số label thành tài nguyên chuỗi R.string.bill_amount:
EditNumberField(
   label = R.string.bill_amount,
   value = amountInput,
   onValueChange = { amountInput = it }
)
  1. Trong ngăn Design (Thiết kế), hãy nhấp vào 2d40b921003ab5eb.pngBuild & Refresh (Tạo và làm mới). Giao diện người dùng của ứng dụng sẽ trông giống như hình sau:

a84cd50c50235a9f.png

  1. Trong hàm TipTimeScreen() sau lệnh gọi hàm EditNumberField(), hãy thêm một trường văn bản khác cho tỷ lệ phần trăm tiền boa tùy chỉnh. Gọi một hàm kết hợp EditNumberField() với các tham số sau:
EditNumberField(
   label = R.string.how_was_the_service,
   value = "",
   onValueChange = { }
)

Thao tác này sẽ thêm một hộp văn bản khác cho tỷ lệ phần trăm tiền boa tùy chỉnh.

  1. Trong ngăn Design (Thiết kế), hãy nhấp vào 2d40b921003ab5eb.pngBuild & Refresh (Tạo và làm mới). Bản xem trước ứng dụng hiện hiển thị trường văn bản Tiền boa (%) như trong hình sau:

9d2c01d577d077ae.png

  1. Ở đầu hàm TipTimeScreen(), hãy thêm một thuộc tính var có tên là tipInput cho biến trạng thái của trường văn bản đã thêm. Hãy dùng mutableStateOf("") để khởi tạo biến và kết hợp với lệnh gọi bằng hàm remember:
var tipInput by remember { mutableStateOf("") }
  1. Trong lệnh gọi hàm mới EditNumberField(), hãy đặt tham số có tên value thành biến tipInput và sau đó cập nhật biến tipInput trong biểu thức lambda onValueChange:
EditNumberField(
   label = R.string.how_was_the_service,
   value = tipInput,
   onValueChange = { tipInput = it }
)
  1. Trong hàm TipTimeScreen() sau định nghĩa biến tipInput, hãy xác định một biến val có tên là tipPercent, biến này sẽ chuyển đổi biến tipInput thành kiểu Double, hãy sử dụng toán tử elvis và trả về 0.0 nếu giá trị là null:
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
  1. Trong hàm TipTimeScreen(), hãy cập nhật lệnh gọi hàm calculateTip(), truyền biến tipPercent làm tham số thứ hai:
val tip = calculateTip(amount, tipPercent)

Đoạn mã cho hàm TipTimeScreen() sẽ trông giống như sau:

@Composable
fun TipTimeScreen() {
   var amountInput by remember { mutableStateOf("") }
   var tipInput by remember { mutableStateOf("") }

   val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
   val amount = amountInput.toDoubleOrNull() ?: 0.0
   val tip = calculateTip(amount, tipPercent)

   Column(
       modifier = Modifier.padding(32.dp),
       verticalArrangement = Arrangement.spacedBy(8.dp)
   ) {
       Text(
           text = stringResource(R.string.calculate_tip),
           fontSize = 24.sp,
           modifier = Modifier.align(Alignment.CenterHorizontally)
       )
       Spacer(Modifier.height(16.dp))
       EditNumberField(
           label = R.string.bill_amount,
           value = amountInput,
           onValueChange = { amountInput = it }
       )
       EditNumberField(
           label = R.string.how_was_the_service,
           value = tipInput,
           onValueChange = { tipInput = it }
       )
       Spacer(Modifier.height(24.dp))
       Text(
           text = stringResource(R.string.tip_amount, tip),
           modifier = Modifier.align(Alignment.CenterHorizontally),
           fontSize = 20.sp,
           fontWeight = FontWeight.Bold
       )
   }
}
  1. Chạy ứng dụng trên trình mô phỏng hoặc thiết bị, sau đó nhập số tiền trên hóa đơn và tỷ lệ phần trăm tiền boa. Ứng dụng có tính đúng số tiền boa không?

bdc482b015472300.png

4. Đặt nút hành động

Trong lớp học lập trình trước, bạn đã tìm hiểu cách dùng lớp KeyboardOptions để cài đặt loại bàn phím. Trong phần này, bạn sẽ tìm hiểu cách thiết lập nút hành động trên bàn phím cũng với KeyboardOptions. Nút hành động trên bàn phím là nút nằm dưới cùng bàn phím. Bạn có thể thấy một số ví dụ trong bảng này:

Thuộc tính

Nút hành động trên bàn phím

ImeAction.SearchĐược sử dụng khi người dùng muốn thực hiện một tìm kiếm.

ImeAction.SendĐược sử dụng khi người dùng muốn gửi văn bản trong trường nhập dữ liệu.

ImeAction.GoĐược sử dụng khi người dùng muốn chuyển đến mục tiêu của văn bản trong mục nhập.

Trong tác vụ này, bạn cài đặt hai nút hành động khác nhau cho các hộp văn bản:

  • Nút hành động Next (Tiếp theo) cho hộp văn bản Bill Amount (Số tiền hóa đơn), cho biết người dùng hiện đã hoàn thành thao tác nhập và muốn chuyển sang hộp văn bản tiếp theo.
  • Nút hành động Done (Xong) cho hộp văn bản Tip % (% tiền boa) cho biết người dùng đã nhập xong thông tin đầu vào.

Bạn có thể xem ví dụ về bàn phím với các nút hành động trong các hình sau:

Thêm tuỳ chọn bàn phím:

  1. Trong lệnh gọi hàm TextField() của hàm EditNumberField(), hãy truyền hàm khởi tạo KeyboardOptions, một imeAction được đặt đối số thành giá trị ImeAction.Next. Dùng hàm KeyboardOptions.Default.copy để sử dụng các chế độ mặc định khác như viết hoa và tự động sửa lỗi.
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       keyboardOptions = KeyboardOptions.Default.copy(
           keyboardType = KeyboardType.Number,
           imeAction = ImeAction.Next
       )
   )
}
  1. Chạy ứng dụng trên trình mô phỏng hoặc thiết bị. Giờ đây, bàn phím hiển thị nút hành động Tiếp theo như có thể thấy trong hình sau:

Tuy nhiên, bạn cần hai nút hành động khác nhau cho các trường văn bản. Bạn sẽ sớm khắc phục được vấn đề này.

  1. Kiểm tra hàm EditNumberField(). Tham số keyboardOptions trong hàm TextField() được mã hóa cứng. Để tạo những nút hành động khác cho các trường văn bản, bạn cần truyền đối tượng KeyboardOptions dưới dạng một đối số. Đây là việc bạn sẽ thực hiện trong bước tiếp theo.
// No need to copy, just examine the code.
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit
) {
   TextField(
       //...
       keyboardOptions = KeyboardOptions.Default.copy(
          keyboardType = KeyboardType.Number,
          imeAction = ImeAction.Next
       )
   )
}
  1. Trong định nghĩa hàm EditNumberField(), hãy thêm một tham số keyboardOptions thuộc loại KeyboardOptions. Trong phần nội dung hàm, hãy chỉ định hàm này vào tham số có tên keyboardOptions của hàm TextField():
@Composable
fun EditNumberField(
   @StringRes label: Int,
   keyboardOptions: KeyboardOptions,
   value: String,
   onValueChange: (String) -> Unit
){
   TextField(
       //...
       keyboardOptions = keyboardOptions
   )
}
  1. Trong hàm TipTimeScreen(), hãy cập nhật lệnh gọi hàm EditNumberField() đầu tiên, truyền tham số có tên keyboardOptions vào trường văn bản Bill Amount (Số tiền trên hoá đơn).
EditNumberField(
   label = R.string.bill_amount,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Next
   ),
   value = amountInput,
   onValueChange = { amountInput = it }
)
  1. Ở lệnh gọi hàm thứ hai EditNumberField(), thay đổi% tiền boa của trường văn bảnimeAction thành ImeAction.Done. Hàm của bạn phải trông giống như đoạn mã sau:
EditNumberField(
   label = R.string.how_was_the_service,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Done
   ),
   value = tipInput,
   onValueChange = { tipInput = it }
)
  1. Chạy ứng dụng. Thành phần này hiển thị các nút hành động Tiếp theoXong như có thể thấy trong các hình sau:

  1. Nhập số tiền hóa đơn bất kỳ và nhấp vào nút hành động Tiếp theo, sau đó nhập tỷ lệ phần trăm tiền boa bất kỳ và nhấp vào nút hành động Xong. Không có gì xảy ra cả vì bạn chưa thêm chức năng nào vào các nút. Bạn có thể làm như vậy trong phần tiếp theo.

5. Đặt thao tác bằng bàn phím

Trong phần này, bạn triển khai chức năng để di chuyển tiêu điểm đến trường văn bản tiếp theo và đóng bàn phím để cải thiện trải nghiệm người dùng bằng lớp KeyboardActions. Điều này cho phép nhà phát triển chỉ định những hành động được kích hoạt trong phản hồi hành động IME (trình chỉnh sửa Phương thức nhập) của người dùng trên bàn phím phần mềm. Một ví dụ về hành động IME là khi người dùng nhấp vào nút hành động Tiếp theo hoặc Xong.

Bạn hãy triển khai như sau:

  • Trên hành động Tiếp theo: Di chuyển tiêu điểm đến trường văn bản tiếp theo (hộp văn bản % tiền boa ).
  • Trên hành động Xong: Đóng bàn phím ảo.
  1. Trong hàm EditNumberField(), hãy thêm một biến val có tên là focusManager và chỉ định giá trị của thuộc tính LocalFocusManager.current:
val focusManager = LocalFocusManager.current

Giao diện LocalFocusManager được dùng để kiểm soát tiêu điểm trong Compose. Bạn sử dụng biến này để di chuyển tiêu điểm đến và xóa tiêu điểm khỏi các hộp văn bản.

  1. Nhập import androidx.compose.ui.platform.LocalFocusManager.
  2. Trong chữ ký hàm EditNumberField(), hãy thêm một tham số keyboardActions khác thuộc loại KeyboardActions:
@Composable
fun EditNumberField(
   @StringRes label: Int,
   keyboardOptions: KeyboardOptions,
   keyboardActions: KeyboardActions,
   value: String,
   onValueChange: (String) -> Unit
) {
   //...
}
  1. Trong phần nội dung hàm EditNumberField(), hãy cập nhật lệnh gọi hàm TextField(), đặt tham số keyboardActions thành tham số keyboardActions được truyền vào.
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       keyboardActions = keyboardActions
   )
}

Giờ đây, bạn có thể tuỳ chỉnh các trường văn bản bằng chức năng khác nhau cho từng nút hành động.

  1. Trong lệnh gọi hàm TipTimeScreen(), hãy cập nhật lệnh gọi hàm EditNumberField() đầu tiên để bao gồm một tham số có tên keyboardActions làm đối số mới. Chỉ định nó một giá trị, KeyboardActions( onNext = { } ):
// Bill amount text field
EditNumberField(
   //...
   keyboardActions = KeyboardActions(
       onNext = { }
   ),
   //...
)

Biểu thức lambda của tham số có tên onNext sẽ chạy khi người dùng nhấn nút hành động Tiếp theo trên bàn phím.

  1. Xác định hàm lambda, yêu cầu FocusManager di chuyển tiêu điểm xuống thành phần kết hợp tiếp theo là Tip % (% tiền boa). Trong biểu thức lambda, hãy gọi hàm moveFocus() trên đối tượng focusManager rồi truyền vào đối số FocusDirection.Down:
// Bill amount text field
EditNumberField(
   label = R.string.bill_amount,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Next
   ),
   keyboardActions = KeyboardActions(
       onNext = { focusManager.moveFocus(FocusDirection.Down) }
   ),
   value = amountInput,
   onValueChange = { amountInput = it }
)

Hàm moveFocus() di chuyển tiêu điểm theo hướng đã chỉ định, khớp với trường văn bản Tip % (% tiền boa) trong trường hợp này.

  1. Nhập các mục sau:
import androidx.compose.ui.focus.FocusDirection
  1. Triển khai tương tự với trường văn bản Tip % (% tiền boa). Sự khác biệt là bạn cần xác định tham số có tên onDone thay vì onNext.
// Tip% text field
EditNumberField(
   //...
   keyboardActions = KeyboardActions(
       onDone = { }
   ),
   //...
)
  1. Sau khi người dùng nhập tiền boa tuỳ chỉnh, thao tác Done (Xong) trên bàn phím sẽ xoá tiêu điểm và cũng sẽ đóng bàn phím. Hãy xác định hàm lambda, yêu cầu FocusManager để xóa tiêu điểm. Trong biểu thức lambda, hãy gọi hàm clearFocus() trên đối tượng focusManager:
EditNumberField(
   label = R.string.how_was_the_service,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Done
   ),
   keyboardActions = KeyboardActions(
       onDone = { focusManager.clearFocus() }),
   value = tipInput,
   onValueChange = { tipInput = it }
)

Hàm clearFocus() xóa tiêu điểm khỏi thành phần được đặt tiêu điểm.

  1. Chạy ứng dụng. Giờ đây, các thao tác trên bàn phím sẽ thay đổi thành phần được đặt tiêu điểm như có thể thấy trong ảnh GIF này:

3164e7a2f39a2d7b.gif

6. Thêm một nút chuyển

Nút chuyển có trạng thái bật hoặc tắt một mục. Nút bật/tắt có 2 trạng thái cho phép người dùng chọn giữa 2 tuỳ chọn. Một nút chuyển bao gồm hai phần thumb và track bạn có thể thấy trong những hình sau:

1. Thumb
2. Theo dõi

Nút chuyển là một lựa chọn kiểm soát có thể được dùng để nhập quyết định hoặc khai báo tùy chọn, chẳng hạn như các cài đặt bạn có thể thấy trong hình sau:

a90c4e22e48b30e0.png

Người dùng có thể kéo nút thumb qua lại để đánh dấu lựa chọn hoặc chỉ cần nhấn vào nút chuyển để bật/tắt. Bạn có thể xem một ví dụ khác về nút bật/tắt trong ảnh GIF này, trong đó chế độ cài đặt Visual options (Tuỳ chọn hiển thị) được chuyển thành Dark mode (Chế độ tối):

91b7bd7a6e02e5ff.gif

Để tìm hiểu thêm về nút chuyển, hãy tìm đọc tài liệu về Nút chuyển.

Bạn sử dụng thành phần kết hợp Switch để người dùng có thể chọn làm tròn số tiền boa lên số nguyên gần nhất như có thể thấy trong hình sau:

cf89a61484296bab.png

Thêm một hàng cho thành phần kết hợp TextSwitch:

  1. Sau hàm EditNumberField(), hãy thêm một hàm tổng hợp RoundTheTipRow() rồi truyền một Modifier mặc định làm đối số tương tự như hàm EditNumberField():
@Composable
fun RoundTheTipRow(modifier: Modifier = Modifier) {
}
  1. Triển khai hàm RoundTheTipRow(), thêm một bố cục thành phần kết hợp Row với modifier sau để đặt chiều rộng của các thành phần con thành mức tối đa trên màn hình, căn giữa và đảm bảo nó có kích thước 48 dp:
Row(
   modifier = Modifier
       .fillMaxWidth()
       .size(48.dp),
   verticalAlignment = Alignment.CenterVertically
) {
}
  1. Nhập các mục sau:
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Size
  1. Trong khối lambda của bố cục thành phần kết hợp Row, hãy thêm một thành phần kết hợp Text sử dụng tài nguyên chuỗi R.string.round_up_tip để hiển thị một chuỗi Round up tip?:
Text(text = stringResource(R.string.round_up_tip))
  1. Sau thành phần kết hợp Text, hãy thêm một thành phần kết hợp Switch và truyền tham số có tên checked được đặt thành roundUp và một tham số có tên onCheckedChange được đặt thành onRoundUpChanged.
Switch(
    checked = roundUp,
    onCheckedChange = onRoundUpChanged,
)

Bảng này chứa thông tin về các tham số bạn đã xác định cho hàm RoundTheTipRow():

Tham số

Mô tả

checked

Liệu nút chuyển này có được kiểm tra hay không. Đây là trạng thái của thành phần kết hợp Switch.

onCheckedChange

Lệnh gọi lại sẽ được gọi khi nút chuyển được nhấp.

  1. Nhập các mục sau đây:
import androidx.compose.material.Switch
  1. Trong hàm RoundTipRow(), hãy thêm tham số roundUp thuộc loại Boolean và hàm lambda onRoundUpChanged nhận Boolean và không trả về giá trị nào:
@Composable
fun RoundTheTipRow(
   roundUp: Boolean,
   onRoundUpChanged: (Boolean) -> Unit,
   modifier: Modifier = Modifier
)

Việc này sẽ chuyển trạng thái của nút chuyển.

  1. Trong thành phần kết hợp Switch, hãy thêm modifier này để căn chỉnh thành phần kết hợp Switch đến cuối màn hình:
       Switch(
           modifier = modifier
               .fillMaxWidth()
               .wrapContentWidth(Alignment.End),
           //...
       )
  1. Nhập các mục sau đây:
import androidx.compose.foundation.layout.wrapContentWidth
  1. Trong hàm TipTimeScreen(), hãy thêm một biến var cho trạng thái của thành phần kết hợp Switch. Tạo một biến var có tên là roundUp, đặt biến đó thành mutableStateOf(), trong đó false làm đối số mặc định. Bao quanh lệnh gọi bằng remember { }.
fun TipTimeScreen() {
   //...
   var roundUp by remember { mutableStateOf(false) }

   //...
   Column(
       ...
   ) {
     //...
  }
}

Đây là biến cho trạng thái thành phần kết hợp Switch và false sẽ là trạng thái mặc định.

  1. Trong khối TipTimeScreen() của hàm Column sau trường văn bản Tip % (% tiền boa), hãy gọi hàm RoundTheTipRow() với các đối số sau: tham số có tên roundUp được đặt thành roundUp và tham số có tên onRoundUpChanged được đặt thành lệnh gọi lại lambda cập nhật giá trị roundUp:
@Composable
fun TipTimeScreen() {
   //...

   Column(
       ...
   ) {
       Text(
           ...
       )
       Spacer(...)
       EditNumberField(
           ...
       )
       EditNumberField(
           ...
       )
       RoundTheTipRow(roundUp = roundUp, onRoundUpChanged = { roundUp = it })
       Spacer(...)
       Text(
           ...
       )
   }
}

Thao tác này sẽ hiển thị hàng Tiền boa được làm tròn.

  1. Chạy ứng dụng. Ứng dụng hiển thị nút bật/tắt Round up tip? (Làm tròn tiền boa?), nhưng nút thumb bật/tắt hầu như không hiển thị như trong hình này:

Các nút chuyển không được chọn và được chọn với các số xác định 2 phần tử và trạng thái của nút chuyển đó1. Thumb
2. Track

Bạn có thể cải thiện khả năng hiển thị nút thumb ở những bước tiếp theo bằng cách chuyển nó sang màu xám đậm.

  1. Trong thành phần kết hợp Switch() của hàm RoundTheTipRow(), hãy thêm một tham số có tên là colors.
  2. Đặt tham số có tên colors thành hàm SwitchDefaults.colors() chấp nhận một tham số có tên uncheckedThumbColor được đặt thành đối số Color.DarkGray.
Switch(
   //...
   colors = SwitchDefaults.colors(
       uncheckedThumbColor = Color.DarkGray
   )
)
  1. Nhập các mục sau:
import androidx.compose.material.SwitchDefaults
import androidx.compose.ui.graphics.Color

Hàm kết hợp RoundTheTipRow() giờ đây sẽ có dạng như đoạn mã sau:

@Composable
fun RoundTheTipRow(roundUp: Boolean, onRoundUpChanged: (Boolean) -> Unit) {
   Row(
       modifier = Modifier
           .fillMaxWidth()
           .size(48.dp),
       verticalAlignment = Alignment.CenterVertically
   ) {
       Text(stringResource(R.string.round_up_tip))
       Switch(
           modifier = Modifier
               .fillMaxWidth()
               .wrapContentWidth(Alignment.End),
           checked = roundUp,
           onCheckedChange = onRoundUpChanged,
           colors = SwitchDefaults.colors(
               uncheckedThumbColor = Color.DarkGray
           )
       )
   }
}
  1. Chạy ứng dụng. Màu nút thumb của nút chuyển sẽ khác như trong hình sau:

24370de6d667a700.png

  1. Nhập số tiền trên hoá đơn và tỷ lệ phần trăm của tiền boa, sau đó chọn nút bật/tắt Round up tip? (Làm tròn tiền boa?). Số tiền boa sẽ không được làm tròn vì bạn vẫn cần phải cập nhật hàm calculateTip() trong phần tiếp theo.

Cập nhật hàm calculateTip() để làm tròn tiền boa

Sửa đổi hàm calculateTip() để chấp nhận biến Boolean nhằm làm tròn tiền boa lên số nguyên gần nhất:

  1. Để làm tròn tiền boa, hàm calculateTip() phải biết trạng thái của nút chuyển, đó là Boolean. Trong hàm calculateTip(), hãy thêm tham số roundUp thuộc loại Boolean:
private fun calculateTip(
   amount: Double,
   tipPercent: Double = 15.0,
   roundUp: Boolean
): String {
   //...
}
  1. Trong hàm calculateTip() trước câu lệnh return, hãy thêm một điều kiện if() kiểm tra giá trị roundUp. NếuroundUptrue, xác định một biến tip và đặt thànhkotlin.math.ceil() rồi truyền hàmtip làm đối số:
if (roundUp)
   tip = kotlin.math.ceil(tip)

Hàm calculateTip() hoàn chỉnh sẽ có dạng như đoạn mã này:

private fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
   var tip = tipPercent / 100 * amount
   if (roundUp)
       tip = kotlin.math.ceil(tip)
   return NumberFormat.getCurrencyInstance().format(tip)
}
  1. Trong hàm TipTimeScreen(), hãy cập nhật lệnh gọi hàm calculateTip() rồi truyền một tham số roundUp:
val tip = calculateTip(amount, tipPercent, roundUp)
  1. Chạy ứng dụng. Giờ đây, nó sẽ làm tròn số tiền boa như bạn thấy ở những hình sau:

7. Lấy mã giải pháp

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

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git

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

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

8. Kết luận

Xin chúc mừng! Bạn đã thêm chức năng tiền boa tùy chỉnh vào Ứng dụng Tip Time của mình. Giờ đây, ứng dụng cho phép người dùng nhập một tỷ lệ phần trăm tiền boa tùy chỉnh và làm tròn số tiền boa đó. Chia sẻ thành quả của bạn trên mạng xã hội với #AndroidBasics!

Tìm hiểu thêm