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ớiAdapter
vàViewHolder
để 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, ViewModel
và LiveData
. Ứ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ớpViewHolder
. 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ớiRecyclerView
. Ad Manager điều chỉnh dữ liệu để dữ liệu có thể hiển thị trongViewHolder
.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
.
- Tải ứng dụng RecyclerViewFundamentals-Starter xuống từ GitHub.
- 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.
- Mở tệp bố cục
fragment_sleep_tracker.xml
trong thẻ Thiết kế trên Android Studio. - Trong ngăn Cây thành phần, hãy xóa
ScrollView
. Hành động này cũng sẽ xóaTextView
bên trongScrollView
. - 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.
- Kéo một
RecyclerView
từ ngăn Palette vào ngăn Cây thành phần. ĐặtRecyclerView
bên trongConstraintLayout
.
- 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.
- 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'
- Chuyển về
fragment_sleep_tracker.xml
. - 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" />
- Cho
RecyclerView
id
vớisleep_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
. Để thực hiện việc này, hãy ràng buộc phần đầu của nútRecyclerView
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"
- Thêm trình quản lý bố cục vào
RecyclerView
XML. MỗiRecyclerView
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ộtLinearLayoutManager
, 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"
- 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ủ.)
- 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. - Trong
text_item_view.xml
, hãy xóa tất cả các mã đã cho. - Thêm một khoảng đệm
TextView
có16dp
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 trongRecyclerView
, nên bạn không phải đặt chế độ xem này 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 đến cuối rồi thêm định nghĩa ở bên dưới để tạo lớpTextItemViewHolder
. Đặt mã ở cuối tệp, sau dấu ngoặc đóng cuối cùng. Mã này được nhập vàoUtil.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)
- Nếu được nhắc, hãy nhập
android.widget.TextView
vàandroidx.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ị.
- Trong gói
sleeptracker
, hãy tạo một lớp Kotlin mới có tên làSleepNightAdapter
. - Đặt lớp
SleepNightAdapter
mở rộngRecyclerView.Adapter
. Lớp này được gọi làSleepNightAdapter
vì lớp này điều chỉnh đối tượngSleepNight
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 trongTextItemViewHolder
. 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>() {}
- Ở cấp cao nhất trong
SleepNightAdapter
, hãy tạo một biếnlistOf
SleepNight
để 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 đê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ọigetItemCount()
.
override fun getItemCount() = data.size
- Trong
SleepNightAdapter
, hãy ghi đè hàmonBindViewHolder()
, như hiển thị bên dưới.
HàmonBindViewHolder()
đượcRecyclerView
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ứconBindViewHolder()
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) {
}
- 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]
ViewHolder
mà bạn đã tạo có một thuộc tính có tên làtextView
. Bên trongonBindViewHolder()
, hãy đặttext
củatextView
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()
- Trong
SleepNightAdapter
, hãy ghi đè và triển khaionCreateViewHolder()
. Lệnh này được gọi khiRecyclerView
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ộtViewHolder
. 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ộtRecyclerView
. 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ộtRecyclerView
, thì hàmonCreateViewHolder()
sẽ cần biết loại chế độ xem nào cần sử dụng.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
- Trong
onCreateViewHolder()
, hãy tạo một bản sao củaLayoutInflater
.
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ế độ xemparent
, đó làRecyclerView
.
val layoutInflater = LayoutInflater.from(parent.context)
- Trong
onCreateViewHolder()
, hãy tạoview
bằng cách yêu cầulayoutinflater
tăng cường giá trị đó.
Chuyển trong bố cục XML của chế độ xem và nhóm chế độ xemparent
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
- Trong
onCreateViewHolder()
, trả về mộtTextItemViewHolder
được thực hiện bằngview
.
return TextItemViewHolder(view)
- Bộ chuyển đổi cần cho
RecyclerView
biết thời điểmdata
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ó.
Để choRecyclerView
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ếndata
nằm ở đầu lớpSleepNightAdapter
. Trong phương thức setter, hãy cung cấp chodata
một giá trị mới, sau đó gọinotifyDataSetChanged()
để 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.
- 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ìnhViewModel
và trước câu lệnhreturn
.
val adapter = SleepNightAdapter()
- Liên kết
adapter
vớiRecyclerView
.
binding.sleepList.adapter = adapter
- 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ảngbinding.sleepList
hoặcbinding.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
.
- Mở
SleepTrackerViewModel
. - 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ếnnights
được đặt bằng cách gọigetAllNights()
trên cơ sở dữ liệu. - Xóa
private
khỏ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ề một danh sách giá trịSleepNight
dưới dạngLiveData
. Điều này có nghĩa là biếnnights
chứaLiveData
đượcRoom
cập nhật và bạn có thể quan sátnights
để biết khi nào biến thay đổi. - Mở
SleepTrackerFragment
. - Trong
onCreateView()
, bên dưới bước tạoadapter
, hãy tạo một đối tượng tiếp nhận dữ liệu lên biếnnights
.
Bằng việc cung cấpviewLifecycleOwner
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 khiRecyclerView
trên màn hình.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})
- 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
}
})
- 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.
- Trong lớp
SleepNightAdapter
, hãy thêm 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 chất lượng giấc ngủ thấp và con số này màu đỏ.
- 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.
KhiRecyclerView
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.
- Để 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
}
- 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.
- Tạo tệp tài nguyên bố cục mới và đặt tên tệp là
list_item_sleep_night
. - 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>
- 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
- Mở
SleepNightAdapter.kt
. - Tạo một lớp bên trong
SleepNightAdapter
có tên làViewHolder
và thêm lớp đó vàoRecyclerView.ViewHolder
.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
- 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ếtViewHolder
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
- Trong định nghĩa
SleepNightAdapter
, thay vìTextItemViewHolder
, hãy sử dụngSleepNightAdapter.ViewHolder
mà 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ả lạiViewHolder
. - Hãy thay đổi kiểu bố cục để sử dụng đúng tài nguyên bố cục,
list_item_sleep_night
. - Xóa truyền tới
TextView
. - Thay vì trả về
TextItemViewHolder
, hãy trả vềViewHolder
.
Sau đây là hàmonCreateViewHolder()
đã 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()
:
- Thay đổi chữ ký của
onBindViewHolder()
để thông sốholder
làViewHolder
thay vìTextItemViewHolder
. - Bên trong
onBindViewHolder()
, hãy xóa tất cả mã, ngoại trừ định nghĩa củaitem
. - Xác định
val
res
có tham chiếu đếnresources
cho chế độ xem này.
val res = holder.itemView.context.resources
- Đặ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)
- Đ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.) - Quay lại
onBindViewHolder()
, sử dụngconvertNumericQualityToString()
để đặt chất lượng.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
- 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
- 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
})
- Dưới đây là hàm
onBindViewHolder()
đã cập nhật xong, đặt 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 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.
- Trong
SleepNightAdapter
, ởonBindViewHolder()
, hãy chọn mọi thứ trừ câu lệnh để khai báo biếnitem
. - Nhấp chuột phải, sau đó chọn Tái cấu trúc > Trích xuất > Hàm.
- Đặt tên hàm
bind
và chấp nhận các tham số đề 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ỏ vào từ
holder
trong thông sốholder
củabind()
. NhấnAlt+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) {...}
- 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ì giờ đây đã có trong
ViewHolder
, nên bạn có thể xóa phầnViewHolder
của chữ ký. Đây là mã cuối cùng 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 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
.
- 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, sau đó chọn Tái cấu trúc > Trích xuất > Hàm.
- Đặt tên hàm
from
và chấp nhận các tham số đề xuất. Nhấp vào OK. - Đặt con trỏ vào tên hàm
from
. NhấnAlt+Enter
(Option+Enter
trên một máy Mac) để mở trình đơn ý định. - 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ớpViewHolder
, không được gọi trên thực thểViewHolder
. - Di chuyển đối tượng
companion
vào lớpViewHolder
. - Đặt
from()
ở chế độ công khai. - Trong
onCreateViewHolder()
, hãy thay đổi câu lệnhreturn
để trả về kết quả gọifrom()
trong lớpViewHolder
.
Các phương thứconCreateViewHolder()
và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)
}
}
- 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ủaViewHolder
nữa.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
- 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ủaRecyclerView
, hãy xác định một phần tử<RecyclerView>
trong tệp bố cục. - LayoutManager
RecyclerView
sử dụngLayoutManager
để sắp xếp bố cục của các mục trongRecyclerView
, 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ínhapp:layoutManager
thành trình quản lý bố cục (chẳng hạn nhưLinearLayoutManager
hoặcGridLayoutManager
).
Bạn cũng có thể đặtLayoutManager
choRecyclerView
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ị trongViewHolder
. Liên kết bộ chuyển đổi vớiRecyclerView
.
KhiRecyclerView
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. - Vì
RecyclerView
không biết gì về dữ liệu nênAdapter
cần phải thông báo choRecyclerView
khi dữ liệu đó thay đổi. Sử dụngnotifyDataSetChanged()
để thông báo choAdapter
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: