Lớp học lập trình này thuộc khoá học Kiến thức cơ bản về Kotlin cho Android. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự. Tất cả lớp học lập trình của khoá học đều được liệt kê trên trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.
Giới thiệu
Lớp học lập trình này hướng dẫn bạn cách sử dụng RecyclerView để hiển thị danh sách các mục. Dựa trên ứng dụng theo dõi giấc ngủ trong loạt lớp học lập trình trước, bạn sẽ tìm hiểu một cách hiệu quả và linh hoạt hơn để hiển thị dữ liệu bằng cách sử dụng RecyclerView với cấu trúc được đề xuất.
Kiến thức bạn cần có
Bạn cần thông thạo:
- Xây dựng giao diện người dùng (UI) cơ bản bằng cách sử dụng một hoạt động, các mảnh và khung hiển thị.
- Điều hướng giữa các mảnh và sử dụng
safeArgsđể truyền dữ liệu giữa các mảnh. - Sử dụng các mô hình hiển thị, nhà máy mô hình hiển thị, các phép biến đổi và
LiveDatacũng như các đối tượng theo dõi của chúng. - Tạo cơ sở dữ liệu
Room, tạo DAO và xác định các thực thể. - Sử dụng coroutine cho các tác vụ cơ sở dữ liệu và các tác vụ chạy trong thời gian dài khác.
Kiến thức bạn sẽ học được
- Cách sử dụng
RecyclerViewvớiAdaptervàViewHolderđể hiển thị danh sách các mục.
Bạn sẽ thực hiện
- Thay đổi ứng dụng TrackMySleepQuality từ bài học trước để sử dụng một
RecyclerViewnhằm hiển thị dữ liệu về chất lượng giấc ngủ.
Trong lớp học lập trình này, bạn sẽ tạo phần RecyclerView của một ứng dụng theo dõi chất lượng giấc ngủ. Ứng dụng này sử dụng cơ sở dữ liệu Room để lưu trữ dữ liệu giấc ngủ theo thời gian.
Ứng dụng theo dõi giấc ngủ khởi đầu có 2 màn hình, được biểu thị bằng các mảnh, như minh hoạ trong hình bên dưới.

Màn hình đầu tiên (xuất hiện ở bên trái) có các nút để bắt đầu và dừng theo dõi. Màn hình này cũng cho thấy tất cả dữ liệu về giấc ngủ của người dùng. Nút Xoá sẽ xoá vĩnh viễn tất cả dữ liệu mà ứng dụng đã thu thập cho người dùng. Màn hình thứ hai (ở bên phải) là màn hình chọn mức đánh giá chất lượng giấc ngủ.
Ứng dụng này sử dụng một cấu trúc đơn giản hoá với một đơn vị điều khiển giao diện người dùng, ViewModel và LiveData. Ứng dụng này cũng sử dụng cơ sở dữ liệu Room để duy trì dữ liệu giấc ngủ.

Danh sách các đêm ngủ xuất hiện trên màn hình đầu tiên có chức năng nhưng không đẹp mắt. Ứng dụng này sử dụng một trình định dạng phức tạp để tạo chuỗi văn bản cho chế độ xem văn bản và các số cho chất lượng. Ngoài ra, thiết kế này không có khả năng mở rộng. Sau khi bạn khắc phục tất cả các vấn đề này trong lớp học lập trình này, ứng dụng cuối cùng sẽ có cùng chức năng và màn hình chính sẽ trông như sau:

Hiển thị danh sách hoặc lưới dữ liệu là một trong những tác vụ giao diện người dùng phổ biến nhất trên Android. Danh sách có thể từ đơn giản đến rất phức tạp. Danh sách khung hiển thị văn bản có thể cho thấy dữ liệu đơn giản, chẳng hạn như danh sách mua sắm. Một danh sách phức tạp, chẳng hạn như danh sách có chú thích về các điểm đến du lịch, có thể cho người dùng thấy nhiều thông tin chi tiết trong một lưới có thể cuộn với các tiêu đề.
Để hỗ trợ tất cả các trường hợp sử dụng này, Android cung cấp tiện ích RecyclerView.

Lợi ích lớn nhất của RecyclerView là rất hiệu quả đối với các danh sách lớn:
- Theo mặc định,
RecyclerViewchỉ hoạt động để xử lý hoặc vẽ các mục đang xuất hiện trên màn hình. Ví dụ: nếu danh sách của bạn có 1.000 phần tử nhưng chỉ có 10 phần tử nhìn thấy được, thìRecyclerViewchỉ dừng lại ở việc vẽ 10 mục trên màn hình. Khi người dùng cuộn,RecyclerViewsẽ tìm ra các mục mới cần xuất hiện trên màn hình và chỉ thực hiện đúng lượng công việc để làm hiển thị những mục đó. - Khi một mục cuộn ra khỏi màn hình, các khung hiển thị của mục đó sẽ được tái chế. Điều này có nghĩa là phần tử này sẽ chứa nội dung mới cuộn lên màn hình. Tính năng này của
RecyclerViewgiúp tiết kiệm rất nhiều thời gian xử lý, đồng thời giúp cuộn danh sách một cách mượt mà. - Khi một mục thay đổi, thay vì vẽ lại toàn bộ danh sách,
RecyclerViewcó thể cập nhật mục đó. Đây là một bước tiến lớn về hiệu suất khi hiển thị danh sách các mục phức tạp!
Trong chuỗi hiển thị bên dưới, bạn có thể thấy rằng một thành phần hiển thị đã được điền dữ liệu, ABC. Sau khi thành phần hiển thị đó ra khỏi màn hình, RecyclerView sẽ sử dụng lại thành phần đó cho dữ liệu mới, XYZ.
Mẫu bộ chuyển đổi
Nếu từng di chuyển giữa các quốc gia sử dụng ổ cắm điện khác nhau, có lẽ bạn biết cách cắm thiết bị vào ổ cắm bằng cách dùng một đầu giắc cắm chuyển đổi. Bộ chuyển đổi cho phép bạn chuyển đổi một loại giắc cắm sang một loại giắc cắm khác, tức là chuyển đổi một giao diện sang một giao diện khác.
Mẫu bộ chuyển đổi trong kỹ thuật phần mềm giúp một đối tượng hoạt động với một API khác. RecyclerView sử dụng một trình chuyển đổi để chuyển đổi dữ liệu ứng dụng thành một thứ mà RecyclerView có thể hiển thị, mà không thay đổi cách ứng dụng lưu trữ và xử lý dữ liệu. Đối với ứng dụng theo dõi giấc ngủ, bạn sẽ tạo một trình chuyển đổi có khả năng điều chỉnh dữ liệu từ cơ sở dữ liệu Room thành một thứ mà RecyclerView biết cách hiển thị mà không cần thay đổi ViewModel.
Triển khai RecyclerView

Để hiển thị dữ liệu trong một RecyclerView, bạn cần có các phần sau:
- Dữ liệu cần hiển thị.
- Một thực thể
RecyclerViewđược xác định trong tệp bố cục của bạn, đóng vai trò là vùng chứa cho các khung hiển thị. - Một bố cục cho một mục dữ liệu.
Nếu tất cả các mục trong danh sách đều giống nhau, bạn có thể sử dụng cùng một bố cục cho tất cả các mục đó, nhưng điều này không bắt buộc. Bố cục mục phải được tạo riêng biệt với bố cục của mảnh, để có thể tạo và điền dữ liệu cho một khung hiển thị mục tại một thời điểm. - Trình quản lý bố cục.
Trình quản lý bố cục xử lý việc sắp xếp (bố cục) các thành phần giao diện người dùng trong một khung hiển thị. - Một ngăn chứa thành phần hiển thị.
Ngăn chứa thành phần hiển thị này mở rộng lớpViewHolder. Nội dung này chứa thông tin về khung hiển thị để hiển thị một mục trong bố cục của mục đó. Trình lưu giữ thành phần hiển thị (view holder) cũng bổ sung thông tin màRecyclerViewsử dụng để di chuyển các thành phần hiển thị xung quanh màn hình một cách hiệu quả. - Một bộ chuyển đổi.
Bộ chuyển đổi này kết nối dữ liệu của bạn vớiRecyclerView. Nó điều chỉnh dữ liệu để có thể hiển thị trong mộtViewHolder.RecyclerViewsử dụng trình chuyển đổi để tìm ra cách hiển thị dữ liệu trên màn hình.
Trong nhiệm vụ này, bạn sẽ thêm một RecyclerView vào tệp bố cục và thiết lập một Adapter để hiển thị dữ liệu về giấc ngủ cho RecyclerView.
Bước 1: Thêm RecyclerView bằng LayoutManager
Trong bước này, bạn sẽ thay thế ScrollView bằng RecyclerView trong tệp fragment_sleep_tracker.xml.
- Tải ứng dụng RecyclerViewFundamentals-Starter xuống từ GitHub.
- Tạo bản dựng và chạy ứng dụng. Lưu ý cách dữ liệu được hiển thị dưới dạng văn bản đơn giản.
- Mở tệp bố cục
fragment_sleep_tracker.xmltrong thẻ Design (Thiết kế) của Android Studio. - Trong ngăn Component Tree (Cây thành phần), hãy xoá
ScrollView. Thao tác này cũng sẽ xoáTextViewbên trongScrollView. - Trong ngăn Palette (Bảng khung hiển thị), hãy di chuyển qua danh sách các loại thành phần ở bên trái để tìm Containers (Vùng chứa), rồi chọn thành phần đó.
- Kéo một
RecyclerViewtừ ngăn Palette (Bảng chế độ xem) sang ngăn Component Tree (Cây thành phần). ĐặtRecyclerViewbên trongConstraintLayout.

- Nếu một hộp thoại mở ra hỏi xem bạn có muốn thêm một phần phụ thuộc hay không, hãy nhấp vào OK để cho phép Android Studio thêm phần phụ thuộc
recyclerviewvào tệp Gradle của bạn. Quá trình này có thể mất vài giây, sau đó ứng dụng của bạn sẽ đồng bộ hoá.

- Mở tệp
build.gradlecủa mô-đun, di chuyển đến cuối và lưu ý đến phần phụ thuộc mới, phần phụ thuộc này sẽ có dạng tương tự như mã dưới đây:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
- Chuyển về
fragment_sleep_tracker.xml. - Trong thẻ Text (Văn bản), hãy tìm mã
RecyclerViewnhư minh hoạ dưới đây:
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />- Gán cho
RecyclerViewidcủasleep_list.
android:id="@+id/sleep_list"- Đặt
RecyclerViewđể chiếm phần còn lại của màn hình bên trongConstraintLayout. Để làm việc này, hãy ràng buộc phần trên cùng củaRecyclerViewvới nút Start (Bắt đầu), phần dưới cùng với nút Clear (Xoá) và mỗi bên với thành phần mẹ. Đặt chiều rộng và chiều cao của bố cục thành 0 dp trong Layout Editor hoặc trong XML bằng mã sau:
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/clear_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stop_button"- Thêm một trình quản lý bố cục vào XML
RecyclerView. MỗiRecyclerViewcần một trình quản lý bố cục để cho biết cách đặt các mục trong danh sách. Android cung cấp mộtLinearLayoutManager, theo mặc định sẽ bố trí các mục trong danh sách dọc gồm các hàng có chiều rộng đầy đủ.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"- Chuyển sang thẻ Design (Thiết kế) và lưu ý rằng các quy tắc hạn chế đã thêm khiến
RecyclerViewmở rộng để lấp đầy không gian có sẵn.

Bước 2: Tạo bố cục mục danh sách và trình giữ chế độ xem văn bản
RecyclerView chỉ là một vùng chứa. Trong bước này, bạn sẽ tạo bố cục và cơ sở hạ tầng cho các mục sẽ xuất hiện bên trong RecyclerView.
Để có được RecyclerView hoạt động nhanh nhất có thể, ban đầu bạn sử dụng một mục danh sách đơn giản chỉ hiển thị chất lượng giấc ngủ dưới dạng một con số. Để làm được điều này, bạn cần một ngăn chứa thành phần hiển thị, TextItemViewHolder. Bạn cũng cần một khung hiển thị, một TextView, cho dữ liệu. (Trong một bước sau, bạn sẽ tìm hiểu thêm về trình giữ chế độ xem và cách bố trí tất cả dữ liệu về giấc ngủ.)
- Tạo một tệp bố cục có tên là
text_item_view.xml. Bạn không cần quan tâm đến việc sử dụng phần tử gốc nào, vì bạn sẽ thay thế mã mẫu. - Trong
text_item_view.xml, hãy xoá tất cả mã đã cho. - Thêm một
TextViewcó khoảng đệm16dpở đầu và cuối, đồng thời có kích thước văn bản là24sp. Đặt chiều rộng khớp với phần tử mẹ và chiều cao bao bọc nội dung. Vì khung hiển thị này xuất hiện bên trongRecyclerView, nên bạn không cần đặt khung hiển thị này bên trongViewGroup.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:textSize="24sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />- Mở
Util.kt. Di chuyển xuống cuối và thêm định nghĩa xuất hiện bên dưới để tạo lớpTextItemViewHolder. Đặt mã ở cuối tệp, sau dấu ngoặc nhọn đóng cuối cùng. Mã này sẽ nằm trongUtil.ktvì trình giữ chế độ xem này chỉ là tạm thời và bạn sẽ thay thế nó sau.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)- Nếu được nhắc, hãy nhập
android.widget.TextViewvàandroidx.recyclerview.widget.RecyclerView.
Bước 3: Tạo SleepNightAdapter
Nhiệm vụ cốt lõi khi triển khai RecyclerView là tạo bộ chuyển đổi. Bạn có một phần tử giữ khung hiển thị đơn giản cho khung hiển thị mục và một bố cục cho từng mục. Giờ đây, bạn có thể tạo một trình chuyển đổi. Trình chuyển đổi tạo một ngăn chứa thành phần hiển thị và điền dữ liệu vào đó để RecyclerView hiển thị.
- Trong gói
sleeptracker, hãy tạo một lớp Kotlin mới có tên làSleepNightAdapter. - Mở rộng lớp
SleepNightAdapterthànhRecyclerView.Adapter. Lớp này có tên làSleepNightAdaptervì nó điều chỉnh một đối tượngSleepNightthành một đối tượng màRecyclerViewcó thể sử dụng. Bộ chuyển đổi cần biết nên sử dụng trình giữ khung hiển thị nào, vì vậy, hãy truyềnTextItemViewHoldervào. Nhập các thành phần cần thiết khi được nhắc, sau đó bạn sẽ thấy lỗi vì có các phương thức bắt buộc cần triển khai.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}- Ở cấp cao nhất của
SleepNightAdapter, hãy tạo một biếnlistOfSleepNightđể lưu giữ dữ liệu.
var data = listOf<SleepNight>()- Trong
SleepNightAdapter, hãy ghi đègetItemCount()để trả về kích thước của danh sách các đêm ngủ trongdata.RecyclerViewcần biết số lượng mục mà bộ chuyển đổi có để hiển thị và thực hiện việc đó bằng cách gọigetItemCount().
override fun getItemCount() = data.size- Trong
SleepNightAdapter, hãy ghi đè hàmonBindViewHolder(), như minh hoạ dưới đây.
HàmonBindViewHolder()đượcRecyclerViewgọi để hiển thị dữ liệu cho một mục trong danh sách ở vị trí đã chỉ định. Vì vậy, phương thứconBindViewHolder()sẽ nhận hai đối số: một trình giữ khung hiển thị và một vị trí của dữ liệu cần liên kết. Đối với ứng dụng này, phần giữ chỗ làTextItemViewHoldervà vị trí là vị trí trong danh sách.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}- Bên trong
onBindViewHolder(), hãy tạo một biến cho một mục ở một vị trí nhất định trong dữ liệu.
val item = data[position]ViewHoldermà bạn đã tạo có một thuộc tính tên làtextView. TrongonBindViewHolder(), hãy đặttextcủatextViewthành số chất lượng giấc ngủ. Đoạn mã này chỉ hiển thị một danh sách các số, nhưng ví dụ đơn giản này cho phép bạn xem cách bộ chuyển đổi đưa dữ liệu vào trình giữ khung hiển thị và lên màn hình.
holder.textView.text = item.sleepQuality.toString()- Trong
SleepNightAdapter, hãy ghi đè và triển khaionCreateViewHolder(). Phương thức này được gọi khiRecyclerViewcần một thành phần chứa chế độ xem để biểu thị một mục.
Hàm này nhận 2 tham số và trả về mộtViewHolder. Tham sốparent(nhóm thành phần hiển thị chứa ngăn chứa thành phần hiển thị) luôn làRecyclerView. Tham sốviewTypeđược dùng khi có nhiều khung hiển thị trong cùng mộtRecyclerView. Ví dụ: nếu bạn đặt một danh sách các khung hiển thị văn bản, một hình ảnh và một video vào cùng mộtRecyclerView, thì hàmonCreateViewHolder()sẽ cần biết loại khung hiển thị cần dùng.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}- Trong
onCreateViewHolder(), hãy tạo một thực thể củaLayoutInflater.
Trình tăng cường bố cục biết cách tạo khung hiển thị từ bố cục XML.contextchứa thông tin về cách tăng kích thước khung hiển thị một cách chính xác. Trong một bộ chuyển đổi cho thành phần hiển thị tái chế, bạn luôn truyền vào ngữ cảnh của nhóm thành phần hiển thịparent, đó làRecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)- Trong
onCreateViewHolder(), hãy tạoviewbằng cách yêu cầulayoutinflatermở rộngview.
Truyền bố cục XML cho khung hiển thị và nhóm khung hiển thịparentcho khung hiển thị. Đối số thứ ba (boolean) làattachToRoot. Đối số này cần mang giá trịfalsebởi vìRecyclerViewsẽ thêm phần tử này vào hệ phân cấp thành phần hiển thị vào thời điểm thích hợp.
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView- Trong
onCreateViewHolder(), hãy trả về mộtTextItemViewHolderđược tạo bằngview.
return TextItemViewHolder(view)- Trình chuyển đổi cần cho
RecyclerViewbiết khidatathay đổi, vìRecyclerViewkhông biết gì về dữ liệu. Nó chỉ biết về các trình giữ khung hiển thị mà bộ điều hợp cung cấp cho nó.
Để choRecyclerViewbiết khi nào dữ liệu mà nó đang hiển thị đã thay đổi, hãy thêm một phương thức thiết lập tuỳ chỉnh vào biếndataở đầu lớpSleepNightAdapter. Trong phương thức thiết lập, hãy gán chodatamột giá trị mới, sau đó gọinotifyDataSetChanged()để kích hoạt việc vẽ lại danh sách bằng dữ liệu mới.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}Bước 4: Cho RecyclerView biết về Bộ chuyển đổi
RecyclerView cần biết về bộ chuyển đổi để sử dụng nhằm lấy trình giữ khung hiển thị.
- Mở
SleepTrackerFragment.kt. - Trong
onCreateview(), hãy tạo một bộ chuyển đổi. Đặt mã này sau khi tạo mô hìnhViewModelvà trước câu lệnhreturn.
val adapter = SleepNightAdapter()- Liên kết
adaptervớiRecyclerView.
binding.sleepList.adapter = adapter- Dọn dẹp và tạo lại dự án để cập nhật đối tượng
binding.
Nếu bạn vẫn thấy lỗi liên quan đếnbinding.sleepListhoặcbinding.FragmentSleepTrackerBinding, hãy vô hiệu hoá bộ nhớ đệm rồi khởi động lại. (Chọn File > Invalidate Caches / Restart (Tệp > Huỷ bộ nhớ đệm/Khởi động lại).)
Nếu chạy ứng dụng ngay bây giờ, bạn sẽ không thấy lỗi nào, nhưng sẽ không thấy dữ liệu nào hiển thị khi nhấn vào Start (Bắt đầu), rồi nhấn vào Stop (Dừng).
Bước 5: Nhập dữ liệu vào bộ chuyển đổi
Đến đây, bạn đã có một trình chuyển đổi và cách lấy dữ liệu từ trình chuyển đổi vào RecyclerView. Bây giờ, bạn cần lấy dữ liệu vào bộ chuyển đổi từ ViewModel.
- Mở
SleepTrackerViewModel. - Tìm biến
nights. Biến này lưu trữ tất cả các đêm ngủ, đây là dữ liệu cần hiển thị. Biếnnightsđược đặt bằng cách gọigetAllNights()trên cơ sở dữ liệu. - Xoá
privatekhỏinights, vì bạn sẽ tạo một đối tượng tiếp nhận dữ liệu cần truy cập vào biến này. Nội dung khai báo của bạn sẽ có dạng như sau:
val nights = database.getAllNights()- Trong gói
database, hãy mởSleepDatabaseDao. - Tìm hàm
getAllNights(). Xin lưu ý rằng hàm này trả về danh sách các giá trịSleepNightdưới dạngLiveData. Điều này có nghĩa là biếnnightschứaLiveDatađượcRoomcập nhật liên tục và bạn có thể quan sátnightsđể biết thời điểm biến này thay đổi. - Mở
SleepTrackerFragment. - Trong
onCreateView(), bên dưới quá trình tạoadapter, hãy tạo một trình quan sát trên biếnnights.
Bằng cách cung cấpviewLifecycleOwnercủa mảnh dưới dạng chủ sở hữu vòng đời, bạn có thể đảm bảo rằng đối tượng tiếp nhận dữ liệu này chỉ hoạt động khiRecyclerViewở trên màn hình.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})- Bên trong đối tượng theo dõi, bất cứ khi nào bạn nhận được một giá trị không rỗng (đối với
nights), hãy chỉ định giá trị đó chodatacủa bộ chuyển đổi. Đây là mã hoàn chỉnh cho đối tượng theo dõi và việc thiết lập dữ liệu:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})- Tạo và chạy mã của bạn.
Bạn sẽ thấy danh sách các chỉ số về chất lượng giấc ngủ nếu bộ chuyển đổi đang hoạt động. Ảnh chụp màn hình bên trái cho thấy số -1 sau khi bạn nhấn vào Bắt đầu. Ảnh chụp màn hình bên phải cho thấy số liệu mới về chất lượng giấc ngủ sau khi bạn nhấn vào Dừng và chọn một điểm xếp hạng chất lượng.

Bước 6: Khám phá cách các trình giữ khung hiển thị được tái chế
RecyclerView tái chế các lớp lưu giữ thành phần hiển thị, tức là sử dụng lại các lớp này. Khi một thành phần hiển thị cuộn ra khỏi màn hình, RecyclerView sẽ sử dụng lại thành phần hiển thị đó cho thành phần hiển thị sắp cuộn vào màn hình.
Vì các trình giữ khung hiển thị này được tái chế, hãy đảm bảo onBindViewHolder() đặt hoặc đặt lại mọi chế độ tuỳ chỉnh mà các mục trước đó có thể đã đặt trên một trình giữ khung hiển thị.
Ví dụ: bạn có thể đặt màu văn bản thành màu đỏ trong các trình giữ khung hiển thị chứa điểm xếp hạng chất lượng nhỏ hơn hoặc bằng 1 và thể hiện giấc ngủ kém.
- Trong lớp
SleepNightAdapter, hãy thêm đoạn mã sau vào cuốionBindViewHolder().
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}- Chạy ứng dụng.
- Thêm một số dữ liệu về giấc ngủ kém chất lượng và số liệu này sẽ có màu đỏ.
- Thêm điểm đánh giá cao cho chất lượng giấc ngủ cho đến khi bạn thấy một số màu đỏ trên màn hình.
VìRecyclerViewsử dụng lại các trình giữ khung hiển thị, nên cuối cùng, nó sẽ sử dụng lại một trong các trình giữ khung hiển thị màu đỏ cho điểm đánh giá chất lượng cao. Đánh giá cao bị hiển thị nhầm bằng màu đỏ.

- Để khắc phục vấn đề này, hãy thêm một câu lệnh
elseđể đặt màu thành đen nếu chất lượng không nhỏ hơn hoặc bằng một.
Với cả hai điều kiện rõ ràng, trình giữ khung hiển thị sẽ sử dụng đúng màu văn bản cho từng mục.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
} else {
// reset
holder.textView.setTextColor(Color.BLACK) // black
}- Chạy ứng dụng và các số phải luôn có màu chính xác.
Xin chúc mừng! Giờ đây, bạn đã có một RecyclerView cơ bản hoạt động đầy đủ.
Trong nhiệm vụ này, bạn sẽ thay thế trình giữ khung hiển thị đơn giản bằng một trình giữ khung hiển thị có thể hiển thị nhiều dữ liệu hơn cho một đêm ngủ.
ViewHolder đơn giản mà bạn đã thêm vào Util.kt chỉ bao bọc một TextView trong một TextItemViewHolder.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)Vậy tại sao RecyclerView không chỉ sử dụng trực tiếp TextView? Một dòng mã này cung cấp rất nhiều chức năng. ViewHolder mô tả một khung hiển thị mục và siêu dữ liệu về vị trí của khung hiển thị đó trong RecyclerView. RecyclerView dựa vào chức năng này để định vị chính xác khung hiển thị khi danh sách cuộn và thực hiện những việc thú vị như tạo hiệu ứng cho khung hiển thị khi các mục được thêm hoặc xoá trong Adapter.
Nếu RecyclerView cần truy cập vào các khung hiển thị được lưu trữ trong ViewHolder, thì nó có thể thực hiện việc này bằng cách sử dụng thuộc tính itemView của trình giữ khung hiển thị. RecyclerView sử dụng itemView khi liên kết một mục để hiển thị trên màn hình, khi vẽ các thành phần trang trí xung quanh một khung hiển thị như đường viền và để triển khai khả năng tiếp cận.
Bước 1: Tạo bố cục mục
Trong bước này, bạn sẽ tạo tệp bố cục cho một mục. Bố cục này bao gồm một ConstraintLayout có ImageView cho chất lượng giấc ngủ, TextView cho thời lượng ngủ và TextView cho chất lượng dưới dạng văn bản. Vì bạn đã từng tạo bố cục trước đây, hãy sao chép và dán mã XML được cung cấp.
- Tạo một tệp tài nguyên bố cục mới và đặt tên là
list_item_sleep_night. - Thay thế toàn bộ mã trong tệp bằng mã bên dưới. Sau đó, hãy làm quen với bố cục bạn vừa tạo.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/quality_image"
android:layout_width="@dimen/icon_size"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_sleep_5" />
<TextView
android:id="@+id/sleep_length"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/quality_image"
app:layout_constraintTop_toTopOf="@+id/quality_image"
tools:text="Wednesday" />
<TextView
android:id="@+id/quality_string"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/sleep_length"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/sleep_length"
app:layout_constraintTop_toBottomOf="@+id/sleep_length"
tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>- Chuyển sang thẻ Thiết kế trong Android Studio. Trong chế độ xem thiết kế, bố cục của bạn sẽ trông giống như ảnh chụp màn hình ở bên trái bên dưới. Ở chế độ xem bản thiết kế, giao diện sẽ giống như ảnh chụp màn hình ở bên phải.

Bước 2: Tạo ViewHolder
- Mở
SleepNightAdapter.kt. - Tạo một lớp bên trong
SleepNightAdaptercó tên làViewHoldervà mở rộngRecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}- Bên trong
ViewHolder, hãy tham chiếu đến các thành phần hiển thị. Bạn cần có một tham chiếu đến các khung hiển thị màViewHoldernày sẽ cập nhật. Mỗi khi liên kếtViewHoldernày, bạn cần truy cập vào hình ảnh và cả hai khung hiển thị văn bản. (Sau này, bạn sẽ chuyển đổi mã này để sử dụng tính năng liên kết dữ liệu.)
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)Bước 3: Dùng ViewHolder trong SleepNightAdapter
- Trong định nghĩa
SleepNightAdapter, thay vìTextItemViewHolder, hãy dùngSleepNightAdapter.ViewHoldermà bạn vừa tạo.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {Cập nhật onCreateViewHolder():
- Thay đổi chữ ký của
onCreateViewHolder()để trả vềViewHolder. - Thay đổi trình mở rộng bố cục để sử dụng tài nguyên bố cục chính xác,
list_item_sleep_night. - Xoá thao tác truyền đến
TextView. - Thay vì trả về
TextItemViewHolder, hãy trả vềViewHolder.
Sau đây là hàmonCreateViewHolder()đã cập nhật hoàn tất:
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater =
LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night,
parent, false)
return ViewHolder(view)
}Cập nhật onBindViewHolder():
- Thay đổi chữ ký của
onBindViewHolder()để tham sốholderlàViewHolderthay vìTextItemViewHolder. - Trong
onBindViewHolder(), hãy xoá toàn bộ mã, ngoại trừ định nghĩa củaitem. - Xác định một
valreschứa một tham chiếu đếnresourcescho khung hiển thị này.
val res = holder.itemView.context.resources- Đặt văn bản của khung hiển thị văn bản
sleepLengththành thời lượng. Sao chép mã bên dưới. Mã này gọi một hàm định dạng được cung cấp cùng với mã khởi đầu.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)- Điều này sẽ gây ra lỗi vì bạn cần xác định
convertDurationToFormatted(). MởUtil.ktrồi bỏ chú thích mã và các nội dung nhập được liên kết cho mã đó. (Chọn Code > Comment with Line comments (Mã > Thêm chú thích bằng chú thích dòng).) - Quay lại
onBindViewHolder(), hãy dùngconvertNumericQualityToString()để đặt chất lượng.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)- Bạn có thể cần nhập các hàm này theo cách thủ công.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString- Đặt biểu tượng chính xác cho chất lượng. Biểu tượng
ic_sleep_activemới được cung cấp cho bạn trong mã khởi đầu.
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})- Sau đây là hàm
onBindViewHolder()đã cập nhật hoàn tất, thiết lập tất cả dữ liệu choViewHolder:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}- Chạy ứng dụng. Màn hình sẽ có dạng như ảnh chụp màn hình dưới đây, cho thấy biểu tượng chất lượng giấc ngủ, cùng với văn bản về thời lượng và chất lượng giấc ngủ.

