หลักพื้นฐานของ Android Kotlin 07.2: DiffUtil และการเชื่อมโยงข้อมูลกับ RecyclerView

Codelab นี้เป็นส่วนหนึ่งของหลักสูตรหลักพื้นฐานของ Android Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ Codelab ของหลักสูตรทั้งหมดแสดงอยู่ในหน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin

บทนำ

ใน Codelab ก่อนหน้านี้ คุณได้อัปเดตแอป TrackMySleepQuality เพื่อแสดงข้อมูลเกี่ยวกับคุณภาพการนอนหลับใน RecyclerView เทคนิคที่คุณได้เรียนรู้เมื่อสร้าง RecyclerView ตัวแรกนั้นเพียงพอสำหรับ RecyclerViews ส่วนใหญ่ที่แสดงรายการแบบง่ายๆ ซึ่งมีขนาดไม่ใหญ่เกินไป อย่างไรก็ตาม มีเทคนิคหลายอย่างที่ช่วยให้ RecyclerView มีประสิทธิภาพมากขึ้นสำหรับรายการขนาดใหญ่ และทำให้โค้ดของคุณดูแลรักษาและขยายได้ง่ายขึ้นสำหรับรายการและตารางที่ซับซ้อน

ใน Codelab นี้ คุณจะสร้างแอปติดตามการนอนหลับจาก Codelab ก่อนหน้า คุณจะได้เรียนรู้วิธีที่มีประสิทธิภาพมากขึ้นในการอัปเดตรายการข้อมูลการนอนหลับ และวิธีใช้การเชื่อมโยงข้อมูลกับ RecyclerView (หากไม่มีแอปจากโค้ดแล็บก่อนหน้า คุณสามารถดาวน์โหลดโค้ดเริ่มต้นสำหรับโค้ดแล็บนี้ได้)

สิ่งที่คุณควรทราบอยู่แล้ว

  • การสร้างอินเทอร์เฟซผู้ใช้พื้นฐานโดยใช้กิจกรรม Fragment และมุมมอง
  • การไปยังส่วนย่อยต่างๆ และการใช้ safeArgs เพื่อส่งข้อมูลระหว่างส่วนย่อย
  • ดูโมเดล โรงงานโมเดล การแปลง และ LiveData รวมถึง Observer ของโมเดล
  • วิธีสร้างRoomฐานข้อมูล สร้าง DAO และกําหนดเอนทิตี
  • วิธีใช้โครูทีนสำหรับฐานข้อมูลและงานอื่นๆ ที่ใช้เวลานาน
  • วิธีใช้ RecyclerView พื้นฐานกับ Adapter, ViewHolder และเลย์เอาต์รายการ

สิ่งที่คุณจะได้เรียนรู้

  • วิธีใช้ DiffUtil เพื่ออัปเดตรายการที่แสดงโดย RecyclerView อย่างมีประสิทธิภาพ
  • วิธีใช้การเชื่อมโยงข้อมูลกับ RecyclerView
  • วิธีใช้อะแดปเตอร์การเชื่อมโยงเพื่อเปลี่ยนรูปแบบข้อมูล

สิ่งที่คุณต้องดำเนินการ

  • สร้างต่อจากแอป TrackMySleepQuality จาก Codelab ก่อนหน้าในชุดนี้
  • อัปเดต SleepNightAdapter เพื่ออัปเดตรายการอย่างมีประสิทธิภาพโดยใช้ DiffUtil
  • ใช้การเชื่อมโยงข้อมูลสำหรับ RecyclerView โดยใช้อะแดปเตอร์การเชื่อมโยงเพื่อเปลี่ยนรูปแบบข้อมูล

แอปติดตามการนอนหลับมี 2 หน้าจอซึ่งแสดงด้วย Fragment ดังที่แสดงในรูปภาพด้านล่าง

หน้าจอแรกที่แสดงทางด้านซ้ายมีปุ่มสำหรับเริ่มและหยุดการติดตาม หน้าจอจะแสดงข้อมูลการนอนหลับบางส่วนของผู้ใช้ ปุ่มล้างจะลบข้อมูลทั้งหมดที่แอปเก็บรวบรวมไว้สำหรับผู้ใช้ออกอย่างถาวร หน้าจอที่ 2 ซึ่งแสดงทางด้านขวาใช้สำหรับเลือกคะแนนคุณภาพการนอนหลับ

แอปนี้ได้รับการออกแบบมาให้ใช้ตัวควบคุม UI, ViewModel และ LiveData รวมถึงฐานข้อมูล Room เพื่อจัดเก็บข้อมูลการนอนหลับ

ข้อมูลการนอนหลับจะแสดงในRecyclerView ในโค้ดแล็บนี้ คุณจะได้สร้างส่วน DiffUtil และการเชื่อมโยงข้อมูลสำหรับ RecyclerView หลังจากทำ Codelab นี้แล้ว แอปของคุณจะดูเหมือนเดิมทุกประการ แต่จะมีประสิทธิภาพมากขึ้น รวมถึงปรับขนาดและบำรุงรักษาได้ง่ายขึ้น

คุณใช้แอป SleepTracker จาก Codelab ก่อนหน้าต่อไปได้ หรือจะดาวน์โหลดแอป RecyclerViewDiffUtilDataBinding-Starter จาก GitHub ก็ได้

  1. หากจำเป็น ให้ดาวน์โหลดแอป RecyclerViewDiffUtilDataBinding-Starter จาก GitHub แล้วเปิดโปรเจ็กต์ใน Android Studio
  2. เรียกใช้แอป
  3. เปิดไฟล์ SleepNightAdapter.kt
  4. ตรวจสอบโค้ดเพื่อให้คุ้นเคยกับโครงสร้างของแอป โปรดดูแผนภาพด้านล่างเพื่อสรุปการใช้ RecyclerView กับรูปแบบอะแดปเตอร์เพื่อแสดงข้อมูลการนอนหลับต่อผู้ใช้

  • แอปจะสร้างรายการออบเจ็กต์ SleepNight จากข้อมูลที่ผู้ใช้ป้อน ออบเจ็กต์ SleepNight แต่ละรายการแสดงถึงการนอนหลับ 1 คืน ระยะเวลา และคุณภาพของการนอนหลับ
  • SleepNightAdapter จะปรับรายการออบเจ็กต์ SleepNight ให้เป็นสิ่งที่ RecyclerView ใช้และแสดงได้
  • SleepNightAdapter อะแดปเตอร์จะสร้าง ViewHolders ที่มีมุมมอง ข้อมูล และข้อมูลเมตาเพื่อให้ RecyclerView แสดงข้อมูล
  • RecyclerView ใช้ SleepNightAdapter เพื่อกำหนดจำนวนรายการที่จะแสดง (getItemCount()) RecyclerView ใช้ onCreateViewHolder() และ onBindViewHolder() เพื่อรับตัวยึดมุมมองที่เชื่อมโยงกับข้อมูลสำหรับการแสดง

เมธอด notifyDataSetChanged() ไม่มีประสิทธิภาพ

หากต้องการบอก RecyclerView ว่ารายการในลิสต์มีการเปลี่ยนแปลงและต้องอัปเดต โค้ดปัจจุบันจะเรียก notifyDataSetChanged() ใน SleepNightAdapter ดังที่แสดงด้านล่าง

var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

แต่ notifyDataSetChanged() จะบอก RecyclerView ว่ารายการทั้งหมดอาจไม่ถูกต้อง ด้วยเหตุนี้ RecyclerView จึงผูกและวาดรายการทั้งหมดในลิสต์ใหม่ รวมถึงรายการที่มองไม่เห็นบนหน้าจอ ซึ่งเป็นงานที่ไม่จำเป็น สำหรับรายการขนาดใหญ่หรือซับซ้อน กระบวนการนี้อาจใช้เวลานานพอที่จอแสดงผลจะกะพริบหรือติดขัดขณะที่ผู้ใช้เลื่อนดูรายการ

หากต้องการแก้ไขปัญหานี้ คุณสามารถบอก RecyclerView ได้ว่ามีการเปลี่ยนแปลงอะไรบ้าง จากนั้น RecyclerView จะอัปเดตเฉพาะมุมมองที่เปลี่ยนไปบนหน้าจอ

RecyclerView มี API ที่ครอบคลุมสำหรับการอัปเดตองค์ประกอบเดียว คุณสามารถใช้ notifyItemChanged() เพื่อแจ้งให้ RecyclerView ทราบว่ามีการเปลี่ยนแปลงรายการ และใช้ฟังก์ชันที่คล้ายกันสำหรับรายการที่เพิ่ม นำออก หรือย้าย คุณอาจทำทุกอย่างด้วยตนเองได้ แต่การดำเนินการดังกล่าวไม่ใช่เรื่องง่ายและอาจต้องใช้โค้ดจำนวนมาก

โชคดีที่ยังมีวิธีที่ดีกว่า

DiffUtil มีประสิทธิภาพและช่วยให้คุณไม่ต้องทำงานที่ซับซ้อน

RecyclerView มีคลาสชื่อ DiffUtil ซึ่งใช้ในการคำนวณความแตกต่างระหว่าง 2 รายการ DiffUtil จะนำรายการเก่าและรายการใหม่มาเปรียบเทียบเพื่อดูว่ามีอะไรแตกต่างกัน โดยจะค้นหารายการที่เพิ่ม นำออก หรือเปลี่ยนแปลง จากนั้นจะใช้อัลกอริทึมที่เรียกว่า Eugene W. อัลกอริทึมความแตกต่างของ Myers เพื่อหาจำนวนการเปลี่ยนแปลงขั้นต่ำที่ต้องทำจากรายการเก่าเพื่อสร้างรายการใหม่

เมื่อ DiffUtil ทราบว่ามีการเปลี่ยนแปลงอะไรบ้าง RecyclerView ก็จะใช้ข้อมูลดังกล่าวเพื่ออัปเดตเฉพาะรายการที่มีการเปลี่ยนแปลง เพิ่ม นำออก หรือย้าย ซึ่งมีประสิทธิภาพมากกว่าการทำรายการทั้งหมดซ้ำ

ในงานนี้ คุณจะอัปเกรด SleepNightAdapter เพื่อใช้ DiffUtil ในการเพิ่มประสิทธิภาพ RecyclerView สำหรับการเปลี่ยนแปลงข้อมูล

ขั้นตอนที่ 1: ติดตั้งใช้งาน SleepNightDiffCallback

หากต้องการใช้ฟังก์ชันของคลาส DiffUtil ให้ขยาย DiffUtil.ItemCallback

  1. เปิด SleepNightAdapter.kt
  2. ใต้คำจำกัดความของคลาสแบบเต็มสำหรับ SleepNightAdapter ให้สร้างคลาสระดับบนสุดใหม่ชื่อ SleepNightDiffCallback ที่ขยาย DiffUtil.ItemCallback ส่ง SleepNight เป็นพารามิเตอร์ทั่วไป
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
}
  1. วางเคอร์เซอร์ในSleepNightDiffCallbackชื่อชั้นเรียน
  2. กด Alt+Enter (Option+Enter ใน Mac) แล้วเลือกใช้สมาชิก
  3. ในกล่องโต้ตอบที่เปิดขึ้น ให้กด Shift แล้วคลิกซ้ายเพื่อเลือกเมธอด areItemsTheSame() และ areContentsTheSame() จากนั้นคลิกตกลง

    ซึ่งจะสร้าง Stub ภายใน SleepNightDiffCallback สำหรับเมธอดทั้ง 2 ดังที่แสดงด้านล่าง DiffUtil ใช้ 2 วิธีนี้เพื่อดูว่ารายการและรายการมีการเปลี่ยนแปลงอย่างไร
    override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
  1. ใน areItemsTheSame() ให้แทนที่ TODO ด้วยโค้ดที่ทดสอบว่ารายการ SleepNight ที่ส่งเข้ามา 2 รายการ ซึ่งได้แก่ oldItem และ newItem เหมือนกันหรือไม่ หากสินค้ามี nightId เดียวกัน แสดงว่าเป็นสินค้าเดียวกัน ให้ส่งคืน true ไม่เช่นนั้น ให้แสดงผล false DiffUtil ใช้การทดสอบนี้เพื่อช่วยตรวจสอบว่ามีการเพิ่ม นำออก หรือย้ายรายการหรือไม่
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem.nightId == newItem.nightId
}
  1. ใน areContentsTheSame() ให้ตรวจสอบว่า oldItem และ newItem มีข้อมูลเดียวกันหรือไม่ นั่นคือเท่ากันหรือไม่ การตรวจสอบความเท่ากันนี้จะตรวจสอบทุกฟิลด์เนื่องจาก SleepNight เป็นคลาสข้อมูล Data คลาสจะกำหนด equals และเมธอดอื่นๆ อีก 2-3 รายการให้คุณโดยอัตโนมัติ หาก oldItem และ newItem แตกต่างกัน โค้ดนี้จะแจ้งให้ DiffUtil ทราบว่ามีการอัปเดตรายการแล้ว
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem == newItem
}

รูปแบบที่ใช้กันโดยทั่วไปคือการใช้ RecyclerView เพื่อแสดงรายการที่มีการเปลี่ยนแปลง RecyclerView มีคลาสอะแดปเตอร์ ListAdapter ที่ช่วยคุณสร้างอะแดปเตอร์ RecyclerView ที่มีรายการเป็นข้อมูลสำรอง

ListAdapter จะติดตามรายการให้คุณและแจ้งเตือนอะแดปเตอร์เมื่อมีการอัปเดตรายการ

ขั้นตอนที่ 1: เปลี่ยนอแดปเตอร์ให้ขยาย ListAdapter

  1. ในSleepNightAdapter.kt ให้เปลี่ยนลายเซ็นของคลาส SleepNightAdapter เพื่อขยาย ListAdapter
  2. หากได้รับข้อความแจ้ง ให้นำเข้า androidx.recyclerview.widget.ListAdapter
  3. เพิ่ม SleepNight เป็นอาร์กิวเมนต์แรกใน ListAdapter ก่อน SleepNightAdapter.ViewHolder
  4. เพิ่ม SleepNightDiffCallback() เป็นพารามิเตอร์ในเครื่องมือสร้าง ListAdapter จะใช้ข้อมูลนี้เพื่อดูว่ามีการเปลี่ยนแปลงอะไรในรายการ SleepNightAdapter ลายเซ็นชั้นเรียนที่เสร็จสมบูรณ์แล้วควรมีลักษณะดังที่แสดงด้านล่าง
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
  1. ในคลาส SleepNightAdapter ให้ลบฟิลด์ data รวมถึงตัวตั้งค่า คุณไม่จำเป็นต้องใช้รายการนี้อีกต่อไป เนื่องจาก ListAdapter จะติดตามรายการให้คุณ
  2. ลบล้าง getItemCount() เนื่องจาก ListAdapter จะใช้วิธีนี้ให้คุณ
  3. หากต้องการแก้ไขข้อผิดพลาดใน onBindViewHolder() ให้เปลี่ยนตัวแปร item แทนที่จะใช้ data เพื่อรับ item ให้เรียกใช้เมธอด getItem(position) ที่ ListAdapter จัดเตรียมไว้
val item = getItem(position)

ขั้นตอนที่ 2: ใช้ submitList() เพื่ออัปเดตรายการ

โค้ดของคุณต้องแจ้งให้ ListAdapter ทราบเมื่อมีรายการที่เปลี่ยนแปลง ListAdapter มีเมธอดที่เรียกว่า submitList() เพื่อแจ้งให้ ListAdapter ทราบว่ามีรายการเวอร์ชันใหม่พร้อมใช้งานแล้ว เมื่อเรียกใช้เมธอดนี้ ListAdapter จะเปรียบเทียบรายการใหม่กับรายการเก่า และตรวจหารายการที่เพิ่ม นำออก ย้าย หรือเปลี่ยนแปลง จากนั้น ListAdapter จะอัปเดตสินค้าที่ RecyclerView แสดง

  1. เปิด SleepTrackerFragment.kt
  2. ใน onCreateView() ในเครื่องมือตรวจสอบใน sleepTrackerViewModel ให้ค้นหาข้อผิดพลาดที่มีการอ้างอิงตัวแปร data ที่คุณลบไปแล้ว
  3. แทนที่ adapter.data = it ด้วยการเรียกใช้ adapter.submitList(it) โค้ดที่อัปเดตแล้วจะแสดงอยู่ด้านล่าง

sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.submitList(it)
   }
})
  1. เรียกใช้แอป แอปจะทำงานเร็วขึ้น แต่คุณอาจไม่สังเกตเห็นหากรายการมีขนาดเล็ก

ในงานนี้ คุณจะได้ใช้เทคนิคเดียวกับใน Codelab ก่อนหน้าเพื่อตั้งค่าการเชื่อมโยงข้อมูล และยกเลิกการเรียกใช้ findViewById()

ขั้นตอนที่ 1: เพิ่มการเชื่อมโยงข้อมูลไปยังไฟล์เลย์เอาต์

  1. เปิดไฟล์เลย์เอาต์ list_item_sleep_night.xml ในแท็บข้อความ
  2. วางเคอร์เซอร์บนแท็ก ConstraintLayout แล้วกด Alt+Enter (Option+Enter ใน Mac) เมนูความตั้งใจ (เมนู "แก้ไขด่วน") จะเปิดขึ้น
  3. เลือกแปลงเป็นเลย์เอาต์การเชื่อมโยงข้อมูล ซึ่งจะรวมเลย์เอาต์ไว้ใน <layout> และเพิ่มแท็ก <data> ไว้ด้านใน
  4. เลื่อนกลับไปที่ด้านบนหากจำเป็น และภายในแท็ก <data> ให้ประกาศตัวแปรชื่อ sleep
  5. ตั้งค่า type เป็นชื่อที่สมบูรณ์ของ SleepNight, com.example.android.trackmysleepquality.database.SleepNight แท็ก <data> ที่เสร็จสมบูรณ์แล้วควรมีลักษณะดังที่แสดงด้านล่าง
   <data>
        <variable
            name="sleep"
            type="com.example.android.trackmysleepquality.database.SleepNight"/>
    </data>
  1. หากต้องการบังคับให้สร้างออบเจ็กต์ Binding ให้เลือกสร้าง > ล้างโปรเจ็กต์ แล้วเลือกสร้าง > สร้างโปรเจ็กต์ใหม่ (หากยังพบปัญหาอยู่ ให้เลือกไฟล์ > ล้างแคช / รีสตาร์ท) ระบบจะเพิ่มListItemSleepNightBindingออบเจ็กต์การเชื่อมโยงพร้อมกับโค้ดที่เกี่ยวข้องลงในไฟล์ที่สร้างขึ้นของโปรเจ็กต์

ขั้นตอนที่ 2: ขยายเลย์เอาต์ของรายการโดยใช้การเชื่อมโยงข้อมูล

  1. เปิด SleepNightAdapter.kt
  2. ในViewHolderคลาส ให้ค้นหาวิธีfrom()
  3. ลบการประกาศตัวแปร view

รหัสสำหรับลบ

val view = layoutInflater
       .inflate(R.layout.list_item_sleep_night, parent, false)
  1. ในตำแหน่งที่มีตัวแปร view ให้กำหนดตัวแปรใหม่ชื่อ binding ซึ่งจะขยายออบเจ็กต์การเชื่อมโยง ListItemSleepNightBinding ดังที่แสดงด้านล่าง นำเข้าออบเจ็กต์การเชื่อมโยงที่จำเป็น
val binding =
ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
  1. ที่ส่วนท้ายของฟังก์ชัน ให้ส่งคืน binding แทนที่จะส่งคืน view
return ViewHolder(binding)
  1. หากต้องการแก้ไขข้อผิดพลาด ให้วางเคอร์เซอร์บนคำว่า binding กด Alt+Enter (Option+Enter ใน Mac) เพื่อเปิดเมนูความตั้งใจ
  1. เลือกเปลี่ยนประเภทพารามิเตอร์ "itemView" ของตัวสร้างหลักของคลาส "ViewHolder" เป็น "ListItemSleepNightBinding" การดำเนินการนี้จะอัปเดตประเภทพารามิเตอร์ของคลาส ViewHolder

  1. เลื่อนขึ้นไปที่คำจำกัดความของคลาสของ ViewHolder เพื่อดูการเปลี่ยนแปลงในลายเซ็น คุณเห็นข้อผิดพลาดสำหรับ itemView เนื่องจากคุณเปลี่ยน itemView เป็น binding ในเมธอด from()

    ในคำจำกัดความของคลาส ViewHolder ให้คลิกขวาที่อินสแตนซ์ของ itemView แล้วเลือกปรับโครงสร้าง > เปลี่ยนชื่อ เปลี่ยนชื่อเป็น binding
  2. นำหน้าพารามิเตอร์ของตัวสร้าง binding ด้วย val เพื่อให้เป็นพร็อพเพอร์ตี้
  3. ในการเรียกคลาสหลัก RecyclerView.ViewHolder ให้เปลี่ยนพารามิเตอร์จาก binding เป็น binding.root คุณต้องส่ง View และ binding.root คือรูท ConstraintLayout ในเลย์เอาต์ของสินค้า
  4. การประกาศคลาสที่เสร็จสมบูรณ์แล้วควรมีลักษณะเหมือนโค้ดด้านล่าง
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){

นอกจากนี้ คุณยังเห็นข้อผิดพลาดสำหรับการเรียกไปยัง findViewById() และคุณจะแก้ไขข้อผิดพลาดนี้ในขั้นตอนถัดไป

ขั้นตอนที่ 3: แทนที่ findViewById()

ตอนนี้คุณสามารถอัปเดตพร็อพเพอร์ตี้ sleepLength, quality และ qualityImage เพื่อใช้ออบเจ็กต์ binding แทน findViewById() ได้แล้ว

  1. เปลี่ยนการเริ่มต้นของ sleepLength, qualityString และ qualityImage เพื่อใช้วิวของออบเจ็กต์ binding ดังที่แสดงด้านล่าง หลังจากนี้ โค้ดของคุณไม่ควรแสดงข้อผิดพลาดอีก
val sleepLength: TextView = binding.sleepLength
val quality: TextView = binding.qualityString
val qualityImage: ImageView = binding.qualityImage

เมื่อมีออบเจ็กต์การเชื่อมโยงแล้ว คุณก็ไม่จำเป็นต้องกำหนดพร็อพเพอร์ตี้ sleepLength, quality และ qualityImage อีกต่อไป DataBinding จะแคชการค้นหา จึงไม่จำเป็นต้องประกาศพร็อพเพอร์ตี้เหล่านี้

  1. คลิกขวาที่ชื่อพร็อพเพอร์ตี้ sleepLength, quality และ qualityImage เลือกจัดระเบียบใหม่ > แทรก หรือกด Control+Command+N (Option+Command+N ใน Mac)
  2. เรียกใช้แอป (คุณอาจต้องล้างและสร้างใหม่โปรเจ็กต์หากมีข้อผิดพลาด)

ในงานนี้ คุณจะได้อัปเกรดแอปให้ใช้การเชื่อมโยงข้อมูลกับ Binding Adapter เพื่อตั้งค่าข้อมูลใน View

ในโค้ดแล็บก่อนหน้านี้ คุณได้ใช้คลาส Transformations เพื่อใช้ LiveData และสร้างสตริงที่จัดรูปแบบเพื่อแสดงในมุมมองข้อความ อย่างไรก็ตาม หากต้องการเชื่อมโยงประเภทต่างๆ หรือประเภทที่ซับซ้อน คุณสามารถระบุ Binding Adapter เพื่อช่วยให้การเชื่อมโยงข้อมูลใช้ประเภทเหล่านั้นได้ Binding Adapter คือ Adapter ที่รับข้อมูลและปรับให้เป็นสิ่งที่การเชื่อมโยงข้อมูลใช้เชื่อมโยง View ได้ เช่น ข้อความหรือรูปภาพ

คุณจะใช้ Binding Adapter 3 ตัว ตัวหนึ่งสำหรับรูปภาพคุณภาพ และอีก 2 ตัวสำหรับช่องข้อความแต่ละช่อง โดยสรุปคือ หากต้องการประกาศ Binding Adapter ให้กำหนดเมธอดที่รับรายการและ View แล้วใส่คำอธิบายประกอบด้วย @BindingAdapter ในส่วนเนื้อหาของเมธอด ให้ใช้การเปลี่ยนรูปแบบ ใน Kotlin คุณสามารถเขียน Binding Adapter เป็นฟังก์ชันส่วนขยายในคลาส View ที่รับข้อมูลได้

ขั้นตอนที่ 1: สร้าง Binding Adapter

โปรดทราบว่าคุณจะต้องนำเข้าชั้นเรียนหลายชั้นเรียนในขั้นตอนนี้ และระบบจะไม่ระบุชั้นเรียนแต่ละชั้นเรียน

  1. เปิด SleepNightAdapater.kt
  2. ภายในViewHolderคลาส ให้ค้นหาเมธอด bind() แล้วเตือนตัวเองว่าเมธอดนี้ทำอะไร คุณจะใช้โค้ดที่คำนวณค่าสำหรับ binding.sleepLength, binding.quality และ binding.qualityImage และใช้โค้ดนั้นภายในอแดปเตอร์แทน (ตอนนี้ให้ปล่อยรหัสไว้ตามเดิม คุณจะย้ายรหัสในขั้นตอนถัดไป)
  3. ในsleeptrackerแพ็กเกจ ให้สร้างและเปิดไฟล์ชื่อ BindingUtils.kt
  4. ประกาศฟังก์ชันส่วนขยายใน TextView ชื่อ setSleepDurationFormatted และส่ง SleepNight ฟังก์ชันนี้จะเป็นตัวดัดแปลงสำหรับการคำนวณและจัดรูปแบบระยะเวลาการนอนหลับ
fun TextView.setSleepDurationFormatted(item: SleepNight) {}
  1. ในเนื้อหาของ setSleepDurationFormatted ให้เชื่อมโยงข้อมูลกับมุมมองเช่นเดียวกับที่ทำใน ViewHolder.bind() เรียกใช้ convertDurationToFormatted() แล้วตั้งค่า text ของ TextView เป็นข้อความที่จัดรูปแบบ (เนื่องจากนี่คือฟังก์ชันส่วนขยายใน TextView คุณจึงเข้าถึงพร็อพเพอร์ตี้ text ได้โดยตรง)
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
  1. หากต้องการบอกการเชื่อมโยงข้อมูลเกี่ยวกับอแดปเตอร์การเชื่อมโยงนี้ ให้ใส่คำอธิบายประกอบฟังก์ชันด้วย @BindingAdapter
  2. ฟังก์ชันนี้เป็นตัวดัดแปลงสำหรับแอตทริบิวต์ sleepDurationFormatted ดังนั้นให้ส่ง sleepDurationFormatted เป็นอาร์กิวเมนต์ไปยัง @BindingAdapter
@BindingAdapter("sleepDurationFormatted")
  1. อแดปเตอร์ที่ 2 จะตั้งค่าคุณภาพการนอนหลับตามค่าในออบเจ็กต์ SleepNight สร้างฟังก์ชันส่วนขยายชื่อ setSleepQualityString() ใน TextView แล้วส่ง SleepNight
  2. ในเนื้อหา ให้เชื่อมโยงข้อมูลกับมุมมองเช่นเดียวกับที่ทำใน ViewHolder.bind() โทรหา convertNumericQualityToString แล้วตั้ง text
  3. ใส่คำอธิบายประกอบฟังก์ชันด้วย @BindingAdapter("sleepQualityString")
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
   text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
  1. Binding Adapter ที่ 3 จะตั้งค่ารูปภาพใน ImageView สร้างฟังก์ชันส่วนขยายใน ImageView เรียกใช้ setSleepImage และใช้โค้ดจาก ViewHolder.bind() ดังที่แสดงด้านล่าง
@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight) {
   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
   })
}

ขั้นตอนที่ 2: อัปเดต SleepNightAdapter

  1. เปิด SleepNightAdapter.kt
  2. ลบทุกอย่างในbind()เมธอด เนื่องจากตอนนี้คุณใช้การเชื่อมโยงข้อมูลและอแดปเตอร์ใหม่เพื่อทำงานนี้แทนได้แล้ว
fun bind(item: SleepNight) {
}
  1. ใน bind() ให้กำหนดการนอนหลับเป็น item เนื่องจากคุณต้องแจ้งออบเจ็กต์การเชื่อมโยงเกี่ยวกับ SleepNight ใหม่
binding.sleep = item
  1. เพิ่ม binding.executePendingBindings() ใต้บรรทัดนั้น การเรียกนี้เป็นการเพิ่มประสิทธิภาพที่ขอให้การเชื่อมโยงข้อมูลดำเนินการเชื่อมโยงที่รอดำเนินการทันที การเรียก executePendingBindings() เมื่อใช้ Binding Adapter ใน RecyclerView เป็นความคิดที่ดีเสมอ เนื่องจากจะช่วยเพิ่มความเร็วในการกำหนดขนาดของ View ได้เล็กน้อย
 binding.executePendingBindings()

ขั้นตอนที่ 3: เพิ่มการเชื่อมโยงไปยังเลย์เอาต์ XML

  1. เปิด list_item_sleep_night.xml
  2. ใน ImageView ให้เพิ่มพร็อพเพอร์ตี้ app ที่มีชื่อเดียวกันกับ Binding Adapter ที่ตั้งค่ารูปภาพ ส่งตัวแปร sleep ดังที่แสดงด้านล่าง

    พร็อพเพอร์ตี้นี้จะสร้างการเชื่อมต่อระหว่างข้อมูลพร็อพเพอร์ตี้กับออบเจ็กต์การเชื่อมโยงผ่านอแดปเตอร์ เมื่อใดก็ตามที่มีการอ้างอิง sleepImage อะแดปเตอร์จะปรับข้อมูลจาก SleepNight
app:sleepImage="@{sleep}"
  1. ทำเช่นเดียวกันกับมุมมองข้อความ sleep_length และ quality_string เมื่อใดก็ตามที่มีการอ้างอิงถึง sleepDurationFormatted หรือ sleepQualityString อะแดปเตอร์จะปรับข้อมูลจาก SleepNight
app:sleepDurationFormatted="@{sleep}"
app:sleepQualityString="@{sleep}"
  1. เรียกใช้แอป ซึ่งจะทำงานเหมือนเดิมทุกประการ Binding Adapter จะจัดการงานทั้งหมดในการจัดรูปแบบและอัปเดต View เมื่อข้อมูลเปลี่ยนแปลง ซึ่งจะช่วยลดความซับซ้อนของ ViewHolder และทำให้โค้ดมีโครงสร้างที่ดีขึ้นกว่าเดิมมาก

คุณได้แสดงรายการเดียวกันในการออกกำลังกาย 2-3 ครั้งล่าสุด ซึ่งเป็นไปตามการออกแบบเพื่อแสดงให้เห็นว่าAdapterอินเทอร์เฟซช่วยให้คุณออกแบบโค้ดได้หลายวิธี ยิ่งโค้ดมีความซับซ้อนมากเท่าใด การออกแบบโค้ดให้ดีก็ยิ่งมีความสำคัญมากขึ้นเท่านั้น ในแอปเวอร์ชันที่ใช้งานจริง รูปแบบเหล่านี้และรูปแบบอื่นๆ จะใช้กับ RecyclerView รูปแบบทั้งหมดใช้งานได้และแต่ละรูปแบบก็มีประโยชน์ในตัว โดยคุณจะเลือกใช้ตัวใดตัวหนึ่งก็ได้ ขึ้นอยู่กับสิ่งที่คุณกำลังสร้าง

ยินดีด้วย ตอนนี้คุณก็พร้อมที่จะเชี่ยวชาญ RecyclerView ใน Android แล้ว

โปรเจ็กต์ Android Studio: RecyclerViewDiffUtilDataBinding

DiffUtil:

  • RecyclerView มีคลาสชื่อ DiffUtil ซึ่งใช้ในการคำนวณความแตกต่างระหว่าง 2 รายการ
  • DiffUtil มีคลาสชื่อ ItemCallBack ที่คุณขยายเพื่อหาความแตกต่างระหว่าง 2 รายการ
  • ในคลาส ItemCallback คุณต้องลบล้างเมธอด areItemsTheSame() และ areContentsTheSame()

ListAdapter:

  • หากต้องการจัดการรายการบางอย่างได้ฟรี คุณสามารถใช้คลาส ListAdapter แทน RecyclerView.Adapter ได้ อย่างไรก็ตาม หากคุณใช้ ListAdapter คุณจะต้องเขียนอแดปเตอร์ของคุณเองสำหรับเลย์เอาต์อื่นๆ ซึ่งเป็นเหตุผลที่ Codelab นี้แสดงวิธีทำ
  • หากต้องการเปิดเมนูความตั้งใจใน Android Studio ให้วางเคอร์เซอร์บนรายการโค้ด แล้วกด Alt+Enter (Option+Enter ใน Mac) เมนูนี้มีประโยชน์อย่างยิ่งสำหรับการปรับโครงสร้างโค้ดและการสร้าง Stub สำหรับการใช้เมธอด เมนูจะขึ้นอยู่กับบริบท ดังนั้นคุณต้องวางเคอร์เซอร์ให้ตรงตำแหน่งเพื่อรับเมนูที่ถูกต้อง

การเชื่อมโยงข้อมูล:

  • ใช้การเชื่อมโยงข้อมูลในเลย์เอาต์ของรายการเพื่อเชื่อมโยงข้อมูลกับมุมมอง

การเชื่อมโยงอะแดปเตอร์

  • ก่อนหน้านี้คุณใช้ Transformations เพื่อสร้างสตริงจากข้อมูล หากต้องการเชื่อมโยงข้อมูลประเภทต่างๆ หรือข้อมูลที่ซับซ้อน ให้ระบุอะแดปเตอร์การเชื่อมโยงเพื่อช่วยให้การเชื่อมโยงข้อมูลใช้ข้อมูลเหล่านั้นได้
  • หากต้องการประกาศ Binding Adapter ให้กำหนดเมธอดที่รับรายการและมุมมอง แล้วใส่คำอธิบายประกอบเมธอดด้วย @BindingAdapter ใน Kotlin คุณสามารถเขียน Binding Adapter เป็นฟังก์ชันส่วนขยายใน View ได้ ส่งชื่อของพร็อพเพอร์ตี้ที่อแดปเตอร์ปรับ เช่น
@BindingAdapter("sleepDurationFormatted")
  • ในเลย์เอาต์ XML ให้ตั้งค่าพร็อพเพอร์ตี้ app ที่มีชื่อเดียวกับ Binding Adapter ส่งตัวแปรพร้อมข้อมูล เช่น
.app:sleepDurationFormatted="@{sleep}"

หลักสูตรของ Udacity

เอกสารประกอบสำหรับนักพัฒนาแอป Android

แหล่งข้อมูลอื่นๆ

ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้

  • มอบหมายการบ้านหากจำเป็น
  • สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
  • ให้คะแนนงานการบ้าน

ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม

หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ

ตอบคำถามต่อไปนี้

คำถามที่ 1

ข้อใดต่อไปนี้จำเป็นต่อการใช้ DiffUtil เลือกได้มากกว่า 1 ข้อ

▢ ขยายItemCallBackชั้นเรียน

▢ ลบล้าง areItemsTheSame()

▢ ลบล้าง areContentsTheSame()

▢ ใช้การเชื่อมโยงข้อมูลเพื่อติดตามความแตกต่างระหว่างรายการ

คำถามที่ 2

ข้อใดต่อไปนี้เป็นจริงเกี่ยวกับ Binding Adapter

▢ Binding Adapter คือฟังก์ชันที่มีคำอธิบายประกอบด้วย @BindingAdapter

▢ การใช้ Binding Adapter ช่วยให้คุณแยกการจัดรูปแบบข้อมูลออกจาก View Holder ได้

▢ คุณต้องใช้ RecyclerViewAdapter หากต้องการใช้อะแดปเตอร์สำหรับยึด

▢ อะแดปเตอร์การเชื่อมโยงเป็นโซลูชันที่ดีเมื่อคุณต้องการเปลี่ยนรูปแบบข้อมูลที่ซับซ้อน

คำถาม 3

คุณควรพิจารณาใช้ Transformations แทนอะแดปเตอร์แบบยึดเมื่อใด เลือกได้มากกว่า 1 ข้อ

▢ ข้อมูลของคุณเรียบง่าย

▢ คุณกำลังจัดรูปแบบสตริง

▢ รายการของคุณยาวมาก

ViewHolder มีเฉพาะมุมมองเดียว

เริ่มบทเรียนถัดไป: 7.3: GridLayout ด้วย RecyclerView