Trong bài viết ngày hôm nay, chúng ta sẽ tiếp tục tìm hiểu kỹ thuật sử dụng ViewHolder trong Android của RecyclerView nhé.
Nội dung chính của bài viết
Các kỹ thuật sử dụng ViewHolder trong Android trong RecyclerView
Đây chính điểm khác biệt so với Listview/Gridview khi ViewHolder là thành phần bắt buộc phải có trong Adapter.
Kỹ thuật Cache View trong Adapter (ViewHolder trong Android)
Để tạo PhotoHolder cho view references của bạn, bạn cần một class trong adapter(Người ta gọi là inner class). Tại sao lại là inner class thay vì ở một class riêng biệt bởi cách xử lý của nó liên kết chặt chẽ với adapter. Đầu tiên, bạn cần import package như bên dưới
import kotlinx.android.synthetic.main.recyclerview_item_row.view.*
Sau đó định nghĩa PhotoHolder bên dưới của Recycler adapter class
//1 class PhotoHolder(v: View) : RecyclerView.ViewHolder(v), View.OnClickListener { //2 private var view: View = v private var photo: Photo? = null //3 init { v.setOnClickListener(this) } //4 override fun onClick(v: View) { Log.d("RecyclerView", "CLICK!") } companion object { //5 private val PHOTO_KEY = "PHOTO" } }
Mình sẽ giải thích qua một chút về đoạn code trên:
- Tạo class và kế thừa từ Recycler View.Viewholder, cho phép PhotoHolder được sử dụng như là ViewHolder cho Adapter.
- Thêm reference đối tượng cho phép ViewHolder trong Android có thể keep được view của bạn. Điều này cho phép holder có thể tiếp cận ImageView và TextView như là một extension property. Kotlin Androd Extensions plugin bổ sung thêm các chức năng và trường ẩn bộ nhớ đệm để view không bị truy vấn thường xuyên.
- Khởi tạo hàm lắng nghe sự kiện Onclicklistener
- Implement các phương thức bắt buộc của Onclicklistener Interface. Như mình đã nói ở phần trước, khác với Listview, RecyclerView trong Android thì ViewHolder sẽ chịu trách nhiệm xử lý sự kiện (event handling)
- Thêm key để dễ dàng reference đến một item cụ thể trong Recycler View.
♣ Có thể bạn cần đến cách tạo class trong Kotlin thì sao?
Làm sao để xử lý lỗi trình biên dịch?
Đến đây bạn sẽ vẫn bị trình biên dịch báo lỗi (compiter errors) với hàm onBindViewHolder()
và onCreateViewHolder()
. Lý do là bạn chưa truyền đúng kiểu dữ liệu cho các tham số của chúng. Đơn giản bạn chỉ cần đổi kiểu dữ liệu của tham số holder: ? trong hàm onBindViewHolder
sang RecyclerAdapter.PhotoHolder.
override fun onBindViewHolder(holder: RecyclerAdapter.PhotoHolder, position: Int) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. }
Bạn là tương tự cho hàm RecyclerAdapter.PhotoHolder()
. Như bên dưới:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerAdapter.PhotoHolder { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. }
OK, bây giờ bạn thử complie và chạy ứng dụng xem sao? Tất nhiên, ứng dụng hiện tại sẽ có các item giống nhau vì bạn chưa có implement các logic ở đây. Cứ bình tĩnh nhé!
Implement các hàm logic ViewHolder trong Android
Thỉnh thoảng nếu không có sẵn ViewHolder. Trong trường hợp này, RecyclerView trong Android sẽ yêu cầu onCreateViewHolder ()
từ RecyclerAdapter tạo một cái mới. Đây là lý do tại sao bạn lại thấy có hàm onCreateViewHolder(). Bạn sẽ sử dụng item layout- PhotoHolder- để tạo một view cho ViewHolder.
Có một cách đơn giản là sử dụng inflate trong onCreatViewHolder()
. Tuy nhiên, mình muốn giới thiệu một cách tiếp cận hoàn toàn mới thông qua một tính năng thú vị của kotlin gọi là Extensions.
Đầu tiên, thêm một file kotlin mới đặt tên là Extensions.kt và bổ sung thêm chức năng extension mới vào file này như sau.
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View { return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot) }
Thay thế dòng TODO ((“not implemented”) giữa các dấu ngoặc nhọn trong onCreatViewHolder()
với:
val inflatedView = parent.inflate(R.layout.recyclerview_item_row, false) return PhotoHolder(inflatedView)
Ở đây, bạn inflate các view từ layout và chuyển vào một PhotoHolder. Phương thức parent.inflate (R.layout.recyclerview_item_row, false) sẽ thực hiện chức năng ViewGroup.inflate(..) extension mới để inflate layout.
Tiếp tục nhé. Bạn có thể start Activity khi click vào một item nào đó, đơn giản là implement logic này trong hàm onClick() như sau:
val context = itemView.context val showPhotoIntent = Intent(context, PhotoActivity::class.java) showPhotoIntent.putExtra(PHOTO_KEY, photo) context.startActivity(showPhotoIntent)
Thao tác này lấy thuộc tính của item view được click và tạo intent để start Activity đồng thời pass các object ảnh bạn muốn hiển thị ra màn hình. Việc chuyển object context vào intent cho phép ứng dụng biết Activity của nó là gì.
Tiếp theo là thêm phương thức sau vào trong PhotoHolder
fun bindPhoto(photo: Photo) { this.photo = photo Picasso.with(view.context).load(photo.url).into(view.itemImage) view.itemDate.text = photo.humanDate view.itemDescription.text = photo.explanation }
Điều này giúp bind các bức ảnh vào PhotoHolder, tức là tạo data cho item là bức ảnh để nó có thể hiển thị ra màn hình. Ở đây chúng ta dùng thư viện Picasso để có thể tối ưu trong việc hiển thị ảnh lớn lên ImageView mà không bị treo ứng dụng
Phần việc cuối cùng trong PhotoHolder là chúng ta sẽ code để ứng dụng hiển thị đúng ảnh vào đúng thời điểm. Để làm được điều này, chúng ta sẽ implement trong hàm onBindViewHolder(). Hàm này sẽ cho biết Item mới đang avaiable trên màn hình và holder cần dữ liệu để hiển thị
Bổ sung đoạn code sau vào trong hàm onBindViewHolder ().
val itemPhoto = photos[position] holder.bindPhoto(itemPhoto)
Và đó là tất cả điều bạn cần làm – Chỉ những item nào xuất hiện trên màn hình thì mới lấy ảnh để hiển thị sau đó thì cache vào ViewHolder trong Android
Kết nối Adapter và RecyclerView
Tất cả điều bạn cần làm là kết nối Adapter và RecyclerView trong Android của bạn với nhau và cho ra những bức ảnh không gian tuyệt vời để bạn có thể khám phá vũ trụ bao la kia – mình đùa chút 🙂
Mở MainActivity.kt và khai báo biến này ở đầu class :
private lateinit var adapter: RecyclerAdapter
Tiếp theo, khởi tạo Adapter như sau:
adapter = RecyclerAdapter(photosList) recyclerView.adapter = adapter
Mặc dù bộ adapter được kết nối, nhưng bạn cần phải lấy danh sách ảnh về để đưa vào Adapter.
Trên hàm onStart()
, dưới hàm super(), thêm đoạn code sau:
if (photosList.size == 0) { requestPhoto() }
Điều này chỉ đơn giản là kiểm tra xem danh sách có rỗng không? nếu rỗng thì mới request danh sách ảnh, hạn chế việc request tùm lum mà không cần thiết
Tiếp theo, trong hàm receivedNewPhoto()
, chúng ta thêm photo vào adapter
override fun receivedNewPhoto(newPhoto: Photo) { runOnUiThread { photosList.add(newPhoto) adapter.notifyItemInserted(photosList.size) } }
Ở đây, bạn sẽ thông báo cho Recycler adapter rằng một item đã được bổ sung sau khi danh sách ảnh được cập nhật thông qua hàm notifyItemInserted()
Chạy chương trình, bạn sẽ thấy nhiều thứ như thế này:
Đây không phải là tất cả, bạn thử chạm vào ảnh và bạn sẽ được chào đón bằng một Activity mới:
Chưa hết đâu nhé ! Hãy thử xoay thiết bị xem sao và bạn sẽ thấy hình ảnh ở trạng thái full màn hình đẹp lung linh
Phụ thuộc vào kích thước hình ảnh và màn hình của thiết bị, ảnh có thể bị méo mó biến dạng chút xíu, nhưng đừng lo lắng về điều đó 🙂
Chúc mừng bạn! Bạn đã có một RecyclerView hoạt động tốt.
Các bạn có thể download final source code của bài hướng dẫn tại đây nhé
Như vậy, mình đã kết thúc bài hướng dẫn toàn tập về RecyclerView trong Android bằng ViewHolder trong Android, nếu có chỗ nào chưa hiểu thì comment bên dưới nhé. Hi vọng bài viết nhận được nhiều ý kiến đóng góp từ các bạn. Hẹn gặp lại ở bài viết sau nhé
Bài viết hay và dễ hiểu quá