Bạn hiện đã hoàn tất thành phần kết hợp RecyclerView! Bạn đã tìm hiểu cách triển khai Adapter và ViewHolder, đồng thời kết hợp chúng để hiển thị một danh sách bằng RecyclerView Adapter.
Đoạn mã của bạn cho đến nay cho thấy quy trình tạo một bộ chuyển đổi và ngăn chứa thành phần hiển thị. Tuy nhiên, bạn có thể cải thiện mã này. Mã để hiển thị và mã để quản lý trình lưu giữ thành phần hiển thị bị lẫn lộn, đồng thời onBindViewHolder() biết thông tin chi tiết về cách cập nhật ViewHolder.
Trong một ứng dụng phát hành công khai, bạn có thể có nhiều trình giữ chế độ xem, bộ điều hợp phức tạp hơn và nhiều nhà phát triển thực hiện các thay đổi. Bạn nên cấu trúc mã sao cho mọi thứ liên quan đến một trình giữ khung hiển thị chỉ nằm trong trình giữ khung hiển thị.
Bước 1: Tái cấu trúc onBindViewHolder()
Trong bước này, bạn sẽ tái cấu trúc mã và di chuyển tất cả chức năng của trình giữ chế độ xem vào ViewHolder. Mục đích của việc tái cấu trúc này không phải là thay đổi giao diện của ứng dụng đối với người dùng, mà là giúp nhà phát triển dễ dàng và an toàn hơn khi làm việc trên mã. May mắn là Android Studio có các công cụ hỗ trợ.
- Trong
SleepNightAdapter, trongonBindViewHolder(), hãy chọn mọi thứ ngoại trừ câu lệnh khai báo biếnitem. - Nhấp chuột phải, rồi chọn Refactor > Extract > Function (Tái cấu trúc > Trích xuất > Hàm).
- Đặt tên cho hàm là
bindvà chấp nhận các tham số được đề xuất. Nhấp vào OK.
Hàmbind()được đặt bên dướionBindViewHolder().
private fun bind(holder: ViewHolder, item: SleepNight) {
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}- Đặt con trỏ lên từ
holdercủa tham sốholdertrongbind(). NhấnAlt+Enter(Option+Entertrên máy Mac) để mở trình đơn ý định. Chọn Convert parameter to receiver (Chuyển đổi tham số thành đối tượng nhận) để chuyển đổi tham số này thành một hàm mở rộng có chữ ký sau:
private fun ViewHolder.bind(item: SleepNight) {...}- Cắt và dán hàm
bind()vàoViewHolder. - Đặt
bind()ở chế độ công khai. - Nhập
bind()vào bộ chuyển đổi, nếu cần. - Vì khoá này hiện nằm trong
ViewHolder, nên bạn có thể xoá phầnViewHoldercủa chữ ký. Sau đây là mã hoàn chỉnh cho hàmbind()trong lớpViewHolder.
fun bind(item: SleepNight) {
val res = itemView.context.resources
sleepLength.text = convertDurationToFormatted(
item.startTimeMilli, item.endTimeMilli, res)
quality.text = convertNumericQualityToString(
item.sleepQuality, res)
qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}Bước 2: Tái cấu trúc onCreateViewHolder
Phương thức onCreateViewHolder() trong trình chuyển đổi hiện tăng cường thành phần hiển thị từ tài nguyên bố cục cho ViewHolder. Tuy nhiên, lạm phát không liên quan gì đến bộ chuyển đổi mà liên quan đến ViewHolder. Lạm phát sẽ xảy ra trong ViewHolder.
- Trong
onCreateViewHolder(), hãy chọn tất cả mã trong phần nội dung của hàm. - Nhấp chuột phải, rồi chọn Refactor > Extract > Function (Tái cấu trúc > Trích xuất > Hàm).
- Đặt tên cho hàm là
fromvà chấp nhận các tham số được đề xuất. Nhấp vào OK. - Đặt con trỏ lên tên hàm
from. NhấnAlt+Enter(Option+Entertrên máy Mac) để mở trình đơn ý định. - Chọn Move to companion object (Di chuyển đến đối tượng đồng hành). Hàm
from()cần nằm trong một đối tượng companion để có thể được gọi trên lớpViewHolder, chứ không được gọi trên một thực thểViewHolder. - Di chuyển đối tượng
companionvào lớpViewHolder. - Đặt
from()ở chế độ công khai. - Trong
onCreateViewHolder(), hãy thay đổi câu lệnhreturnđể trả về kết quả của việc gọifrom()trong lớpViewHolder.
Phương thứconCreateViewHolder()vàfrom()đã hoàn tất của bạn sẽ có dạng như mã bên dưới, đồng thời mã của bạn sẽ được tạo và chạy mà không gặp lỗi.
override fun onCreateViewHolder(parent: ViewGroup, viewType:
Int): ViewHolder {
return ViewHolder.from(parent)
}companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
return ViewHolder(view)
}
}- Thay đổi chữ ký của lớp
ViewHolderđể hàm khởi tạo là private. Vìfrom()hiện là một phương thức trả về một thực thểViewHoldermới, nên không có lý do gì để người dùng gọi hàm khởi tạo củaViewHoldernữa.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){- Chạy ứng dụng. Ứng dụng của bạn sẽ tạo và chạy giống như trước đây. Đây là kết quả mong muốn sau khi tái cấu trúc.
Dự án Android Studio: RecyclerViewFundamentals
- Hiển thị danh sách hoặc lưới dữ liệu là một trong những tác vụ giao diện người dùng phổ biến nhất trên Android.
RecyclerViewđược thiết kế để hoạt động hiệu quả ngay cả khi hiển thị danh sách có nhiều mục. RecyclerViewchỉ thực hiện những việc cần thiết để xử lý hoặc vẽ các mục đang xuất hiện trên màn hình.- Khi một mục cuộn ra khỏi màn hình, các khung hiển thị của mục đó sẽ được tái chế. Điều này có nghĩa là phần tử này sẽ chứa nội dung mới cuộn lên màn hình.
- Mẫu bộ chuyển đổi trong kỹ thuật phần mềm giúp một đối tượng hoạt động cùng với một API khác.
RecyclerViewsử dụng một trình chuyển đổi để chuyển đổi dữ liệu ứng dụng thành một thứ mà nó có thể hiển thị, mà không cần thay đổi cách ứng dụng lưu trữ và xử lý dữ liệu.
Để hiển thị dữ liệu trong một RecyclerView, bạn cần có các phần sau:
- RecyclerView
Để tạo một thực thể củaRecyclerView, hãy xác định một phần tử<RecyclerView>trong tệp bố cục. - LayoutManager
RecyclerViewsử dụngLayoutManagerđể sắp xếp bố cục của các mục trongRecyclerView, chẳng hạn như sắp xếp các mục đó trong một lưới hoặc trong một danh sách tuyến tính.
Trong<RecyclerView>trong tệp bố cục, hãy đặt thuộc tínhapp:layoutManagerthành trình quản lý bố cục (chẳng hạn nhưLinearLayoutManagerhoặcGridLayoutManager).
Bạn cũng có thể đặtLayoutManagerchoRecyclerViewtheo phương thức lập trình. (Kỹ thuật này sẽ được đề cập trong một lớp học lập trình sau này.) - Bố cục cho từng mục
Tạo bố cục cho một mục dữ liệu trong tệp bố cục XML. - Trình chuyển đổi
Tạo một trình chuyển đổi để chuẩn bị dữ liệu và cách dữ liệu sẽ xuất hiện trongViewHolder. Liên kết đối tượng chuyển đổi vớiRecyclerView.
KhiRecyclerViewchạy, nó sẽ sử dụng trình chuyển đổi để tìm ra cách hiển thị dữ liệu trên màn hình.
Trình chuyển đổi yêu cầu bạn triển khai các phương thức sau:
–getItemCount()để trả về số lượng mục.
–onCreateViewHolder()để trả vềViewHoldercho một mục trong danh sách.
–onBindViewHolder()để điều chỉnh dữ liệu cho các khung hiển thị của một mục trong danh sách. - ViewHolder
ViewHolderchứa thông tin về chế độ xem để hiển thị một mục trong bố cục của mục đó. - Phương thức
onBindViewHolder()trong trình chuyển đổi sẽ điều chỉnh dữ liệu cho phù hợp với các thành phần hiển thị. Bạn luôn ghi đè phương thức này. Thông thường,onBindViewHolder()sẽ mở rộng bố cục cho một mục và đặt dữ liệu vào các thành phần hiển thị trong bố cục. - Vì
RecyclerViewkhông biết gì về dữ liệu, nênAdaptercần thông báo choRecyclerViewkhi dữ liệu đó thay đổi. Sử dụngnotifyDataSetChanged()để thông báo choAdapterrằng dữ liệu đã thay đổi.
Khoá học của Udacity:
Tài liệu dành cho nhà phát triển Android:
Phần này liệt kê các bài tập về nhà cho học viên của lớp học lập trình này trong phạm vi khoá học có người hướng dẫn. Người hướng dẫn phải thực hiện các việc sau đây:
- Giao bài tập về nhà nếu cần.
- Trao đổi với học viên về cách nộp bài tập về nhà.
- Chấm điểm bài tập về nhà.
Người hướng dẫn có thể sử dụng các đề xuất này ít hoặc nhiều tuỳ ý và nên giao cho học viên bất kỳ bài tập về nhà nào khác mà họ cảm thấy phù hợp.
Nếu bạn đang tự học các lớp học lập trình, hãy sử dụng những bài tập về nhà này để kiểm tra kiến thức của mình.
Trả lời các câu hỏi sau
Câu hỏi 1
RecyclerView hiển thị các mục như thế nào? Hãy chọn mọi câu trả lời phù hợp.
▢ Hiển thị các mục trong một danh sách hoặc một lưới.
▢ Cuộn theo chiều dọc hoặc chiều ngang.
▢ Cuộn theo đường chéo trên các thiết bị lớn hơn, chẳng hạn như máy tính bảng.
▢ Cho phép bố cục tuỳ chỉnh khi một danh sách hoặc một lưới không đủ cho trường hợp sử dụng.
Câu hỏi 2
Lợi ích của việc dùng RecyclerView là gì? Hãy chọn mọi câu trả lời phù hợp.
▢ Hiển thị danh sách có nhiều mục một cách hiệu quả.
▢ Tự động cập nhật dữ liệu.
▢ Giảm thiểu nhu cầu làm mới khi cập nhật, xoá hoặc thêm một mục vào danh sách.
▢ Dùng lại khung hiển thị cuộn khỏi màn hình để hiển thị mục tiếp theo cuộn trên màn hình.
Câu hỏi 3
Một số lý do dùng bộ chuyển đổi là gì? Hãy chọn mọi câu trả lời phù hợp.
▢ Việc tách riêng các vấn đề giúp thay đổi và kiểm thử mã dễ dàng hơn.
▢ RecyclerView độc lập với dữ liệu đang hiển thị.
▢ Các tầng xử lý dữ liệu không cần quan tâm đến cách hiển thị dữ liệu.
▢ Ứng dụng sẽ chạy nhanh hơn.
Câu hỏi 4
Câu nào sau đây đúng về ViewHolder? Hãy chọn mọi câu trả lời phù hợp.
▢ Bố cục ViewHolder được xác định trong các tệp bố cục XML.
▢ Có một ViewHolder cho mỗi đơn vị dữ liệu trong tập dữ liệu.
▢ Bạn có thể có nhiều ViewHolder trong một RecyclerView.
▢ Adapter liên kết dữ liệu với ViewHolder.
Bắt đầu bài học tiếp theo: