Hướng dẫn toàn tập RecyclerView trong Android- Phần 3: ViewHolder

0
21
Bài này thuộc phần 3 của 3 phần trong series Hướng dẫn toàn tập RecyclerView trong Android

recyclerView-Android

Kỹ thuật Cache View trong Adapter

Để 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 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, trong RecyclerView 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.

Đế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 trong ViewHolder

Thỉnh thoảng nếu không có sẵn ViewHolder. Trong trường hợp này, RecyclerView 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

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

7-RecyclerView-Working-281x500

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

8-Focus-Activity-281x500

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

9-Landscape-Focus-650x366

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

stars_so_beautiful-320x320

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, 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 toàn tập RecyclerView trong Android- Phần 2: Layout và Adapter

BÌNH LUẬN

Please enter your comment!
Please enter your name here