1. Trước khi bắt đầu
Như bạn đã tìm hiểu trong các lớp học lập trình trước đây, Material là một hệ thống thiết kế do Google tạo ra với các nguyên tắc, thành phần và công cụ nhằm hỗ trợ các phương pháp hay nhất để thiết kế giao diện người dùng. Trong lớp học lập trình này, bạn sẽ cập nhật ứng dụng tính tiền boa (trong các lớp học lập trình trước) để cải thiện sự tinh tế trong trải nghiệm người dùng, thể hiện trong ảnh chụp màn hình cuối cùng bên dưới. Bạn cũng sẽ kiểm thử ứng dụng trong một số trường hợp bổ sung để đảm bảo người dùng có được trải nghiệm suôn sẻ nhất có thể.
Điều kiện tiên quyết
- Quen thuộc với các tiện ích giao diện người dùng phổ biến như
TextView
,ImageView
,Button
,EditText
,RadioButton
,RadioGroup
vàSwitch
- Quen thuộc với
ConstraintLayout
cũng như cách dùng điều kiện ràng buộc để định vị khung nhìn con - Tự tin chỉnh sửa bố cục XML
- Hiểu rõ sự khác biệt giữa hình ảnh bitmap và vectơ vẽ được
- Có thể thiết lập thuộc tính giao diện trong một giao diện (theme)
- Có thể bật chế độ Giao diện tối trên thiết bị
- Từng chỉnh sửa tệp
build.gradle
của ứng dụng cho các phần phụ thuộc của dự án
Kiến thức bạn sẽ học được
- Cách sử dụng Thành phần Material Design trong ứng dụng
- Cách sử dụng biểu tượng Material trên ứng dụng bằng cách nhập qua Image Asset Studio
- Cách tạo và áp dụng kiểu định dạng mới
- Cách thiết lập các thuộc tính giao diện khác ngoài màu sắc
Sản phẩm bạn sẽ tạo ra
- Một ứng dụng tính toán tiền boa chỉn chu, làm theo các phương pháp hay nhất được đề xuất về giao diện người dùng
Bạn cần có
- Máy tính đã cài đặt phiên bản Android Studio ổn định mới nhất
- Mã cho ứng dụng Tip Time hoàn thành trong các lớp học lập trình trước tại đường dẫn này và đường dẫn này
2. Tổng quan về ứng dụng khởi động
Qua các lớp học lập trình trước, bạn đã xây dựng ứng dụng Tip Time. Đây là một ứng dụng tính tiền boa cho phép tuỳ chỉnh số tiền boa. Giao diện hiện tại của ứng dụng có dạng như ảnh chụp màn hình dưới đây. Chức năng ứng dụng vẫn hoạt động, nhưng giao diện thì như một bản thiết kế mẫu. Các trường thông tin chưa được sắp xếp một cách trực quan. Còn rất nhiều chỗ cần cải thiện, chẳng hạn như cách tạo kiểu và độ giãn cách cần nhất quán hơn cũng như cần sử dụng thành phần Material Design.
3. Thành phần Material
Thành phần Material (Material Component) là các tiện ích giao diện người dùng phổ biến, giúp bạn định kiểu Material cho ứng dụng dễ dàng hơn. Tài liệu này hướng dẫn cách sử dụng và tuỳ chỉnh các Thành phần Material Design. Có một số nguyên tắc chung về thiết kế Material Design cho mỗi thành phần cũng như hướng dẫn riêng cho các thành phần hiện có trên Android. Các sơ đồ có gắn nhãn sẽ cung cấp đầy đủ thông tin, giúp bạn tái tạo một thành phần nào đó nếu nền tảng bạn đã chọn chưa có thành phần đó.
Khi bạn sử dụng Thành phần Material, ứng dụng của bạn sẽ hoạt động nhất quán hơn so với các ứng dụng khác trên thiết bị của người dùng. Nhờ đó, các mẫu giao diện người dùng đã học từ ứng dụng này có thể chuyển sang cho ứng dụng khác. Do đó, người dùng có thể tìm hiểu cách sử dụng ứng dụng của bạn nhanh hơn. Bạn nên sử dụng Thành phần Material bất cứ khi nào có thể (thay vì các tiện ích không phải Material). Các thành phần dễ tuỳ chỉnh và linh hoạt hơn. Bạn sẽ tìm hiểu về điều này trong nhiệm vụ tiếp theo.
Bạn cần đưa thư viện Thành phần Material Design (MDC) vào dự án dưới dạng một thành phần phụ thuộc. Dòng này phải hiển thị trong dự án của bạn theo mặc định. Trong tệp build.gradle
của ứng dụng, hãy đảm bảo phần phụ thuộc này có trong phiên bản thư viện mới nhất. Để biết thêm thông tin, hãy xem trang Get started (Bắt đầu) trên trang web của Material.
app/build.gradle
dependencies {
...
implementation 'com.google.android.material:material:<version>'
}
Trường văn bản
Trong ứng dụng tính tiền boa, ở phần trên cùng của bố cục, bạn sẽ thấy trường EditText
dành cho chi phí dịch vụ. Trường EditText
này vẫn hoạt động nhưng không tuân theo các nguyên tắc thiết kế mới đây của Material Design về hình thức cũng như hành vi của trường văn bản.
Nếu bạn muốn sử dụng thành phần mới nào đó, trước hết hãy tìm hiểu về thành phần đó trên trang web của Material. Trong hướng dẫn về Text Fields (Trường văn bản), có hai loại trường văn bản:
Trường văn bản được tô màu nền (filled text field)
Trường văn bản có đường viền (outlined text field)
Để tạo trường văn bản như trình bày ở trên, hãy sử dụng TextInputLayout
kèm theo TextInputEditText
trong thư viện MDC. Trường văn bản Material có thể tuỳ chỉnh dễ dàng để:
- Hiện văn bản nhập hoặc một nhãn luôn xuất hiện
- Hiện một biểu tượng trong trường văn bản
- Hiện trình trợ giúp hoặc thông báo lỗi
Trong nhiệm vụ đầu tiên của lớp học lập trình này, bạn sẽ thay thế phần chi phí dịch vụ EditText
bằng một trường văn bản Material (bao gồm một TextInputLayout
và TextInputEditText
).
- Mở ứng dụng Tip Time (Tiền boa) trong Android Studio rồi chuyển đến tệp bố cục
activity_main.xml
. Tệp này chứaConstraintLayout
cho bố cục của phần tính tiền boa. - Để xem ví dụ về định dạng XML cho trường văn bản Material, hãy xem lại hướng dẫn dành cho Android về Trường văn bản. Bạn sẽ thấy các đoạn mã như sau:
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/label">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
- Sau khi xem ví dụ này, hãy chèn trường văn bản Material vào trường con đầu tiên của
ConstraintLayout
(trước trườngEditText
). Bạn sẽ bỏ trườngEditText
trong một bước sau này.
Bạn có thể nhập nội dung này vào Android Studio và sử dụng tính năng tự động hoàn thành để nhập dễ dàng hơn. Hoặc bạn có thể sao chép nội dung XML mẫu qua trang tài liệu rồi dán vào bố cục như dưới đây. Hãy lưu ý TextInputLayout
có một khung nhìn con là TextInputEditText
. Hãy nhớ rằng dấu ba chấm (...) được dùng để viết tắt đoạn trích, giúp bạn tập trung vào các dòng XML thực sự cần thay đổi.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
...>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/label">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<EditText
android:id="@+id/cost_of_service" ... />
...
Theo dự kiến, bạn sẽ thấy lỗi trên thành phần TextInputLayout
. Bạn chưa ràng buộc đúng khung hiển thị này trong thành phần mẹ ConstraintLayout
. Ngoài ra cũng chưa nhận dạng được tài nguyên chuỗi. Bạn sẽ khắc phục những lỗi này trong các bước sắp tới.
- Thêm điều kiện ràng buộc theo chiều dọc và chiều ngang vào trường văn bản để định vị chính xác trong
ConstraintLayout
gốc. Vì bạn chưa xoá trườngEditText
, hãy cắt và dán các thuộc tính sau từEditText
rồi đặt vàoTextInputLayout
: điều kiện ràng buộc, mã nhận dạng tài nguyêncost_of_service
, chiều rộng bố cục160dp
, chiều cao bố cụcwrap_content
và văn bản gợi ý@string/cost_of_service
.
...
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/cost_of_service"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:hint="@string/cost_of_service"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout>
...
Có thể bạn sẽ thấy lỗi mã nhận dạng cost_of_service
giống với mã nhận dạng tài nguyên của EditText
. Tuy nhiên, hiện tại bạn có thể bỏ qua lỗi này. (EditText
sẽ được xoá trong vài bước tới).
- Tiếp theo, hãy đảm bảo thành phần
TextInputEditText
có tất cả thuộc tính phù hợp. Cắt và dán kiểu nhập từEditText
vàoTextInputEditText
. Thay đổi mã tài nguyên của phần tửTextInputEditText
thànhcost_of_service_edit_text
.
<com.google.android.material.textfield.TextInputLayout ... >
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/cost_of_service_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal" />
</com.google.android.material.textfield.TextInputLayout>
Chiều rộng của match_parent
và chiều cao của wrap_content
như hiện tại không có vấn đề gì. Khi thiết lập chiều rộng của match_parent
, TextInputEditText
sẽ có cùng chiều rộng với bố cục TextInputLayout
gốc là 160dp
.
- Giờ đây khi bạn đã sao chép toàn bộ thông tin liên quan từ
EditText
, hãy tiếp tục và xoáEditText
khỏi bố cục. - Trong chế độ xem Design (Thiết kế) của bố cục, bạn sẽ thấy bản xem trước dưới đây. Trường chi phí dịch vụ hiện tại trông giống một trường văn bản Material.
- Bạn chưa thể chạy ứng dụng vì có lỗi xảy ra trong tệp
MainActivity.kt
, ở phương thứccalculateTip()
. Hãy nhớ lại một lớp học lập trình trước đây khi dự án của bạn đã bật tính năng liên kết khung nhìn, Android sẽ tạo các thuộc tính trong đối tượng liên kết dựa trên tên mã nhận dạng tài nguyên. Trường để truy xuất chi phí dịch vụ đã thay đổi trong bố cục XML, do đó bạn cần cập nhật lại mã Kotlin cho phù hợp.
Bây giờ, bạn sẽ truy xuất thông tin đầu vào của người dùng qua thành phần TextInputEditText
có mã nhận dạng tài nguyên cost_of_service_edit_text
. Trong MainActivity
, hãy sử dụng binding.costOfServiceEditText
để truy cập chuỗi văn bản được lưu trữ trong đó. Phần còn lại của phương thức calculateTip()
có thể giữ nguyên.
private fun calculateTip() {
// Get the decimal value from the cost of service text field
val stringInTextField = binding.costOfServiceEditText.text.toString()
val cost = stringInTextField.toDoubleOrNull()
...
}
- Tuyệt vời! Bây giờ, hãy chạy ứng dụng và kiểm tra để đảm bảo ứng dụng vẫn hoạt động tốt. Hãy lưu ý cách xuất hiện của "Cost of Service" ("Chi phí dịch vụ") phía trên thông tin đầu vào khi bạn nhập. Tiền boa vẫn sẽ được tính như dự kiến.
Nút chuyển
Trong nguyên tắc thiết kế Material Design cũng có hướng dẫn về nút chuyển (switch). Nút chuyển là một tiện ích dùng để bật hoặc tắt một chế độ cài đặt nào đó.
- Hãy tham khảo hướng dẫn cho Android về nút chuyển của Material. Bạn sẽ tìm hiểu về tiện ích
SwitchMaterial
(trong thư viện MDC). Tiện ích này sẽ cung cấp tính năng tạo kiểu Material cho các nút chuyển. Nếu tiếp tục xem qua hướng dẫn này, bạn sẽ thấy một số XML mẫu. - Để sử dụng
SwitchMaterial
, bạn phải chỉ định rõSwitchMaterial
trong bố cục và sử dụng tên đường dẫn đủ điều kiện.
Trong bố cục activity_main.xml
, hãy thay đổi thẻ XML từ Switch
sang com.google.android.material.switchmaterial.SwitchMaterial
.
...
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/round_up_switch"
android:layout_width="0dp"
android:layout_height="wrap_content" ... />
...
- Chạy ứng dụng để kiểm tra xem ứng dụng còn biên dịch được không. Bạn sẽ thấy ứng dụng chưa có gì thay đổi. Tuy nhiên, việc sử dụng
SwitchMaterial
trong thư viện MDC (thay vìSwitch
trong nền tảng Android) sẽ mang lại lợi ích cho bạn. Đó là khi thư việnSwitchMaterial
được cập nhật (ví dụ: thay đổi nguyên tắc thiết kế Material Design), bạn sẽ được sử dụng miễn phí tiện ích đã cập nhật mà không cần thay đổi gì. Điều này giúp ích cho ứng dụng của bạn trong tương lai.
Đến đây bạn đã nhìn thấy hai ví dụ về những lợi ích khi sử dụng các Thành phần Material Design độc đáo để thiết kế giao diện người dùng cũng như cách thức để ứng dụng của bạn phù hợp hơn với các nguyên tắc của Material Design. Đừng quên rằng bạn có thể khám phá các Thành phần Material Design khác được cung cấp cho Android tại trang web này.
4. Biểu tượng
Biểu tượng (icon) là các ký hiệu giúp người dùng hiểu giao diện người dùng qua việc truyền đạt chức năng dự định dưới dạng hình ảnh trực quan. Biểu tượng thường lấy cảm hứng từ đối tượng trong thế giới thực người dùng thường bắt gặp. Thiết kế biểu tượng thường giảm tối đa mức độ chi tiết để tạo cảm giác quen thuộc cho người dùng. Ví dụ: trong thế giới thực, bút chì dùng để viết, vì thế biểu tượng bút chì thường biểu thị việc tạo, thêm hoặc chỉnh sửa một mục.
Ảnh chụp của Angelina Litvin trên Unsplash |
Đôi khi biểu tượng còn được liên kết với các đối tượng đã lỗi thời trong thế giới thực, chẳng hạn như biểu tượng chiếc đĩa mềm. Đây là biểu tượng phổ biến, biểu thị cho việc lưu tệp hoặc bản ghi cơ sở dữ liệu; tuy nhiên, đĩa mềm chỉ phổ biến vào những năm 1970 và dần biến mất sau năm 2000. Tuy nhiên, biểu tượng này vẫn được sử dụng cho đến ngày nay, là một minh chứng cho thấy một hình ảnh mạnh mẽ có thể vượt trên dạng thức vật chất của nó.
Ảnh chụp của Vincent Botta trên UnSplash |
Biểu tượng đại diện trong ứng dụng
Đối với biểu tượng trong ứng dụng, thay vì cung cấp phiên bản hình ảnh bitmap riêng cho từng mật độ màn hình, bạn nên sử dụng các vectơ vẽ được. Vectơ vẽ được thường được biểu thị dưới dạng tệp XML. Tệp này lưu trữ hướng dẫn về cách tạo hình ảnh thay vì lưu các pixel thực tế tạo nên hình ảnh đó. Bạn có thể tăng hoặc giảm tỷ lệ các vectơ vẽ được mà không ảnh hưởng đến chất lượng hình ảnh hay làm tăng kích thước tệp.
Biểu tượng được cung cấp
Material Design cung cấp một số biểu tượng được sắp xếp trong các danh mục phổ biến, đáp ứng được hầu hết nhu cầu của bạn. Xem danh sách biểu tượng.
Các biểu tượng này có thể được tô màu và vẽ bằng một trong 5 giao diện (Lấp đầy, Đường viền, Bo tròn góc, Hai tông màu và Sắc cạnh).
Lấp đầy (filled) | Đường viền (outlined) | Bo tròn góc (rounded) | Hai tông màu (two-tone) | Sắc cạnh (sharp) |
Thêm biểu tượng
Trong nhiệm vụ này, bạn sẽ thêm vào ứng dụng 3 biểu tượng vectơ vẽ được sau đây:
- Biểu tượng bên cạnh trường văn bản chi phí dịch vụ
- Biểu tượng bên cạnh câu hỏi về chất lượng dịch vụ
- Biểu tượng bên cạnh lời nhắc làm tròn tiền boa
Dưới đây là ảnh chụp màn hình của phiên bản hoàn thiện của ứng dụng. Sau khi thêm các biểu tượng, bạn sẽ điều chỉnh bố cục cho phù hợp với vị trí của các biểu tượng này. Hãy để ý việc các trường và nút tính toán được dịch chuyển sang phải khi bổ sung các biểu tượng này.
Thêm thành phần vectơ vẽ được
Bạn có thể trực tiếp sử dụng Asset Studio trong Android Studio để tạo những biểu tượng này dưới dạng vectơ vẽ được.
- Mở thẻ Resource Manager (Trình quản lý tài nguyên) ở bên trái của cửa sổ ứng dụng.
- Nhấp vào biểu tượng + rồi chọn Vector Asset (Thành phần vectơ).
- Đối với Asset Type (Loại thành phần), hãy nhớ chọn nút chọn gắn nhãn Clip Art (Hình mẫu).
- Nhấp vào nút bên cạnh Clip Art: (Hình mẫu:) để chọn một hình mẫu khác. Khi lời nhắc xuất hiện, hãy nhập "call made" ("thực hiện cuộc gọi") vào cửa sổ đang hiện. Bạn sẽ sử dụng biểu tượng mũi tên này cho chế độ làm tròn tiền boa. Chọn biểu tượng này rồi nhấp OK.
- Đổi tên biểu tượng thành ic_round_up. (Bạn nên sử dụng tiền tố ic_ khi đặt tên tệp biểu tượng.) Bạn có thể để Size (kích thước) là 24 dp x 24 dp và Color (màu) là màu đen 000000.
- Nhấp vào Tiếp theo.
- Chấp nhận vị trí thư mục mặc định rồi nhấp vào Finish (Hoàn tất).
- Lặp lại các bước từ 2 đến 7 cho hai biểu tượng còn lại:
- Biểu tượng câu hỏi về chất lượng dịch vụ: Tìm biểu tượng "room service" ("dịch vụ phòng") rồi lưu lại thành
ic_service
. - Biểu tượng chi phí dịch vụ: Tìm kiếm biểu tượng "store" ("cửa hàng") rồi lưu lại thành
ic_store
.
- Sau khi hoàn tất, giao diện Resource Manager (Trình quản lý tài nguyên) sẽ trông như ảnh chụp màn hình dưới đây. Bạn cũng có 3 tệp vectơ vẽ được này (
ic_round_up
,ic_service
vàic_store
) trong thư mụcres/drawable
.
Hỗ trợ phiên bản Android cũ
Bạn vừa thêm các vectơ vẽ được vào ứng dụng, nhưng bạn phải lưu ý rằng vectơ vẽ được chỉ được hỗ trợ trên nền tảng Android 5.0 (cấp độ API 21) trở lên.
Dựa trên cách thiết lập dự án, phiên bản SDK tối thiểu cho ứng dụng Tip Time là API 19. Tức là ứng dụng có thể hoạt động trên các thiết bị Android chạy nền tảng Android phiên bản 19 trở lên.
Để ứng dụng hoạt động trên các phiên bản Android cũ hơn (còn gọi là khả năng tương thích ngược), hãy thêm thành phần vectorDrawables
vào tệp build.gradle
của ứng dụng. Nhờ vậy, bạn có thể sử dụng vectơ vẽ được trên các phiên bản nền tảng thấp hơn API 21 mà không cần chuyển đổi sang PNG khi xây dựng dự án. Tìm hiểu thêm chi tiết tại đây.
app/build.gradle
android {
defaultConfig {
...
vectorDrawables.useSupportLibrary = true
}
...
}
Khi dự án đã được định cấu hình chính xác, bây giờ bạn có thể chuyển sang phần thêm biểu tượng vào bố cục.
Chèn biểu tượng và thành phần vị trí
Bạn sẽ sử dụng ImageViews
để hiện biểu tượng trong ứng dụng. Đây là giao diện người dùng hoàn thiện của bạn.
- Mở bố cục
activity_main.xml
. - Đầu tiên, hãy đặt biểu tượng cửa hàng bên cạnh trường văn bản chi phí dịch vụ. Chèn một
ImageView
mới làm thành phần con đầu tiên củaConstraintLayout
, trướcTextInputLayout
.
<androidx.constraintlayout.widget.ConstraintLayout
...>
<ImageView
android:layout_width=""
android:layout_height=""
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/cost_of_service"
...
- Thiết lập các thuộc tính thích hợp trên
ImageView
để giữ biểu tượngic_store
. Đặt mã nhận dạng thànhicon_cost_of_service
. Đặt thuộc tínhapp:srcCompat
thành tài nguyên có thể vẽ@drawable/ic_store
và bạn sẽ thấy bản xem trước của biểu tượng này bên cạnh dòng XML đó.
Ngoài ra, hãy đặt android:importantForAccessibility="no"
vì hình ảnh này chỉ dùng để trang trí.
<ImageView
android:id="@+id/icon_cost_of_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_store" />
Theo dự kiến, sẽ có lỗi trên ImageView
vì khung nhìn chưa được ràng buộc phù hợp. Bạn sẽ khắc phục lỗi này trong bước tiếp theo.
- Xác định vị trí
icon_cost_of_service
trong 2 bước. Trước tiên, hãy thêm điều kiện ràng buộc vàoImageView
(bước này), sau đó cập nhật điều kiện ràng buộc trênTextInputLayout
ở bên cạnh (bước 5). Sơ đồ này thể hiện cách thiết lập điều kiện ràng buộc.
Trên ImageView
, bạn muốn cạnh bắt đầu của ứng dụng ràng buộc với cạnh bắt đầu của khung nhìn gốc (app:layout_constraintStart_toStartOf="parent"
).
Biểu tượng xuất hiện ở giữa theo chiều dọc so với trường văn bản bên cạnh, vì vậy, hãy ràng buộc phần trên cùng của ImageView
(layout_constraintTop_toTopOf
) này với phần trên cùng của trường văn bản. Cố định phần đáy của ImageView
(layout_constraintBottom_toBottomOf
) này phần đáy của trường văn bản. Để tham chiếu đến trường văn bản, hãy sử dụng mã nhận dạng tài nguyên @id/cost_of_service
. Khi 2 điều kiện ràng buộc được áp dụng trên một tiện ích trong cùng một kích thước (chẳng hạn như ràng buộc trên cùng và dưới cùng), các điều kiện ràng buộc sẽ được mặc định áp dụng như nhau. Kết quả là biểu tượng này sẽ được căn giữa theo chiều dọc, tương quan với trường chi phí dịch vụ.
<ImageView
android:id="@+id/icon_cost_of_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_store"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/cost_of_service"
app:layout_constraintBottom_toBottomOf="@id/cost_of_service" />
Biểu tượng và trường văn bản vẫn còn chồng chéo trong chế độ xem Design (Thiết kế). Tình trạng này sẽ được khắc phục trong bước tiếp theo.
- Trước khi thêm biểu tượng, trường văn bản nằm ở vị trí bắt đầu của thành phần mẹ. Bây giờ, cần dịch chuyển biểu tượng sang phải. Hãy cập nhật các điều kiện ràng buộc trên trường văn bản
cost_of_service
theo tương quan vớiicon_cost_of_service
.
Cạnh bắt đầu của TextInputLayout
phải được cố định với cạnh cuối của ImageView
(@id/icon_cost_of_service
). Để thêm khoảng cách giữa hai khung nhìn này, hãy thêm khoảng cách lề bắt đầu 16dp
cho TextInputLayout
.
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/cost_of_service"
...
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/icon_cost_of_service">
<com.google.android.material.textfield.TextInputEditText ... />
</com.google.android.material.textfield.TextInputLayout>
Sau khi hoàn thành tất cả thay đổi này, biểu tượng phải được đặt đúng vị trí ở bên cạnh trường văn bản.
- Tiếp theo, hãy chèn biểu tượng chuông dịch vụ bên cạnh "How was the service?" ("Bạn thấy thế nào về dịch vụ?")
TextView
. Tuy bạn có thể khai báoImageView
ở bất kỳ đâu trongConstraintLayout
, nhưng bố cục XML sẽ dễ đọc hơn nếu bạn chènImageView
mới vào bố cục XML sauTextInputLayout
nhưng trướcTextView
service_question
.
Với ImageView
mới, hãy gán mã nhận dạng tài nguyên @+id/icon_service_question
. Đặt các điều kiện ràng buộc phù hợp trên ImageView
và phần câu hỏi dịch vụ TextView
.
Ngoài ra, hãy thêm khoảng cách lề trên (top margin) 16dp
cho service_question TextView
để có thêm khoảng trống theo chiều dọc giữa phần câu hỏi dịch vụ và trường văn bản về chi phí dịch vụ ở phía trên.
...
<ImageView
android:id="@+id/icon_service_question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_service"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/service_question"
app:layout_constraintBottom_toBottomOf="@id/service_question" />
<TextView
android:id="@+id/service_question"
...
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="@id/cost_of_service"
app:layout_constraintTop_toBottomOf="@id/cost_of_service"/>
...
- Lúc này, chế độ xem Design (Thiết kế) sẽ có dạng như sau. Trường chi phí dịch vụ và câu hỏi về chất lượng dịch vụ (cũng như các biểu tượng tương ứng của dịch vụ) trông rất tuyệt, nhưng hiện không còn chỗ để hiện các nút chọn. Những nút chọn này chưa được căn chỉnh theo chiều dọc với phần nội dung ở phía trên.
- Điều chỉnh vị trí của các nút chọn bằng cách dịch chuyển các nút này sang phải, bên dưới câu hỏi về chất lượng dịch vụ. Tức là bạn cần cập nhật điều kiện ràng buộc cho
RadioGroup
. Ràng buộc cạnh bắt đầu củaRadioGroup
với cạnh bắt đầu củaservice_question
TextView
. Tất cả thuộc tính khác trênRadioGroup
có thể giữ nguyên.
...
<RadioGroup
android:id="@+id/tip_options"
...
app:layout_constraintStart_toStartOf="@id/service_question">
...
- Sau đó, hãy tiếp tục thêm biểu tượng
ic_round_up
vào bố cục bên cạnh nút chuyển "Round up tip?" ("Làm tròn tiền boa?"). Hãy thử tự thực hiện việc này và nếu gặp khó khăn, bạn có thể tham khảo đoạn mã XML dưới đây. Bạn có thể gán mã nhận dạng tài nguyênicon_round_up
choImageView
mới. - Trong bố cục XML, hãy chèn một
ImageView
mới sauRadioGroup
nhưng trước tiện íchSwitchMaterial
. - Gán mã nhận dạng tài nguyên
icon_round_up
choImageView
rồi đặtsrcCompat
thành biểu tượng@drawable/ic_round_up
có thể vẽ. Ràng buộc phần bắt đầu củaImageView
với phần bắt đầu của thành phần mẹ, đồng thời căn giữa biểu tượng này theo chiều dọc so vớiSwitchMaterial
. - Cập nhật
SwitchMaterial
bên cạnh biểu tượng này và thiết lập khoảng cách lề bắt đầu (start margin)16dp
. Kết quả XML choicon_round_up
vàround_up_switch
sẽ có dạng như sau.
...
<ImageView
android:id="@+id/icon_round_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_round_up"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/round_up_switch"
app:layout_constraintBottom_toBottomOf="@id/round_up_switch" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/round_up_switch"
...
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/icon_round_up" />
...
- Chế độ xem Design (Thiết kế) sẽ có dạng như sau. Cả ba biểu tượng đều được đặt đúng vị trí.
- Nếu so sánh với ảnh chụp màn hình ứng dụng hoàn thiện, bạn sẽ thấy nút tính tiền boa cũng được dịch chuyển theo chiều dọc tương ứng với trường chi phí dịch vụ, câu hỏi về chất lượng dịch vụ, tuỳ chọn trên nút chọn và câu hỏi làm tròn tiền boa. Bạn có thể làm được điều này bằng cách ràng buộc phần bắt đầu của nút tính tiền với phần bắt đầu của
round_up_switch
. Đồng thời, thêm khoảng cách lề dọc8dp
giữa nút tính tiền và nút chuyển ở phía trên.
...
<Button
android:id="@+id/calculate_button"
...
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="@id/round_up_switch" />
...
- Cuối cùng nhưng không kém phần quan trọng, hãy định vị
tip_result
bằng cách thêm khoảng cách lề trên (top margin)8dp
choTextView
.
...
<TextView
android:id="@+id/tip_result"
...
android:layout_marginTop="8dp" />
...
- Thật sự phải qua rất nhiều bước! Bạn đã làm rất tốt theo từng bước. Bạn cần rất chú ý đến từng chi tiết để có thể chỉnh sửa các thành phần bố cục một cách chính xác. Nhờ vậy kết quả cuối cùng sẽ đẹp hơn rất nhiều! Hãy chạy ứng dụng và ứng dụng sẽ có dạng như ảnh chụp màn hình dưới đây. Việc căn chỉnh theo chiều dọc và tăng khoảng cách giữa các thành phần giúp cho những thành phần này không bị chồng chéo lên nhau.
Vẫn chưa xong đâu! Bạn có thể thấy kích thước và màu phông chữ của phần câu hỏi về chất lượng dịch vụ và giá trị tiền boa đang khác với phần văn bản trong các nút chọn và nút chuyển. Nhiệm vụ tiếp theo sẽ giúp tạo ra tính nhất quán bằng cách sử dụng tính năng định kiểu và thiết lập giao diện (theme).
5. Kiểu và giao diện
Kiểu (style) là một tập hợp giá trị thuộc tính của khung nhìn cho một loại tiện ích nào đó. Ví dụ: kiểu cho TextView
có thể chỉ định màu phông chữ, kích thước phông chữ và màu nền, v.v. Bằng cách trích xuất các thuộc tính này vào một kiểu, bạn có thể dễ dàng áp dụng kiểu này cho nhiều khung nhìn trong bố cục và lưu giữ kiểu này tại một nơi duy nhất.
Trong nhiệm vụ này, trước tiên bạn sẽ tạo kiểu cho khung nhìn văn bản, nút chọn và tiện ích dạng nút chuyển.
Tạo kiểu
- Tạo một tệp mới có tên
styles.xml
trong thư mục res > values nếu chưa có. Tạo tệp bằng cách nhấp chuột phải vào thư mục values rồi chọn New (Mới) > Values Resource File (Tệp tài nguyên giá trị). Đặt tên tệp làstyles.xml
. Tệp mới sẽ có những nội dung sau đây.
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>
- Tạo một kiểu
TextView
mới giúp văn bản xuất hiện nhất quán trong toàn bộ ứng dụng. Xác định kiểu này một lần trongstyles.xml
, sau đó bạn có thể áp dụng kiểu đó cho tất cảTextViews
trong bố cục. Tuy có thể định nghĩa một kiểu từ đầu, nhưng bạn cũng có thể mở rộng một kiểuTextView
hiện có trong thư viện MDC.
Khi định kiểu cho một thành phần nào đó, thường thì bạn nên mở rộng từ kiểu gốc của loại tiện ích bạn đang sử dụng. Việc này thực sự quan trọng vì hai lý do. Thứ nhất, việc này giúp đảm bảo tất cả giá trị mặc định quan trọng được đặt trên thành phần của bạn. Thứ hai, kiểu của bạn sẽ tiếp tục kế thừa mọi thay đổi sau này đối với kiểu gốc.
Bạn có thể tuỳ ý đặt tên cho kiểu của mình, nhưng nên đặt theo quy ước dưới đây. Nếu bạn kế thừa từ một kiểu Material gốc, hãy đặt tên kiểu dưới dạng song song bằng cách thay thế MaterialComponents
bằng tên ứng dụng (TipTime
). Lúc này, các thay đổi của bạn sẽ được chuyển vào không gian tên riêng, giúp loại bỏ khả năng xảy ra xung đột sau này khi các Thành phần Material đưa ra kiểu mới. Ví dụ:
Tên kiểu của bạn: Widget.TipTime.TextView
, kế thừa từ kiểu gốc: Widget.MaterialComponents.TextView
.
Thêm thông tin này vào tệp styles.xml
ở giữa thẻ mở và thẻ đóng resources
.
<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
</style>
- Thiết lập kiểu
TextView
để ghi đè các thuộc tính sau:android:minHeight,android:gravity,
vàandroid:textAppearance.
android:minHeight
đặt ra chiều cao tối thiểu là 48 dp trên TextView
. Theo nguyên tắc của Material Design, chiều cao tối thiểu cho một hàng bất kỳ phải là 48 dp.
Bạn có thể căn chỉnh văn bản trong TextView
vào giữa theo chiều dọc bằng cách đặt thuộc tính android:gravity
. (Xem ảnh chụp màn hình dưới đây.) Thuộc tính Gravity (Trọng tâm) giúp kiểm soát cách nội dung tự định vị trong khung nhìn. Vì nội dung văn bản thực tế không chiếm toàn bộ chiều cao 48 dp nên giá trị center_vertical
sẽ căn giữa văn bản bên trong TextView
theo chiều dọc (nhưng không thay đổi vị trí chiều ngang). Có thể kể đến một số giá trị gravity (lực hấp dẫn) khác như center
, center_horizontal
, top
và bottom
. Bạn có thể thử các giá trị gravity (lực hấp dẫn) này để xem hiệu ứng trên văn bản.
Đặt giá trị cho thuộc tính giao diện văn bản (text appearance) thành ?attr/textAppearanceBody1
. TextAppearance là tập hợp các kiểu được tạo sẵn về kích thước văn bản, phông chữ và các thuộc tính văn bản khác. Để biết thêm về các giao diện văn bản khác do Material cung cấp, hãy xem danh sách kiểu chữ này.
<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
<item name="android:minHeight">48dp</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
- Áp dụng kiểu
Widget.TipTime.TextView
choservice_question
TextView
bằng cách thêm thuộc tính định kiểu trên mỗiTextView
trongactivity_main.xml
.
<TextView
android:id="@+id/service_question"
style="@style/Widget.TipTime.TextView"
... />
Trước khi tạo kiểu, TextView
trông như dưới đây với phông chữ cỡ nhỏ và màu xám:
Sau khi thêm kiểu, TextView
sẽ có dạng như sau. Bây giờ TextView
này đã trông nhất quán hơn với phần bố cục còn lại.
- Áp dụng kiểu
Widget.TipTime.TextView
tương tự chotip_result
TextView
.
<TextView
android:id="@+id/tip_result"
style="@style/Widget.TipTime.TextView"
... />
- Bạn nên áp dụng cùng một kiểu văn bản cho nhãn văn bản trên nút chuyển. Tuy nhiên, bạn không thể đặt kiểu
TextView
cho tiện íchSwitchMaterial
. Chỉ có thể áp dụng kiểuTextView
trênTextViews
. Do đó, hãy tạo một kiểu mới cho nút chuyển đó. Các thuộc tính này giống nhau vềminHeight
,gravity
vàtextAppearance
. Điểm khác biệt ở đây thể hiện ở tên kiểu vừa tạo và tên của kiểu gốc vì bạn đang kế thừa kiểu này từ kiểuSwitch
trong thư viện MDC. Tên kiểu của bạn cũng phải phản ánh tên của kiểu gốc.
Tên kiểu của bạn: Widget.TipTime.CompoundButton.Switch
, kế thừa từ kiểu gốc: Widget.MaterialComponents.CompoundButton.Switch.
<style name="Widget.TipTime.CompoundButton.Switch" parent="Widget.MaterialComponents.CompoundButton.Switch">
<item name="android:minHeight">48dp</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
Bạn cũng có thể chỉ định các thuộc tính bổ sung dành riêng cho các nút chuyển trong kiểu này, nhưng không nhất thiết phải làm điều đó.
- Văn bản của nút chọn là phần cuối cùng bạn cần kiểm tra để đảm bảo tính nhất quán của văn bản. Bạn không thể áp dụng kiểu
TextView
hoặcSwitch
cho tiện íchRadioButton
. Thay vào đó, bạn phải tạo kiểu mới cho các nút chọn. Bạn có thể mở rộng từ kiểuRadioButton
của thư viện MDC.
Khi tạo kiểu này, hãy thêm một số khoảng đệm giữa văn bản nút chọn và hình ảnh vòng tròn. paddingStart
là một thuộc tính mới mà bạn chưa từng sử dụng. Khoảng đệm là khoảng không gian giữa nội dung của một khung nhìn và đường biên của khung nhìn đó. Thuộc tính paddingStart
thiết lập khoảng đệm ở phần bắt đầu của mỗi thành phần. Hãy xem sự khác biệt giữa paddingStart
0dp và 8dp của trên nút chọn.
<style name="Widget.TipTime.CompoundButton.RadioButton"
parent="Widget.MaterialComponents.CompoundButton.RadioButton">
<item name="android:paddingStart">8dp</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
- (Không bắt buộc) Tạo tệp
dimens.xml
để cải thiện khả năng quản lý các giá trị thường dùng. Bạn có thể tạo tệp này theo cách tương tự như cho tệpstyles.xml
ở trên. Chọn thư mụcvalues, nhấp chuột phải rồi chọn New (Mới) > Values Resource File (Tệp tài nguyên giá trị).
Trong ứng dụng nhỏ này, bạn đã lặp lại thao tác cài đặt chiều cao tối thiểu hai lần. Giờ thì việc này có thể quản lý dễ dàng, nhưng nếu có 4, 6, 10 thành phần trở lên cần thực hiện thao tác này thì việc kiểm soát sẽ trở nên phức tạp. Việc ghi nhớ để thay đổi lần lượt tất cả thành phần này là một công việc rất tẻ nhạt và thường gặp lỗi. Bạn có thể tạo một tệp tài nguyên trợ giúp khác trong res > values có tên dimens.xml
chứa các kích thước phổ biến phân biệt theo tên. Khi chuẩn hoá các giá trị dùng chung dưới dạng các kích thước có tên, bạn có thể quản lý ứng dụng của mình dễ dàng hơn. Tip Time là một ứng dụng nhỏ nên không cần thực hiện bước tuỳ chọn này. Tuy nhiên, với các ứng dụng trong môi trường sản xuất phức tạp hơn và cần phải hợp tác với đội ngũ thiết kế, dimens.xml
cho phép bạn thay đổi các giá trị này dễ dàng hơn.
dimens.xml
<resources>
<dimen name="min_text_height">48dp</dimen>
</resources>
Bạn sẽ cập nhật tệp styles.xml
để sử dụng @dimen/min_text_height
thay vì trực tiếp khai báo giá trị 48dp
.
...
<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
<item name="android:minHeight">@dimen/min_text_height</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
...
Thêm kiểu vào giao diện
Có thể thấy rằng bạn chưa áp dụng kiểu RadioButton
và Switch
mới cho các tiện ích tương ứng. Lý do là bạn sẽ sử dụng thuộc tính giao diện (theme) để thiết lập kiểu cho radioButtonStyle
và switchStyle
trong giao diện ứng dụng. Hãy cùng xem lại giao diện là gì.
Giao diện (theme) là một tập hợp các tài nguyên có tên (gọi là thuộc tính giao diện), cho phép tham chiếu sau này về kiểu, bố cục, v.v. Bạn có thể chỉ định giao diện cho toàn bộ ứng dụng, hoạt động hoặc hệ phân cấp khung hiển thị – chứ không chỉ cho từng View
riêng lẻ. Ở bước trước, bạn đã sửa đổi giao diện ứng dụng trong themes.xml
bằng cách đặt các thuộc tính giao diện như colorPrimary
và colorSecondary
, áp dụng trên toàn bộ ứng dụng cũng như các thành phần của ứng dụng.
Bạn có thể sử dụng các thuộc tính giao diện khác như radioButtonStyle
và switchStyle
. Tài nguyên định kiểu mà bạn cung cấp cho các thuộc tính giao diện này sẽ được áp dụng cho mọi nút chọn và mọi nút chuyển trong hệ phân cấp khung nhìn đang áp dụng giao diện đó.
Ngoài ra, có một thuộc tính giao diện cho textInputStyle
, trong đó tài nguyên định kiểu được chỉ định sẽ áp dụng cho tất cả trường nhập văn bản trong ứng dụng. Để TextInputLayout
xuất hiện dưới dạng trường văn bản có đường viền (như trong nguyên tắc Material Design), bạn có thể sử dụng kiểu OutlinedBox
theo định nghĩa trong thư viện MDC dưới dạng Widget.MaterialComponents.TextInputLayout.OutlinedBox
. Đây là kiểu bạn sẽ sử dụng.
- Chỉnh sửa tệp
themes.xml
để giao diện tham chiếu đến các kiểu mong muốn. Việc thiết lập thuộc tính giao diện được thực hiện tương tự như cách khai báo các thuộc tính giao diệncolorPrimary
vàcolorSecondary
trong một lớp học lập trình trước. Tuy nhiên, lần này các thuộc tính giao diện liên quan làtextInputStyle
,radioButtonStyle
vàswitchStyle
. Bạn sẽ sử dụng các kiểu mà bạn đã tạo trước đây choRadioButton
vàSwitch
cùng với kiểu cho trường văn bảnOutlinedBox
của Material.
Sao chép nội dung sau vào res/values/themes.xml
trong thẻ định kiểu cho giao diện ứng dụng.
<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
<item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
<item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
- Tệp
res/values/themes.xml
của bạn sẽ có dạng như dưới đây. Bạn có thể thêm ghi chú (comment) trong tệp XML nếu muốn (biểu thị bằng cặp dấu<!-
và-->
).
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.TipTime" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
...
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Text input fields -->
<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
<!-- Radio buttons -->
<item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
<!-- Switches -->
<item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
</style>
</resources>
- Đừng quên thực hiện thay đổi tương tự cho giao diện tối trong themes.xml (night) (themes.xml (ban đêm)). Tệp
res/values-night/themes.xml
của bạn sẽ có dạng như dưới đây.
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Application theme for dark theme. -->
<style name="Theme.TipTime" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
...
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Text input fields -->
<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
<!-- For radio buttons -->
<item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
<!-- For switches -->
<item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
</style>
</resources>
- Chạy ứng dụng và xem sự thay đổi. Kiểu
OutlinedBox
cho trường văn bản trông đẹp hơn, đồng thời, tất cả văn bản giờ đây đều nhất quán với nhau!
6. Nâng cao trải nghiệm người dùng
Khi gần hoàn thành ứng dụng, bạn nên kiểm thử ứng dụng không chỉ theo quy trình công việc dự kiến mà còn theo nhiều kịch bản người dùng khác. Có thể thấy rằng một số thay đổi nhỏ về mã có thể giúp cải thiện đáng kể trải nghiệm người dùng.
Xoay thiết bị
- Xoay thiết bị sang chế độ ngang. Trước hết, bạn cần bật chế độ cài đặt Tự động xoay. (Chế độ này nằm trong phần Cài đặt nhanh của thiết bị hoặc trong tuỳ chọn Cài đặt > Màn hình > Nâng cao > Tự động xoay màn hình.)
Trong trình mô phỏng, bạn có thể sử dụng các tuỳ chọn mô phỏng (nằm ở phía trên bên phải ngay cạnh thiết bị) để xoay màn hình sang phải hoặc trái.
- Bạn sẽ nhận thấy một số thành phần giao diện người dùng, bao gồm cả nút lệnh Calculate (Tính toán), sẽ bị cắt đi. Rõ ràng là việc này khiến bạn không dùng được ứng dụng!
- Để khắc phục lỗi này, hãy thêm
ScrollView
xung quanhConstraintLayout
. Tệp XML của bạn sẽ có dạng như sau.
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
tools:context=".MainActivity">
...
</ConstraintLayout>
</ScrollView>
- Chạy và kiểm tra lại ứng dụng. Khi xoay thiết bị sang chế độ ngang, bạn có thể cuộn giao diện người dùng để sử dụng nút tính tiền và xem kết quả tiền boa. Bản sửa lỗi này không chỉ hữu ích khi dùng chế độ ngang mà còn áp dụng tốt trên các thiết bị Android đa dạng kích thước. Bây giờ, người dùng có thể cuộn bố cục bất kể kích thước màn hình thiết bị như thế nào.
Ẩn bàn phím bằng phím Enter
Có thể bạn sẽ để ý thấy sau khi nhập chi phí dịch vụ, bàn phím vẫn còn ở đó. Việc này có thể hơi phiền phức khi bạn phải tự mình ẩn bàn phím mỗi lần cần truy cập nút tính toán. Thay vào đó, hãy khiến bàn phím tự động ẩn khi nhấn phím Enter.
Với trường văn bản, bạn có thể định nghĩa một trình nghe phím (key listener) để phản hồi sự kiện khi nhấn vào một số phím nhất định. Mọi tuỳ chọn nhập liệu trên bàn phím đều có một mã phím tương ứng, bao gồm cả phím Enter
. Hãy lưu ý rằng bàn phím ảo còn được gọi là bàn phím mềm (trái với bàn phím thực).
Trong nhiệm vụ này, hãy thiết lập một trình nghe phím trên trường văn bản để theo dõi khi nhấn phím Enter
. Khi phát hiện sự kiện đó, hãy bắt đầu ẩn bàn phím.
- Sao chép và dán phương thức trợ giúp này vào lớp
MainActivity
. Bạn có thể chèn phương thức này ngay phía trước dấu ngoặc nhọn đóng của lớpMainActivity
.handleKeyEvent()
là một hàm trợ giúp riêng tư, giúp ẩn bàn phím ảo khi tham số nhậpkeyCode
bằngKeyEvent.
KEYCODE_ENTER
. InputMethodManager sẽ kiểm soát xem bàn phím mềm sẽ xuất hiện hay bị ẩn, đồng thời cho phép người dùng lựa chọn bàn phím mềm nào sẽ xuất hiện. Phương thức này sẽ trả về giá trị đúng (true) nếu sự kiện bàn phím đã được xử lý và trả về sai (false) nếu xử lý không thành công.
MainActivity.kt
private fun handleKeyEvent(view: View, keyCode: Int): Boolean {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
// Hide the keyboard
val inputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
return true
}
return false
}
- Bây giờ, hãy gắn một trình nghe phím vào tiện ích
TextInputEditText
. Hãy lưu ý bạn có thể truy cập tiện íchTextInputEditText
thông qua đối tượng liên kết dưới dạngbinding.costOfServiceEditText
.
Gọi phương thức setOnKeyListener()
qua costOfServiceEditText
rồi truyền vào OnKeyListener
. Thao tác này tương tự như cách thiết lập trình nghe lượt nhấp trên nút tính tiền trong ứng dụng có binding.calculateButton.setOnClickListener { calculateTip() }
.
Mã để thiết lập trình nghe phím trên một khung nhìn sẽ phức tạp hơn một chút, nhưng nhìn chung ý tưởng là OnKeyListener
có một phương thức onKey()
được kích hoạt khi người dùng nhấn phím. Phương thức onKey()
gồm 3 tham số đầu vào: khung nhìn, mã phím được nhấn và sự kiện phím nhấn (bạn chỉ cần gọi sự kiện này dưới dạng "_
"). Khi phương thức onKey()
được gọi, bạn sẽ gọi phương thức handleKeyEvent()
và truyền vào các tham số khung nhìn và mã phím. Cú pháp để lập trình là: view, keyCode, _ -> handleKeyEvent(view, keyCode)
. Biểu thức này được gọi là lambda và bạn sẽ tìm hiểu thêm về lambda trong một bài sau.
Thêm mã để thiết lập trình nghe phím trên trường văn bản trong phương thức onCreate()
của hoạt động. Lý do là trình nghe phím nên được đính kèm ngay khi bố cục được tạo và trước khi người dùng bắt đầu tương tác với hoạt động.
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(binding.root)
binding.calculateButton.setOnClickListener { calculateTip() }
binding.costOfServiceEditText.setOnKeyListener { view, keyCode, _ -> handleKeyEvent(view, keyCode)
}
}
- Hãy kiểm tra để đảm bảo các thay đổi hoạt động tốt trên ứng dụng. Chạy ứng dụng rồi nhập chi phí dịch vụ. Nhấn phím Enter trên bàn phím và bàn phím mềm sẽ bị ẩn đi.
Kiểm thử ứng dụng dùng TalkBack
Bạn học khoá này vì muốn xây dựng một ứng dụng tiếp cận càng nhiều người dùng càng tốt. Một số người dùng có thể sử dụng TalkBack để truy cập và thao tác trên ứng dụng của bạn. TalkBack là trình đọc màn hình của Google có sẵn trên thiết bị Android. TalkBack cung cấp tính năng phản hồi bằng giọng nói để người dùng có thể sử dụng thiết bị mà không cần nhìn màn hình.
Khi TalkBack đang bật, hãy đảm bảo rằng người dùng có thể hoàn tất việc tính tiền boa trong ứng dụng của bạn.
- Bật Talkback trên thiết bị của bạn theo hướng dẫn này.
- Quay lại ứng dụng Tip Time (Tiền boa).
- Dùng TalkBack để khám phá ứng dụng theo hướng dẫn này. Vuốt sang phải để di chuyển lần lượt qua từng thành phần màn hình rồi vuốt sang trái để di chuyển theo hướng ngược lại. Nhấn đúp vào vị trí bất kỳ để chọn. Kiểm tra để đảm bảo rằng bạn có thể truy cập vào mọi thành phần của ứng dụng bằng cách vuốt.
- Đảm bảo người dùng Talkback có thể di chuyển đến từng mục trên màn hình, nhập chi phí dịch vụ, thay đổi cách tính tiền boa, tính tiền boa và nghe được số tiền boa đã tính. Hãy nhớ rằng bạn không cung cấp phản hồi bằng giọng nói cho các biểu tượng vì bạn đã đánh dấu các biểu tượng đó là
importantForAccessibility="no"
.
Để biết thêm thông tin về cách cải thiện khả năng hỗ trợ tiếp cận của ứng dụng, hãy tham khảo các nguyên tắc này và lộ trình học tập này.
(Không bắt buộc) Điều chỉnh sắc thái màu của vectơ vẽ được
Trong nhiệm vụ không bắt buộc này, bạn sẽ phủ màu cho các biểu tượng dựa trên màu chính của giao diện, nhờ đó các biểu tượng sẽ thay đổi giữa giao diện sáng và giao diện tối (như hình dưới đây). Đây là một tính năng bổ sung tuyệt vời cho giao diện người dùng, giúp các biểu tượng thể hiện nhất quán hơn với giao diện ứng dụng.
Như đã đề cập, một trong những ưu điểm của VectorDrawables
so với hình ảnh bitmap là khả năng điều chỉnh tỷ lệ và phủ màu. Bên dưới là mã XML thể hiện biểu tượng chuông. Có 2 thuộc tính màu sắc đặc trưng cần lưu ý là android:tint
và android:fillColor
.
ic_service.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M2,17h20v2L2,19zM13.84,7.79c0.1,-0.24 0.16,-0.51 0.16,-0.79 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2c0,0.28 0.06,0.55 0.16,0.79C6.25,8.6 3.27,11.93 3,16h18c-0.27,-4.07 -3.25,-7.4 -7.16,-8.21z"/>
</vector>
Nếu một sắc thái màu nào đó đang hiện diện, sắc thái màu này sẽ ghi đè lên mọi lệnh fillColor
cho đối tượng có thể vẽ. Trong trường hợp này, thuộc tính giao diện colorControlNormal
sẽ ghi đè màu trắng. colorControlNormal
là màu ở trạng thái "bình thường" (chưa chọn/chưa kích hoạt) của một tiện ích. Hiện tại, màu này là màu xám.
Có một cách để cải thiện hình ảnh cho ứng dụng là phủ màu lên đối tượng có thể vẽ dựa trên màu chính của giao diện ứng dụng. Đối với giao diện sáng, biểu tượng sẽ xuất hiện dưới dạng @color/green
, còn trong giao diện tối, biểu tượng sẽ xuất hiện dưới dạng @color/green_light
, chính là ?attr/colorPrimary
. Việc phủ màu lên đối tượng có thể vẽ dựa trên màu chính của giao diện ứng dụng giúp cho các phần tử trong bố cục trở nên nhất quán và gắn kết hơn. Đồng thời, điều này cũng giúp chúng ta không phải tạo bản sao tập hợp biểu tượng riêng cho giao diện sáng và giao diện tối. Chỉ có 1 tập hợp vectơ vẽ được và sắc thái màu sẽ thay đổi dựa trên thuộc tính giao diện colorPrimary
.
- Thay đổi giá trị của thuộc tính
android:tint
trongic_service.xml
android:tint="?attr/colorPrimary"
Trong Android Studio, biểu tượng này nay đã có sắc thái màu phù hợp.
Giá trị mà thuộc tính giao diện colorPrimary
trỏ đến sẽ thay đổi tuỳ thuộc vào giao diện sáng hay tối.
- Lặp lại các bước trên để thay đổi sắc thái màu cho các vectơ vẽ được khác.
ic_store.xml
<vector ...
android:tint="?attr/colorPrimary">
...
</vector>
ic_round_up.xml
<vector ...
android:tint="?attr/colorPrimary">
...
</vector>
- Chạy ứng dụng. Đảm bảo rằng các biểu tượng thay đổi tuỳ theo giao diện sáng và tối.
- Bước cuối cùng là dọn dẹp, hãy nhớ định dạng lại tất cả tệp mã XML và Kotlin trong ứng dụng.
Xin chúc mừng! Cuối cùng, bạn đã hoàn tất ứng dụng tính tiền boa! Bạn nên tự hào về những gì mình đã làm được. Hy vọng rằng đây chính là nền tảng giúp bạn xây dựng những ứng dụng đẹp mắt và dễ sử dụng hơn nữa!
7. Mã giải pháp
Mã giải pháp cho lớp học lập trình này nằm trong kho lưu trữ GitHub nêu dưới đây.
Để lấy mã cho lớp học lập trình này và mở trong Android Studio, hãy thực hiện các bước sau.
Lấy mã
- Nhấp vào URL được cung cấp. Thao tác này sẽ mở trang GitHub của dự án trong một trình duyệt.
- Kiểm tra để đảm bảo tên nhánh khớp với tên nhánh được chỉ định trong lớp học lập trình. Ví dụ: trong ảnh chụp màn hình sau đây, tên nhánh là main (chính).
- Trên trang GitHub cho dự án này, hãy nhấp vào nút Code (Mã), một cửa sổ bật lên sẽ hiện ra.
- Trong cửa sổ bật lên, nhấp vào nút Download ZIP (Tải tệp ZIP xuống) để lưu dự án vào máy tính. Chờ quá trình tải xuống hoàn tất.
- Xác định vị trí của tệp trên máy tính (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
- Nhấp đúp vào tệp ZIP để giải nén. Thao tác này sẽ tạo một thư mục mới chứa các tệp dự án.
Mở dự án trong Android Studio
- Khởi động Android Studio.
- Trong cửa sổ Welcome to Android Studio (Chào mừng bạn đến với Android Studio), hãy nhấp vào Open (Mở).
Lưu ý: Nếu Android Studio đã mở thì chuyển sang chọn tuỳ chọn File (Tệp) > Open (Mở) trong trình đơn.
- Trong trình duyệt tệp, hãy chuyển đến vị trí của thư mục dự án chưa giải nén (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
- Nhấp đúp vào thư mục dự án đó.
- Chờ Android Studio mở dự án.
- Nhấp vào nút Run (Chạy) để tạo và chạy ứng dụng. Đảm bảo ứng dụng được xây dựng như mong đợi.
8. Tóm tắt
- Sử dụng các Thành phần Material Design nếu có thể nhằm tuân thủ nguyên tắc của Material Design và tăng khả năng tuỳ chỉnh.
- Thêm biểu tượng để cung cấp cho người dùng dấu hiệu trực quan về cách hoạt động của các phần trong ứng dụng.
- Sử dụng
ConstraintLayout
để định vị các thành phần trong bố cục. - Kiểm tra ứng dụng theo một số trường hợp cụ thể (ví dụ: xoay ứng dụng ở chế độ ngang) và cải thiện (nếu cần).
- Chú thích mã để giúp người khác hiểu được phương pháp tiếp cận của bạn khi đọc mã.
- Định dạng lại và dọn dẹp để mã của bạn càng ngắn gọn càng tốt.
9. Tìm hiểu thêm
10. Tự thực hành
- Tiếp nối các lớp học lập trình trước đây, hãy cập nhật ứng dụng chuyển đổi đơn vị nấu ăn để tuân thủ chặt chẽ hơn các nguyên tắc của Material Design bằng cách sử dụng các phương pháp hay nhất mà bạn đã học được trong lớp học lập trình này (chẳng hạn như sử dụng Thành phần Material Design).