Android Kotlin Fundamentals 07.1: Kiến thức cơ bản về RecyclerView

Lớp học lập trình này nằm trong khóa học về Khái niệm cơ bản về Android Kotlin. Bạn sẽ nhận được nhiều giá trị nhất từ khóa học này nếu bạn làm việc qua các lớp học lập trình theo trình tự. Tất cả các lớp học lập trình trong khóa học đều có trên trang đích của các lớp học lập trình cơ bản về Android 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 chuỗi lớp học lập trình trước đây, bạn sẽ học được cách hiển thị dữ liệu tốt hơn và linh hoạt hơn bằng cách sử dụng RecyclerView có cấu trúc đề xuất.

Kiến thức bạn cần có

Bạn cần thông thạo:

  • Xây dựng một giao diện người dùng (UI) cơ bản bằng cách sử dụng một hoạt động, mảnh và chế độ xem.
  • Di chuyển giữa các mảnh và sử dụng safeArgs để chuyển dữ liệu giữa các mảnh.
  • Bằng cách sử dụng các mô hình chế độ xem, xem các trạng thái, biến đổi, LiveData và mô hình quan sát của mô hình đó.
  • Tạo cơ sở dữ liệu Room, tạo DAO và xác định các thực thể.
  • Dùng coroutine cho các tác vụ cơ sở dữ liệu và những 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 RecyclerView với AdapterViewHolder để hiển thị danh sách các mục.

Bạn sẽ thực hiện

  • Thay đổi ứng dụng TrackMySleep chiến lược từ bài học trước để sử dụng RecyclerView hiển thị dữ liệu chất lượng giấc ngủ.

Trong lớp học lập trình này, bạn xây dựng 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 dùng cơ sở dữ liệu Room để lưu trữ dữ liệu về giấc ngủ theo thời gian.

Ứng dụng theo dõi giấc ngủ dành cho người mới bắt đầu có hai màn hình, được biểu thị bằng các mảnh, như minh họa trong hình bên dưới.

Màn hình đầu tiên (hiển thị ở 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 hiển thị tất cả dữ liệu giấc ngủ của người dùng. Nút Xóa xóa 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 hiển thị ở bên phải, cho biết mức chọn điểm xếp hạng chất lượng giấc ngủ.

Ứng dụng này dùng cấu trúc đơn giản hóa với bộ điều khiển giao diện người dùng, ViewModelLiveData. Ứng dụng này cũng dùng cơ sở dữ liệu Room để duy trì dữ liệu giấc ngủ liên tục.

Danh sách các đêm ngủ được hiển thị trong màn hình đầu tiên là hoạt động nhưng không đẹp. Ứ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 con số cho chất lượng. Ngoài ra, thiết kế này không tăng tỷ lệ. 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 trông giống như sau:

Việc hiển thị danh sách hoặc lưới dữ liệu là một trong những thao tác giao diện người dùng phổ biến nhất trong Android. Các danh sách từ đơn giản đến rất phức tạp. Danh sách chế độ xem văn bản có thể hiển thị 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 chú thích về đích đến, có thể hiển thị cho người dùng nhiều thông tin chi tiết bên trong lưới cuộn 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à tính năng này rất hiệu quả cho các danh sách lớn:

  • Theo mặc định, RecyclerView chỉ hoạt động để xử lý hoặc vẽ các mục hiện có trên màn hình. Ví dụ: nếu danh sách của bạn có một nghìn phần tử nhưng chỉ có 10 phần tử hiển thị, thì RecyclerView chỉ thực hiện đủ để vẽ 10 phần tử trên màn hình. Khi người dùng cuộn trên trang, RecyclerView sẽ xác định được những mục mới nào trên màn hình và chỉ hoạt động đủ để hiển thị các mục đó.
  • Khi một mục cuộn ra khỏi màn hình, chế độ xem của mục đó sẽ được tái chế. Điều đó có nghĩa là mục này được lấp đầy bằng nội dung mới cuộn lên màn hình. Hành vi RecyclerView này giúp tiết kiệm nhiều thời gian xử lý và giúp các danh sách di chuyển linh hoạt.
  • Khi một mục thay đổi, thay vì vẽ lại toàn bộ danh sách, RecyclerView có thể cập nhật mục đó. Đây là mức tăng hiệu quả rất lớn 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 bạn đã từng di chuyển giữa các quốc gia sử dụng ổ cắm điện khác nhau, có thể bạn đã biết cách cắm thiết bị vào ổ cắm bằng cách sử dụng bộ sạc. Bộ chuyển đổi cho phép bạn chuyển đổi một loại phích cắm sang một loại khác, thực sự 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 bộ chuyển đổi để chuyển đổi dữ liệu ứng dụng thành nội dung 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 tạo một bộ chuyển đổi giúp điều chỉnh dữ liệu từ cơ sở dữ liệu của Room thành dữ liệu 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 của bạn trong RecyclerView, bạn cần 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 chế độ xem.
  • Bố cục cho một mục dữ liệu.
    Nếu tất cả các mục danh sách trông giống nhau, bạn có thể sử dụng cùng một bố cục cho tất cả mục đó, nhưng điều này là không bắt buộc. Bố cục mục phải được tạo riêng với bố cục của mảnh để có thể tạo một chế độ xem mục tại một thời điểm và lấp đầy dữ liệu.
  • Trình quản lý bố cục.
    Trình quản lý bố cục xử lý tổ chức (bố cục) các thành phần trên giao diện người dùng trong một chế độ xem.
  • Chủ sở hữu chế độ xem.
    Chủ sở hữu chế độ xem mở rộng lớp ViewHolder. Báo cáo này chứa thông tin về chế độ xem để hiển thị một mục trong bố cục của mục. Các chủ sở hữu chế độ xem cũng thêm thông tin mà RecyclerView sử dụng để di chuyển các chế độ xem xung quanh màn hình một cách hiệu quả.
  • Bộ chuyển đổi.
    Bộ chuyển đổi kết nối dữ liệu của bạn với RecyclerView. Ad Manager điều chỉnh dữ liệu để dữ liệu có thể hiển thị trong ViewHolder. RecyclerView sử dụng bộ chuyển đổi để tìm hiểu cách hiển thị dữ liệu trên màn hình.

Trong nhiệm vụ này, bạn thêm một RecyclerView vào tệp bố cục của mình và thiết lập một Adapter để hiển thị dữ liệu giấc ngủ cho RecyclerView.

Bước 1: Thêm RecyclerView bằng LayoutManager

Trong bước này, bạn thay thế ScrollView bằng một RecyclerView trong tệp fragment_sleep_tracker.xml.

  1. Tải ứng dụng RecyclerViewFundamentals-Starter xuống từ GitHub.
  2. Tạo và chạy ứng dụng. Lưu ý cách dữ liệu hiển thị dưới dạng văn bản đơn giản.
  3. Mở tệp bố cục fragment_sleep_tracker.xml trong thẻ Thiết kế trên Android Studio.
  4. Trong ngăn Cây thành phần, hãy xóa ScrollView. Hành động này cũng sẽ xóa TextView bên trong ScrollView.
  5. Trong ngăn Bảng màu, hãy cuộn qua danh sách các loại thành phần ở bên trái để tìm Vùng chứa, sau đó chọn mục.
  6. Kéo một RecyclerView từ ngăn Palette vào ngăn Cây thành phần. Đặt RecyclerView bên trong ConstraintLayout.

  1. Nếu một hộp thoại mở ra hỏi xem bạn có muốn thêm 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 recyclerview và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 sẽ đồng bộ hóa.

  1. Mở tệp mô-đun build.gradle, cuộn đến cuối và ghi chú phần phụ thuộc mới, trông giống như mã bên dưới:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. Chuyển về fragment_sleep_tracker.xml.
  2. Trong thẻ Văn bản, hãy tìm mã RecyclerView bên dưới:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Cho RecyclerView id với sleep_list.
android:id="@+id/sleep_list"
  1. Đặt RecyclerView để chiếm phần còn lại của màn hình bên trong ConstraintLayout. Để thực hiện việc này, hãy ràng buộc phần đầu của nút RecyclerView với nút Bắt đầu, phần dưới cùng với nút Xóa và mỗi bên ở nút mẹ. Đặt chiều rộng và chiều cao của bố cục thành 0 dp trong Layout Editor hoặc ở định dạng XML, bằng cách dù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"
  1. Thêm trình quản lý bố cục vào RecyclerView XML. Mỗi RecyclerView cần có một trình quản lý bố cục để hướng dẫn cách đặt các mục trong danh sách. Android cung cấp một LinearLayoutManager, theo mặc định, bố trí các mục trong một danh sách hàng dọc có chiều rộng đầy đủ.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. Chuyển sang thẻ Thiết kế và nhận thấy rằng các hạn chế đã thêm đã khiến RecyclerView mở 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à giá trị đặt ở chế độ xem văn bản

RecyclerView chỉ là một vùng chứa. Trong bước này, bạn tạo bố cục và cơ sở hạ tầng cho các mục sẽ hiển thị bên trong RecyclerView.

Để đạt được RecyclerView hoạt động nhanh nhất có thể, trước tiên, bạn sử dụng mục danh sách đơn giản chỉ hiển thị chất lượng giấc ngủ dưới dạng số. Để làm được việc này, bạn cần có một chủ sở hữu chế độ xem, TextItemViewHolder. Bạn cũng cần có chế độ xem, TextView, cho dữ liệu. (Trong bước sau, bạn tìm hiểu thêm về người xem chế độ xem và cách bố trí tất cả dữ liệu giấc ngủ.)

  1. Tạo một tệp bố cục có tên là text_item_view.xml. Không quan trọng bạn có gì sử dụng làm phần tử gốc vì bạn sẽ thay thế mã mẫu.
  2. Trong text_item_view.xml, hãy xóa tất cả các mã đã cho.
  3. Thêm một khoảng đệm TextView16dp khoảng đệm ở đầu và cuối cũng như kích thước văn bản là 24sp. Để chiều rộng khớp với chiều cao gốc và chiều cao bao bọc nội dung. Vì chế độ xem này hiển thị bên trong RecyclerView, nên bạn không phải đặt chế độ xem này trong ViewGroup.
<?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" />
  1. Mở Util.kt. Di chuyển đến cuối rồi thêm định nghĩa ở bên dưới để tạo lớp TextItemViewHolder. Đặt mã ở cuối tệp, sau dấu ngoặc đóng cuối cùng. Mã này được nhập vào Util.kt vì trình xem này là tạm thời và bạn thay thế sau.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Nếu được nhắc, hãy nhập android.widget.TextViewandroidx.recyclerview.widget.RecyclerView.

Bước 3: Tạo SleepnightAdapter

Nhiệm vụ cốt lõi trong việc triển khai RecyclerView là tạo bộ chuyển đổi. Bạn có một chế độ xem đơn giản dành cho chế độ xem mục và bố cục cho mỗi mục. Giờ đây, bạn có thể tạo một bộ chuyển đổi. Bộ chuyển đổi tạo giá trị xem và điền dữ liệu để RecyclerView hiển thị.

  1. Trong gói sleeptracker, hãy tạo một lớp Kotlin mới có tên là SleepNightAdapter.
  2. Đặt lớp SleepNightAdapter mở rộng RecyclerView.Adapter. Lớp này được gọi là SleepNightAdapter vì lớp này điều chỉnh đối tượng SleepNight thành loại mà RecyclerView có thể sử dụng. Bộ chuyển đổi cần biết đối tượng sử dụng chế độ xem nào, vì vậy, hãy chuyển thông tin trong TextItemViewHolder. 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 để triển khai.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. Ở cấp cao nhất trong SleepNightAdapter, hãy tạo một biến listOf SleepNight để lưu giữ dữ liệu.
var data =  listOf<SleepNight>()
  1. Trong SleepNightAdapter, hãy ghi đè getItemCount() để trả về kích thước của danh sách đêm ngủ ở data. RecyclerView cần biết bộ chuyển đổi có thể hiển thị bao nhiêu mục và chức năng này sẽ thực hiện việc này bằng cách gọi getItemCount().
override fun getItemCount() = data.size
  1. Trong SleepNightAdapter, hãy ghi đè hàm onBindViewHolder(), như hiển thị bên dưới.

    Hàm onBindViewHolder() được RecyclerView gọi để hiển thị dữ liệu cho một mục danh sách ở vị trí đã chỉ định. Vì vậy, phương thức onBindViewHolder() sẽ nhận 2 đối số: một trình xem chế độ xem và một vị trí dữ liệu để liên kết. Đối với ứng dụng này, chủ sở hữu là TextItemViewHolder và vị trí là vị trí trong danh sách.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. Bên trong onBindViewHolder(), hãy tạo một biến cho một mục tại một vị trí đã cho trong dữ liệu.
 val item = data[position]
  1. ViewHolder mà bạn đã tạo có một thuộc tính có tên là textView. Bên trong onBindViewHolder(), hãy đặt text của textView thành số chất lượng giấc ngủ. Mã này chỉ hiển thị 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 xem chế độ xem và lên màn hình.
holder.textView.text = item.sleepQuality.toString()
  1. Trong SleepNightAdapter, hãy ghi đè và triển khai onCreateViewHolder(). Lệnh này được gọi khi RecyclerView cần có giá trị xem để đại diện cho một mục.

    Hàm này nhận 2 tham số và trả về một ViewHolder. Thông số parent, là nhóm chế độ xem lưu giữ chủ sở hữu chế độ xem, luôn là RecyclerView. Thông số viewType được sử dụng khi có nhiều chế độ xem trong cùng một RecyclerView. Ví dụ: nếu bạn đặt danh sách chế độ xem văn bản, hình ảnh và video vào cùng một RecyclerView, thì hàm onCreateViewHolder() sẽ cần biết loại chế độ xem nào cần sử dụng.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. Trong onCreateViewHolder(), hãy tạo một bản sao của LayoutInflater.

    Trình tăng cường bố cục biết cách tạo chế độ xem từ bố cục XML. context chứa thông tin về cách tăng cường chế độ xem đúng cách. Trong bộ chuyển đổi cho chế độ xem tuần hoàn, bạn luôn chuyển trong ngữ cảnh của nhóm chế độ xem parent, đó là RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. Trong onCreateViewHolder(), hãy tạo view bằng cách yêu cầu layoutinflater tăng cường giá trị đó.

    Chuyển trong bố cục XML của chế độ xem và nhóm chế độ xem parent cho chế độ xem. Đối số thứ ba, boolean, đối số là attachToRoot. Đối số này cần phải là false, bởi vì RecyclerView sẽ thêm mục này vào hệ phân cấp chế độ xem cho bạn khi đến thời gian.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. Trong onCreateViewHolder(), trả về một TextItemViewHolder được thực hiện bằng view.
return TextItemViewHolder(view)
  1. Bộ chuyển đổi cần cho RecyclerView biết thời điểm data thay đổi, vì RecyclerView không biết gì về dữ liệu. Bộ chuyển đổi này chỉ biết các lớp lưu giữ chế độ xem mà bộ chuyển đổi cung cấp cho nó.

    Để cho RecyclerView biết khi dữ liệu mà bộ chuyển đổi hiển thị đã thay đổi, hãy thêm một phương thức setter tùy chỉnh vào biến data nằm ở đầu lớp SleepNightAdapter. Trong phương thức setter, hãy cung cấp cho data một giá trị mới, sau đó gọi notifyDataSetChanged() để kích hoạt việc vẽ lại danh sách với dữ liệu mới.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Bước 4: Thông báo cho RecyclerView về Bộ chuyển đổi

RecyclerView cần biết về bộ chuyển đổi để sử dụng cho việc lấy chủ sở hữu chế độ xem.

  1. Mở SleepTrackerFragment.kt.
  2. Trong onCreateview(), hãy tạo một bộ chuyển đổi. Đặt mã này sau khi tạo mô hình ViewModel và trước câu lệnh return.
val adapter = SleepNightAdapter()
  1. Liên kết adapter với RecyclerView.
binding.sleepList.adapter = adapter
  1. Dọn dẹp và tạo lại dự án của bạn để cập nhật đối tượng binding.

    Nếu bạn vẫn thấy lỗi vào khoảng binding.sleepList hoặc binding.FragmentSleepTrackerBinding, hãy vô hiệu hóa bộ nhớ đệm và khởi động lại. (Chọn Tệp > Vô hiệu hóa bộ nhớ đệm / Khởi động lại.)

    Nếu bạn chạy ứng dụng ngay bây giờ, sẽ không có lỗi, nhưng bạn sẽ không thấy bất kỳ dữ liệu nào hiển thị khi nhấn vào Bắt đầu, rồi nhấn Dừng.

Bước 5: Lấy dữ liệu vào bộ chuyển đổi

Cho đến nay, bạn đã có bộ chuyển đổi và cách lấy dữ liệu từ bộ 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.

  1. Mở SleepTrackerViewModel.
  2. Tìm biến nights lưu trữ tất cả các đêm ngủ, đây là dữ liệu cần hiển thị. Biến nights được đặt bằng cách gọi getAllNights() trên cơ sở dữ liệu.
  3. Xóa private khỏi nights 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()
  1. Trong gói database, hãy mở SleepDatabaseDao.
  2. Tìm hàm getAllNights(). Xin lưu ý rằng hàm này trả về một danh sách giá trị SleepNight dưới dạng LiveData. Điều này có nghĩa là biến nights chứa LiveData được Room cập nhật và bạn có thể quan sát nights để biết khi nào biến thay đổi.
  3. Mở SleepTrackerFragment.
  4. Trong onCreateView(), bên dưới bước tạo adapter, hãy tạo một đối tượng tiếp nhận dữ liệu lên biến nights.

    Bằng việc cung cấp viewLifecycleOwner với tư cách là chủ sở hữu vòng đời, mảnh này có thể đảm bảo rằng trình quan sát này chỉ hoạt động khi RecyclerView trên màn hình.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. Bên trong đối tượng tiếp nhận dữ liệu, bất cứ khi nào bạn nhận được giá trị không có giá trị null (cho nights), hãy chỉ định giá trị đó cho bộ chuyển đổidata. Đây là mã hoàn chỉnh cho đối tượng tiếp nhận dữ liệu và thiết lập dữ liệu:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Tạo và chạy mã.

    Bạn sẽ thấy các con số chất lượng giấc ngủ dưới dạng danh sách nếu bộ chuyển đổi đang hoạt động. Ảnh chụp màn hình ở bên trái hiển thị -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ố lượng chất lượng giấc ngủ mới cập nhật sau khi bạn nhấn vào Dừng và chọn một mức xếp hạng chất lượng.

Bước 6: Khám phá cách tái chế các chủ sở hữu chế độ xem

RecyclerView tái chế chủ sở hữu chế độ xem, có nghĩa là nó tái sử dụng chúng. Khi một chế độ xem cuộn ra khỏi màn hình, RecyclerView sẽ sử dụng lại chế độ xem đó cho chế độ xem sắp di chuyển lên màn hình.

Vì các chủ chế độ xem này được tái chế, hãy đảm bảo onBindViewHolder() đặt hoặc đặt lại mọi nội dung tùy chỉnh mà các mục trước đó có thể đã đặt trên chủ sở hữu chế độ xem.

Ví dụ: bạn có thể đặt màu văn bản thành màu đỏ trong trình xem giá trị chứa thông tin xếp hạng chất lượng thấp hơn hoặc bằng 1 và đại diện cho giấc ngủ kém.

  1. Trong lớp SleepNightAdapter, hãy thêm mã sau vào cuối onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. Chạy ứng dụng.
  2. Thêm một số dữ liệu chất lượng giấc ngủ thấp và con số này màu đỏ.
  3. Thêm điểm xếp hạng cao cho chất lượng giấc ngủ cho đến khi bạn nhìn thấy một con số cao màu đỏ trên màn hình.

    Khi RecyclerView sử dụng lại giá trị nhận dạng chế độ xem, cuối cùng, hệ thống sẽ sử dụng lại một trong những giá trị nhận dạng màu đỏ cho mức phân loại chất lượng cao. Xếp hạng cao bị hiển thị màu đỏ do nhầm lẫn.

  1. Để khắc phục vấn đề này, hãy thêm câu lệnh else để đặt màu thành màu đen nếu chất lượng không nhỏ hơn hoặc bằng 1.

    Với cả hai điều kiện rõ ràng, trình xem chế độ xem sẽ sử dụng màu văn bản chính xác cho từng mục.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. Chạy ứng dụng và các con số phải luôn có màu chính xác.

Xin chúc mừng! Bạn hiện đã có RecyclerView đầy đủ chức năng cơ bản.

Trong nhiệm vụ này, bạn thay thế trình giữ chỗ đơn giản bằng một trình xem có thể hiển thị nhiều dữ liệu hơn cho một giấc ngủ.

ViewHolder đơn giản mà bạn đã thêm vào Util.kt chỉ bao gồm một TextView trong 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 chế độ xem mục và siêu dữ liệu về vị trí của mục trong RecyclerView. RecyclerView hoạt động dựa trên chức năng này để xác định vị trí chính xác của chế độ xem khi danh sách di chuyển lên và để làm những việc thú vị như tạo ảnh động cho các chế độ xem khi các mục được thêm vào hoặc bị xóa khỏi Adapter.

Nếu cần truy cập vào các chế độ xem được lưu trữ trong ViewHolder, thì RecyclerView có thể truy cập vào chế độ xem này bằng cách sử dụng thuộc tính itemView của chủ sở hữu chế độ xem. RecyclerView sử dụng itemView khi đang liên kết một mục để hiển thị trên màn hình, khi vẽ trang trí xung quanh một chế độ xem như đường viền và để triển khai tính năng hỗ trợ tiếp cận.

Bước 1: Tạo bố cục mục

Ở bước này, bạn tạo tệp bố cục cho một mục. Bố cục bao gồm ConstraintLayout với 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ì trước đó bạn đã tạo bố cục, hãy sao chép và dán mã XML được cung cấp.

  1. Tạo tệp tài nguyên bố cục mới và đặt tên tệp là list_item_sleep_night.
  2. Thay thế tất cả 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>
  1. Chuyển sang thẻ Thiết kế trong Android Studio. Trong chế độ xem thiết kế, bố cục của bạn trông giống như ảnh chụp màn hình ở bên trái. Trong chế độ xem sơ đồ thiết kế, hình này trông giống như ảnh chụp màn hình ở bên phải.

Bước 2: Tạo ViewHolder

  1. Mở SleepNightAdapter.kt.
  2. Tạo một lớp bên trong SleepNightAdapter có tên là ViewHolder và thêm lớp đó vào RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. Bên trong ViewHolder, hãy lấy thông tin tham chiếu đến các chế độ xem. Bạn cần tham chiếu đến các chế độ xem mà ViewHolder này sẽ cập nhật. Mỗi khi liên kết ViewHolder này, bạn cần truy cập vào hình ảnh và cả hai chế độ xem văn bản. (Bạn chuyển đổi mã này để sử dụng liên kết dữ liệu sau này.)
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: Sử dụng ViewHolder trong SleepnightAdapter

  1. Trong định nghĩa SleepNightAdapter, thay vì TextItemViewHolder, hãy sử dụng SleepNightAdapter.ViewHolder mà bạn vừa tạo.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

Cập nhật onCreateViewHolder():

  1. Thay đổi chữ ký của onCreateViewHolder() để trả lại ViewHolder.
  2. Hãy thay đổi kiểu bố cục để sử dụng đúng tài nguyên bố cục, list_item_sleep_night.
  3. Xóa truyền tới TextView.
  4. Thay vì trả về TextItemViewHolder, hãy trả về ViewHolder.

    Sau đây là hàm onCreateViewHolder() đã cập nhật xong:
    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():

  1. Thay đổi chữ ký của onBindViewHolder() để thông số holderViewHolder thay vì TextItemViewHolder.
  2. Bên trong onBindViewHolder(), hãy xóa tất cả mã, ngoại trừ định nghĩa của item.
  3. Xác định val res có tham chiếu đến resources cho chế độ xem này.
val res = holder.itemView.context.resources
  1. Đặt thời lượng của chế độ xem văn bản sleepLength thành thời lượng. Hãy sao chép mã ở bên dưới để gọi một hàm định dạng được cung cấp bằng mã dành cho người mới bắt đầu.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. Điều này gây ra lỗi, vì convertDurationToFormatted() cần được xác định. Mở Util.kt và bỏ nhận xét mã cũng như dữ liệu nhập liên kết cho mã đó. (Chọn Mã > Nhận xét bằng nhận xét dòng.)
  2. Quay lại onBindViewHolder(), sử dụng convertNumericQualityToString() để đặt chất lượng.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Bạn có thể cần phải tự nhập các chức năng này.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. Hãy đặt đúng biểu tượng cho chất lượng. Biểu tượng ic_sleep_active mới được cung cấp cho bạn trong mã dành cho người mới bắt đầ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
})
  1. Dưới đây là hàm onBindViewHolder() đã cập nhật xong, đặt tất cả dữ liệu cho ViewHolder:
   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
        })
    }
  1. Chạy ứng dụng của bạn. Màn hình của bạn sẽ trông giống như ảnh chụp màn hình bên dưới, hiển thị biểu tượng chất lượng giấc ngủ, cùng với văn bản cho thời lượng giấc ngủ và chất lượng giấc ngủ.

