‫Android Kotlin Fundamentals 07.2: DiffUtil and data binding with RecyclerView

ה-codelab הזה הוא חלק מהקורס Android Kotlin Fundamentals. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא יסודות Kotlin ל-Android.

מבוא

במדריך הקודם, עדכנתם את האפליקציה TrackMySleepQuality כדי להציג נתונים על איכות השינה ב-RecyclerView. הטכניקות שלמדתם כשבניתם את RecyclerView הראשון שלכם מספיקות לרוב RecyclerViews שמציגים רשימות פשוטות שלא גדולות מדי. עם זאת, יש מספר טכניקות שבעזרתן אפשר להשתמש ב-RecyclerView בצורה יעילה יותר ברשימות גדולות, ולשמור על קוד קל לתחזוקה ולהרחבה ברשימות ובטבלאות מורכבות.

ב-codelab הזה, תמשיכו לפתח את אפליקציית מעקב השינה מה-codelab הקודם. תלמדו דרך יעילה יותר לעדכן את רשימת נתוני השינה, ותלמדו איך להשתמש בקישור נתונים עם RecyclerView. (אם אין לכם את האפליקציה מה-codelab הקודם, אתם יכולים להוריד קוד התחלתי ל-codelab הזה).

מה שכדאי לדעת

  • בניית ממשק משתמש בסיסי באמצעות פעילות, רכיבי Fragment ותצוגות.
  • ניווט בין פרגמנטים ושימוש ב-safeArgs כדי להעביר נתונים בין פרגמנטים.
  • אפשר לראות את המודלים, את מפעלי המודלים, את הטרנספורמציות ואת LiveData ואת האובזרברים שלהם.
  • איך יוצרים מסד נתונים של Room, יוצרים DAO ומגדירים ישויות.
  • איך משתמשים ב-coroutines למסד נתונים ולמשימות אחרות שפועלות לאורך זמן.
  • איך מטמיעים RecyclerView בסיסי עם Adapter, ViewHolder ופריסת פריטים.

מה תלמדו

  • איך משתמשים ב-DiffUtil כדי לעדכן ביעילות רשימה שמוצגת על ידי RecyclerView.
  • איך משתמשים בקישור נתונים עם RecyclerView
  • איך משתמשים במתאמי קישור כדי לשנות נתונים.

הפעולות שתבצעו:

  • המשך פיתוח של אפליקציית TrackMySleepQuality מתוך ה-codelab הקודם בסדרה הזו.
  • כדי לעדכן את הרשימה ביעילות באמצעות DiffUtil, צריך לעדכן את SleepNightAdapter.
  • מטמיעים קשירת נתונים עבור RecyclerView באמצעות מתאמי קשירה כדי לבצע טרנספורמציה של הנתונים.

לאפליקציה למעקב אחרי שינה יש שני מסכים, שמיוצגים על ידי פרגמנטים, כמו שמוצג באיור שלמטה.

במסך הראשון, שמוצג בצד ימין, יש לחצנים להפעלה ולהפסקה של המעקב. במסך מוצגים חלק מנתוני השינה של המשתמש. הלחצן ניקוי מוחק באופן סופי את כל הנתונים שהאפליקציה אספה על המשתמש. במסך השני, שמוצג בצד שמאל, בוחרים את דירוג איכות השינה.

האפליקציה הזו מתוכננת להשתמש בבקר ממשק משתמש, ב-ViewModel וב-LiveData, ובמסד נתונים של Room כדי לשמור את נתוני השינה.

נתוני השינה מוצגים בRecyclerView. ב-codelab הזה, תבנו את החלק DiffUtil ואת החלק של קישור הנתונים ל-RecyclerView. אחרי שתסיימו את ה-codelab הזה, האפליקציה שלכם תיראה בדיוק אותו דבר, אבל היא תהיה יעילה יותר ויהיה קל יותר להרחיב ולתחזק אותה.

אתם יכולים להמשיך להשתמש באפליקציית SleepTracker מה-codelab הקודם, או להוריד את אפליקציית RecyclerViewDiffUtilDataBinding-Starter מ-GitHub.

  1. אם צריך, מורידים את אפליקציית RecyclerViewDiffUtilDataBinding-Starter מ-GitHub ופותחים את הפרויקט ב-Android Studio.
  2. מפעילים את האפליקציה.
  3. פותחים את הקובץ SleepNightAdapter.kt.
  4. כדאי לבדוק את הקוד כדי להכיר את המבנה של האפליקציה. בתרשים שלמטה מוסבר איך להשתמש ב-RecyclerView עם תבנית המתאם כדי להציג למשתמש נתוני שינה.

  • על סמך קלט מהמשתמש, האפליקציה יוצרת רשימה של אובייקטים מסוג SleepNight. כל אובייקט SleepNight מייצג לילה אחד של שינה, את משך השינה ואת האיכות שלה.
  • ה-SleepNightAdapter מתאים את רשימת האובייקטים של SleepNight למשהו ש-RecyclerView יכול להשתמש בו ולהציג אותו.
  • המתאם SleepNightAdapter יוצר ViewHolders שמכילים את התצוגות, הנתונים והמטא-נתונים של תצוגת הרשימה הניתנת לגלגול, כדי להציג את הנתונים.
  • 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 שמשמשת לחישוב ההבדלים בין שתי רשימות. ‫DiffUtil לוקח רשימה ישנה ורשימה חדשה ומגלה מה ההבדלים ביניהן. הוא מוצא פריטים שנוספו, הוסרו או שונו. לאחר מכן, המערכת משתמשת באלגוריתם שנקרא Eugene W. אלגוריתם ההבדלים של מאיירס כדי לחשב את המספר המינימלי של שינויים שצריך לבצע ברשימה הישנה כדי ליצור את הרשימה החדשה.

אחרי ש-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(), ואז לוחצים על OK.

    כך נוצרים stub בתוך SleepNightDiffCallback לשתי השיטות, כמו שמוצג למטה. ‫DiffUtil משתמש בשתי השיטות האלה כדי להבין איך הרשימה והפריטים השתנו.
    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 שהועברו, 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 ועוד כמה שיטות בשבילכם. אם יש הבדלים בין 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, כולל שיטת ה-setter. אין יותר צורך בזה, כי 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(), ב-observer ב-sleepTrackerViewModel, מחפשים את השגיאה שבה יש הפניה למשתנה data שמחקתם.
  3. מחליפים את adapter.data = it בשיחה אל adapter.submitList(it). הקוד המעודכן מוצג בהמשך.

sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.submitList(it)
   }
})
  1. מריצים את האפליקציה. היא תפעל מהר יותר, אבל יכול להיות שלא תשימו לב לשינוי אם הרשימה קצרה.

במשימה הזו, משתמשים באותה טכניקה כמו ב-codelabs הקודמים כדי להגדיר קשירת נתונים, ומבטלים את הקריאות ל-findViewById().

שלב 1: מוסיפים קשירת נתונים לקובץ הפריסה

  1. פותחים את קובץ הפריסה list_item_sleep_night.xml בכרטיסייה Text.
  2. מציבים את הסמן על התג ConstraintLayout ומקישים על Alt+Enter (Option+Enter ב-Mac). תפריט הכוונות (תפריט התיקון המהיר) ייפתח.
  3. בוחרים באפשרות Convert to data binding layout (המרה לפריסת קשירת נתונים). הפעולה הזו עוטפת את הפריסה בתג <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, בוחרים באפשרות Build > Clean Project (בנייה > ניקוי הפרויקט), ואז בוחרים באפשרות Build > Rebuild Project (בנייה > בנייה מחדש של הפרויקט). (אם הבעיות נמשכות, בוחרים באפשרות קובץ > ביטול תוקף של מטמונים / הפעלה מחדש). אובייקט הקישור 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. בסוף הפונקציה, במקום להחזיר את הערך view, מחזירים את הערך binding.
return ViewHolder(binding)
  1. כדי להיפטר מהשגיאה, מציבים את הסמן על המילה binding. מקישים על Alt+Enter (או על Option+Enter ב-Mac) כדי לפתוח את תפריט הכוונות.
  1. בוחרים באפשרות Change parameter 'itemView' type of primary constructor of class 'ViewHolder' to 'ListItemSleepNightBinding' (שינוי סוג הפרמטר itemView של בנאי ראשי של המחלקה ViewHolder ל-ListItemSleepNightBinding). הפעולה הזו מעדכנת את סוג הפרמטר של המחלקה ViewHolder.

  1. גוללים למעלה להגדרת המחלקה של ViewHolder כדי לראות את השינוי בחתימה. מוצגת שגיאה לגבי itemView, כי שיניתם את itemView ל-binding בשיטה from().

    בהגדרת המחלקה ViewHolder, לוחצים לחיצה ימנית על אחד המקרים של itemView ובוחרים באפשרות Refactor (ארגון מחדש) > Rename (שינוי שם). משנים את השם ל-binding.
  2. מוסיפים את הקידומת val לפרמטר של ה-constructor‏ binding כדי להפוך אותו למאפיין (property).
  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. בוחרים באפשרות Refactor > Inline (שינוי מבנה > הטמעה), או מקישים על Control+Command+N (Option+Command+N ב-Mac).
  2. מריצים את האפליקציה. (יכול להיות שתצטרכו לנקות ולבנות מחדש את הפרויקט אם יש בו שגיאות).

