Hướng dẫn RecyclerView – Phần 3: ViewHolder trong Android

1
Dịch vụ dạy kèm gia sư lập trình
Bài này thuộc phần 2 của 2 phần trong series Hướng dẫn toàn tập RecyclerView trong Android

Hướng dẫn RecyclerView - Phần 3: ViewHolder trong Android

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é.

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:

  1. Tạo class và kế thừa từ Recycler View.Viewholder, cho phép PhotoHolder được sử dụng như là ViewHolder cho Adapter.
  2. 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.
  3. Khởi tạo hàm lắng nghe sự kiện Onclicklistener
  4. 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)
  5. 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()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:

Hướng dẫn RecyclerView - Phần 3: ViewHolder trong Android

Đâ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:

Hướng dẫn RecyclerView - Phần 3: ViewHolder trong Android

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

Hướng dẫn RecyclerView - Phần 3: ViewHolder trong Android

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 đó 🙂

Hướng dẫn RecyclerView - Phần 3: ViewHolder trong Android

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é

Xem tiếp các bài trong Series
Phần trước: Hướng dẫn RecyclerView trong Android- Phần 2: Layout Adapter
Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng
Bài trướcHướng dẫn RecyclerView trong Android- Phần 2: Layout Adapter
Bài tiếp theoXem Bóng đá “tẹt ga” với gói cước DATA khủng chỉ từ 5K
Tên đầy đủ là Dương Anh Sơn. Tốt nghiệp ĐH Bách Khoa Hà Nội. Mình bắt đầu nghiệp coder khi mà ra trường chẳng xin được việc đúng chuyên ngành. Mình tin rằng chỉ có chia sẻ kiến thức mới là cách học tập nhanh nhất. Các bạn góp ý bài viết của mình bằng cách comment bên dưới nhé !

1
Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Mới nhất Cũ nhất Nhiều voted nhất
Thông báo
Ngô Tuấn Nghĩa
Guest
Ngô Tuấn Nghĩa

Bài viết hay và dễ hiểu quá