RecyclerView của bạn hiện đã hoàn tất! Bạn đã tìm hiểu cách triển khai một Adapter và một ViewHolder, đồng thời bạn đã ghép chúng lại với nhau để hiển thị danh sách với RecyclerView Adapter.

Từ đầu đến nay, mã của bạn sẽ cho thấy quá trình tạo một bộ chuyển đổi và trình xem. Tuy nhiên, bạn có thể cải thiện mã này. Mã hiển thị và mã để quản lý chủ sở hữu chế độ xem bị lẫn lộn, và onBindViewHolder() biết chi tiết về cách cập nhật ViewHolder.

Trong ứng dụng chính thức, bạn có thể có nhiều chủ sở hữu chế độ xem, bộ chuyển đổi phức tạp hơn và nhiều nhà phát triển thực hiện thay đổi. Bạn nên tổ chức mã của mình để mọi thứ liên quan đến chủ sở hữu chế độ xem chỉ nằm trong thiết bị xem.

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 chủ sở hữu 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 cho người dùng, mà giúp các nhà phát triển làm việc với mã dễ dàng và an toàn hơn. May mắn thay, Android Studio đã có công cụ trợ giúp.

  1. Trong SleepNightAdapter, ở onBindViewHolder(), hãy chọn mọi thứ trừ câu lệnh để khai báo biến item.
  2. Nhấp chuột phải, sau đó chọn Tái cấu trúc > Trích xuất > Hàm.
  3. Đặt tên hàm bind và chấp nhận các tham số đề xuất. Nhấp vào OK.

    Hàm bind() được đặt bên dưới onBindViewHolder().
    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
        })
    }
  1. Đặt con trỏ vào từ holder trong thông số holder của bind(). Nhấn Alt+Enter (Option+Enter trên một máy Mac) để mở trình đơn ý định. Chọn Chuyển đổi thông số thành người nhận để chuyển đổi thông số này thành hàm mở rộng có chữ ký sau:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. Cắt và dán hàm bind() vào ViewHolder.
  2. Đặt bind() ở chế độ công khai.
  3. Nhập bind() vào bộ chuyển đổi nếu cần.
  4. Vì giờ đây đã có trong ViewHolder, nên bạn có thể xóa phần ViewHolder của chữ ký. Đây là mã cuối cùng cho hàm bind() trong lớp ViewHolder.
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 bộ chuyển đổi hiện tăng cường chế độ xem từ tài nguyên bố cục cho ViewHolder. Tuy nhiên, hệ thống sẽ cho thấy mức lạm phát không liên quan đến bộ chuyển đổi và mọi thứ liên quan đến ViewHolder. Việc tăng xảy ra sẽ xảy ra ở ViewHolder.

  1. Trong onCreateViewHolder(), hãy chọn tất cả mã trong phần nội dung của hàm.
  2. Nhấp chuột phải, sau đó chọn Tái cấu trúc > Trích xuất > Hàm.
  3. Đặt tên hàm from và chấp nhận các tham số đề xuất. Nhấp vào OK.
  4. Đặt con trỏ vào tên hàm from. Nhấn Alt+Enter (Option+Enter trên một máy Mac) để mở trình đơn ý định.
  5. Chọn Di chuyển đến đối tượng companion. Hàm from() cần phải nằm trong đối tượng companion để có thể được gọi trên lớp ViewHolder, không được gọi trên thực thể ViewHolder.
  6. Di chuyển đối tượng companion vào lớp ViewHolder.
  7. Đặt from() ở chế độ công khai.
  8. Trong onCreateViewHolder(), hãy thay đổi câu lệnh return để trả về kết quả gọi from() trong lớp ViewHolder.

    Các phương thức onCreateViewHolder()from() đã hoàn thành của bạn sẽ trông giống như mã bên dưới và 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)
   }
}
  1. Thay đổi chữ ký của lớp ViewHolder để hàm dựng ở chế độ riêng tư. Vì from() hiện là một phương thức trả về một thực thể ViewHolder mới, nên không có lý do gì để mọi người gọi hàm dựng của ViewHolder nữa.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. Chạy ứng dụng. Ứng dụng của bạn phải xây dựng và chạy như trước, đây là kết quả mong muốn sau khi tái cấu trúc.

Dự án Android Studio: RecyclerViewFundamentals

  • Việc hiển thị danh sách hoặc lưới dữ liệu là một trong những thao tác giao diện người dùng phổ biến nhất trong Android. RecyclerView được thiết kế để hoạt động hiệu quả ngay cả khi hiển thị các danh sách cực lớn.
  • RecyclerView chỉ thực hiện công việc cần thiết để xử lý hoặc vẽ các mục hiện có trên màn hình.
  • Khi một mục cuộn ra khỏi màn hình, chế độ xem của mục đó sẽ được tái chế. Điều đó có nghĩa là mục này được lấp đầy bằng 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. RecyclerView sử dụng bộ chuyển đổi để chuyển đổi dữ liệu ứng dụng thành nội dung mà ứng dụng 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 của bạn trong RecyclerView, bạn cần các phần sau:

  • RecyclerView
    Để tạo một phiên bản của RecyclerView, hãy xác định một phần tử <RecyclerView> trong tệp bố cục.
  • LayoutManager
    RecyclerView sử dụng LayoutManager để sắp xếp bố cục của các mục trong RecyclerView, chẳng hạn như bố trí các mục đó trong lưới hoặc trong danh sách tuyến tính.

    Trong <RecyclerView> trong tệp bố cục, hãy đặt thuộc tính app:layoutManager thành trình quản lý bố cục (chẳng hạn như LinearLayoutManager hoặc GridLayoutManager).

    Bạn cũng có thể đặt LayoutManager cho RecyclerView theo phương thức lập trình. (Kỹ thuật này đượ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.
  • Bộ chuyển đổi
    Tạo bộ chuyển đổi giúp chuẩn bị dữ liệu và cách dữ liệu hiển thị trong ViewHolder. Liên kết bộ chuyển đổi với RecyclerView.

    Khi RecyclerView chạy, bộ chuyển đổi sẽ tìm cách hiển thị dữ liệu trên màn hình.

    Bộ chuyển đổi sẽ 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ề ViewHolder cho một mục trong danh sách.

    để điều chỉnh dữ liệu cho các chế độ xem của một mục trong danh sách.

  • ViewHolder
    ViewHolder chứa thông tin về chế độ xem để hiển thị một mục từ bố cục của mục.
  • Phương thức onBindViewHolder() trong bộ chuyển đổi sẽ điều chỉnh dữ liệu cho phù hợp với chế độ xem. Bạn luôn ghi đè phương thức này. Thông thường, onBindViewHolder() tăng cường bố cục cho một mục và đặt dữ liệu vào các chế độ xem trong bố cục.
  • RecyclerView không biết gì về dữ liệu nên Adapter cần phải thông báo cho RecyclerView khi dữ liệu đó thay đổi. Sử dụng notifyDataSetChanged() để thông báo cho Adapter rằng dữ liệu đã thay đổi.

Khóa học từ 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à có thể được giao cho học viên đang làm việc qua lớp học lập trình này trong khóa học do người hướng dẫn tổ chức. Người hướng dẫn có thể làm những việc sau:

  • Giao bài tập về nhà nếu được yêu cầu.
  • Trao đổi với học viên 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 những đề xuất này ít hay nhiều tùy ý. Do đó, họ có thể thoải mái giao 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ự mình làm việc qua lớp học lập trình này, hãy thoải mái sử dụng các bài tập về nhà này để kiểm tra kiến thức của bạn.

Trả lời những câu hỏi này

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 phù hợp.

▢ Hiển thị các mục trong danh sách hoặc 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 tùy chỉnh khi danh sách hoặc 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 phù hợp.

▢ Hiển thị hiệu quả các danh sách lớn.

▢ Tự động cập nhật dữ liệu.

▢Giảm thiểu nhu cầu làm mới khi một mục được cập nhật, xóa hoặc thêm vào danh sách.

▢ Sử dụng lại chế độ xem cuộn ra 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 phù hợp.

▢ Việc tách biệt vấn đề giúp bạn dễ dàng thay đổi và kiểm tra mã hơn.

RecyclerView không thể dự đoán dữ liệu đang hiển thị.

▢ Các lớp xử lý dữ liệu không phải lo lắng về cách dữ liệu sẽ hiển thị.

▢ Ứ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 phù hợp.

▢ Bố cục ViewHolder được xác định trong 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: 7.2: DiffUtil và liên kết dữ liệu với RecyclerView