במשימה הזו תשדרגו את האפליקציה כדי להשתמש בקשירת נתונים עם מתאמי קשירה להגדרת הנתונים בתצוגות.

ב-codelab הקודם השתמשתם במחלקה Transformations כדי לקחת את LiveData וליצור מחרוזות מעוצבות להצגה בתצוגות טקסט. עם זאת, אם אתם צריכים לקשור סוגים שונים או סוגים מורכבים, אתם יכולים לספק מתאמי קשירה כדי לעזור לקשירת הנתונים להשתמש בסוגים האלה. מתאמי איגוד הם מתאמים שלוקחים את הנתונים שלכם ומעבדים אותם כך שניתן יהיה להשתמש בהם באיגוד נתונים כדי לאגד תצוגה, כמו טקסט או תמונה.

תטמיעו שלושה מתאמי קישור, אחד לתמונת האיכות ואחד לכל שדה טקסט. לסיכום, כדי להצהיר על מתאם binding, מגדירים שיטה שמקבלת פריט ותצוגה, ומוסיפים לה את ההערה @BindingAdapter. בגוף השיטה, מטמיעים את הטרנספורמציה. ב-Kotlin, אפשר לכתוב מתאם של Binding כפונקציית הרחבה במחלקת התצוגה שמקבלת את הנתונים.

שלב 1: יצירת מתאמי קשירה

הערה: בשלב הזה תצטרכו לייבא מספר כיתות, והן לא יצוינו בנפרד.

  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. המתאם השני מגדיר את איכות השינה על סמך הערך באובייקט 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. המתאם השלישי של הקישור מגדיר את התמונה בתצוגת תמונה. יוצרים את פונקציית התוסף ב-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(). הקריאה הזו היא אופטימיזציה שמבקשת מ-data binding להפעיל מיד את כל הקישורים שממתינים להפעלה. תמיד מומלץ להתקשר אל executePendingBindings() כשמשתמשים במתאמי איגוד ב-RecyclerView, כי זה יכול להאיץ מעט את שינוי הגודל של התצוגות.
 binding.executePendingBindings()

שלב 3: הוספת קשירות לפריסת XML

  1. פתיחת list_item_sleep_night.xml.
  2. ב-ImageView, מוסיפים מאפיין app עם אותו שם כמו מתאם הקישור שמגדיר את התמונה. מעבירים את המשתנה sleep, כמו שמוצג בהמשך.

    המאפיין הזה יוצר את החיבור בין התצוגה לאובייקט הקישור, דרך המתאם. בכל פעם שיש הפניה ל-sleepImage, המתאם יתאים את הנתונים מ-SleepNight.
app:sleepImage="@{sleep}"
  1. חוזרים על הפעולה גם לגבי תצוגות הטקסט sleep_length ו-quality_string. בכל פעם שיש הפניה אל sleepDurationFormatted או אל sleepQualityString, המתאמים יתאימו את הנתונים מ-SleepNight.
app:sleepDurationFormatted="@{sleep}"
app:sleepQualityString="@{sleep}"
  1. מריצים את האפליקציה. היא פועלת בדיוק כמו קודם. מתאמי הקישור מטפלים בכל העבודה של עיצוב התצוגות ועדכונן כשהנתונים משתנים, וכך מפשטים את ViewHolder ומשפרים את המבנה של הקוד.

הצגת את אותה רשימה בכמה תרגילים אחרונים. זו התנהגות מכוונת, כדי להראות לכם שממשק Adapter מאפשר לכם לתכנן את הקוד בדרכים רבות ושונות. ככל שהקוד מורכב יותר, כך חשוב יותר לתכנן אותו בצורה טובה. באפליקציות בסביבת ייצור, נעשה שימוש בדפוסים האלה ובדפוסים אחרים עם RecyclerView. כל הדפוסים פועלים, ולכל אחד מהם יש יתרונות. הבחירה תלויה במה שאתם בונים.

מזל טוב! בשלב הזה, אתם כבר בדרך הנכונה לשליטה ב-RecyclerView ב-Android.

פרויקט Android Studio: ‏ RecyclerViewDiffUtilDataBinding.

DiffUtil:

  • RecyclerView כולל מחלקה בשם DiffUtil שמשמשת לחישוב ההבדלים בין שתי רשימות.
  • ל-DiffUtil יש מחלקה בשם ItemCallBack שמרחיבים אותה כדי לגלות את ההבדל בין שתי רשימות.
  • במחלקת ItemCallback, צריך לבטל את השיטות areItemsTheSame() ו-areContentsTheSame().

ListAdapter:

  • כדי לנהל רשימות בחינם, אפשר להשתמש במחלקה ListAdapter במקום במחלקה RecyclerView.Adapter. עם זאת, אם משתמשים ב-ListAdapter, צריך לכתוב מתאם משלכם עבור פריסות אחרות, ולכן במעבדת הקוד הזו מוסבר איך לעשות זאת.
  • כדי לפתוח את תפריט הכוונות ב-Android Studio, מציבים את הסמן על פריט קוד כלשהו ומקישים על Alt+Enter (או על Option+Enter ב-Mac). התפריט הזה שימושי במיוחד כשמבצעים רפקטורינג של קוד ויוצרים stubs להטמעת שיטות. התפריט תלוי הקשר, לכן צריך למקם את הסמן בדיוק במקום הנכון כדי לקבל את התפריט הנכון.

קישור נתונים:

  • משתמשים בקשירת נתונים בפריסת הפריט כדי לקשור נתונים לתצוגות.

מתאמי כריכה:

  • בעבר השתמשת ב-Transformations כדי ליצור מחרוזות מנתונים. אם אתם צריכים לקשור נתונים מסוגים שונים או מורכבים, אתם יכולים לספק מתאמי קשירה שיעזרו לקשירת הנתונים להשתמש בהם.
  • כדי להצהיר על מתאם קישור, מגדירים שיטה שמקבלת פריט ותצוגה, ומוסיפים לשיטה את ההערה @BindingAdapter. ב-Kotlin, אפשר לכתוב את מתאם הקישור כפונקציית הרחבה ב-View. מעבירים את שם הנכס שהמתאם מתאים. לדוגמה:
@BindingAdapter("sleepDurationFormatted")
  • בפריסת ה-XML, מגדירים מאפיין app עם אותו שם כמו של מתאם הקישור. מעבירים משתנה עם הנתונים. לדוגמה:
.app:sleepDurationFormatted="@{sleep}"

קורסים ב-Udacity:

מסמכי תיעוד למפתחי Android:

מקורות מידע נוספים:

בקטע הזה מפורטות אפשרויות למשימות ביתיות לתלמידים שעובדים על ה-Codelab הזה כחלק מקורס בהנחיית מדריך. המורה צריך:

  • אם צריך, מקצים שיעורי בית.
  • להסביר לתלמידים איך להגיש מטלות.
  • בודקים את שיעורי הבית.

אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.

אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.

עונים על השאלות הבאות

שאלה 1

אילו מהפעולות הבאות נדרשות כדי להשתמש ב-DiffUtil? יש לבחור בכל האפשרויות הרלוונטיות.

‫▢ הארכת השיעור ItemCallBack.

‫▢ שינוי מברירת המחדל areItemsTheSame().

‫▢ שינוי מברירת המחדל areContentsTheSame().

‫▢ שימוש בקשירת נתונים כדי לעקוב אחרי ההבדלים בין פריטים.

שאלה 2

אילו מההצהרות הבאות נכונות לגבי מתאמי Binding?

‫▢ מתאם של קישור הוא פונקציה עם ההערה @BindingAdapter.

‫▢ שימוש במתאם איגוד מאפשר להפריד בין עיצוב הנתונים לבין מחזיק התצוגה.

▢ אם רוצים להשתמש במתאמי איגוד, צריך להשתמש ב-RecyclerViewAdapter.

‫▢ Binding adapters הם פתרון טוב כשצריך לשנות נתונים מורכבים.

שאלה 3

מתי כדאי להשתמש ב-Transformations במקום במתאם איגוד? יש לבחור בכל האפשרויות הרלוונטיות.

‫▢ הנתונים שלכם פשוטים.

‫▢ אתם מעצבים מחרוזת.

▢ הרשימה ארוכה מאוד.

‫▢ ViewHolder מכיל רק צפייה אחת.

להתחלת השיעור הבא: 7.3: GridLayout with RecyclerView