Android Kotlin Fundamentals 07.1: היסודות של RecyclerView

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

מבוא

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

דברים שחשוב לדעת

כדאי שתכירו את:

  • בניית ממשק משתמש בסיסי (UI) באמצעות פעילות, שברים וצפיות.
  • לנווט בין מקטעים, ולהשתמש ב-safeArgs כדי להעביר נתונים בין מקטעים.
  • שימוש במודלים של תצוגה מפורטת, צפייה במפעלים לבניית מודלים, טרנספורמציות ו-LiveData והצופים שלהם.
  • יצירה של מסד נתונים מסוג Room, יצירת DAO והגדרה של ישויות.
  • שימוש בשגרת נתונים למשימות במסד הנתונים ובמשימות ארוכות אחרות.

מה תלמדו

  • איך להשתמש בRecyclerView עם Adapter וViewHolder כדי להציג רשימה של פריטים.

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

  • כדי להשתמש בRecyclerView להצגת נתוני איכות השינה, עליך לשנות את אפליקציית TrackMySleepquality מהשיעור הקודם.

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

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

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

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

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

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

כדי לתמוך בכל תרחישי השימוש האלה, Android מספק את הווידג'ט RecyclerView.

היתרון הגדול ביותר של RecyclerView הוא שהוא יעיל מאוד ברשימות גדולות:

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

ברצף שבהמשך אפשר לראות שתצוגה אחת מלאה בנתונים, ABC. לאחר שהתצוגה גוללים למסך, האפליקציה RecyclerView משתמשת שוב בתצוגה של הנתונים החדשים, XYZ.

דפוס המתאם

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

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

הטמעה של RecyclerView

כדי להציג את הנתונים שלך בRecyclerView, צריך את החלקים הבאים:

  • נתונים להצגה.
  • מופע של RecyclerView המוגדר בקובץ הפריסה כך שיוכל לשמש כמאגר של התצוגות המפורטות.
  • פריסה של פריט נתונים אחד.
    אם כל הפריטים ברשימה נראים זהים, אפשר להשתמש באותה פריסה לכולם. עם זאת, זו לא חובה. את הפריסה של הפריטים צריך ליצור בנפרד מהפריסה של קטעי קוד וקטע אחד, כדי שאפשר יהיה ליצור תצוגת פריטים אחת בכל פעם ולמלא אותה בנתונים.
  • מנהל פריסה.
    מנהל הפריסה מטפל בארגון (הפריסה) של רכיבי ממשק המשתמש בתצוגה מפורטת.
  • בעל תצוגה.
    הבעלים של התצוגה מרחיב את הכיתה מסוג ViewHolder. הוא מכיל את פרטי התצוגה של פריט אחד מהפריסה של הפריט. בנוסף, בעלי התצוגה יכולים להוסיף מידע שמשמש את RecyclerView כדי להעביר ביעילות את הצפיות במסך.
  • מתאם.
    המתאם מחבר את הנתונים ל-RecyclerView. היא מתאימה את הנתונים כך שניתן יהיה להציג אותם ב-ViewHolder. RecyclerView משתמש במתאם כדי להבין איך להציג את הנתונים במסך.

במשימה הזו צריך להוסיף RecyclerView לקובץ הפריסה ולהגדיר Adapter כדי לחשוף את נתוני השינה למכשיר RecyclerView.

שלב 1: הוספת RecyclerView עם LayoutManager

בשלב זה, אתם מחליפים את ScrollView ב-RecyclerView בקובץ fragment_sleep_tracker.xml.

  1. מורידים את האפליקציה RecyclerViewFundamentals-Starter מ-GitHub.
  2. בונים ומפעילים את האפליקציה. חשוב לשים לב איך הנתונים מוצגים כטקסט פשוט.
  3. פותחים את קובץ הפריסה fragment_sleep_tracker.xml בכרטיסייה עיצוב ב-Android Studio.
  4. בחלונית עץ הרכיבים, מוחקים את ScrollView. פעולה זו גם מוחקת את TextView בתוך ScrollView.
  5. בחלונית לוח הצבעים, גוללים ברשימה של סוגי הרכיבים שמימין כדי למצוא את מאגרים, ולאחר מכן בוחרים אותם.
  6. גוררים RecyclerView מהחלונית לוח הצבעים אל החלונית עץ הרכיבים. יש למקם את RecyclerView בתוך ConstraintLayout.

  1. אם תיפתח תיבת דו-שיח עם שאלה אם רוצים להוסיף תלות, לוחצים על אישור כדי לאפשר ל-Android Studio להוסיף את התלות של recyclerview לקובץ Gradle שלכם. ייתכן שיחלפו כמה שניות עד שהאפליקציה תסונכרן.

  1. פותחים את קובץ המודול build.gradle, גוללים עד הסוף ומחפשים את התלות החדשה, שנראית כמו הקוד הבא:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. חזרה אל fragment_sleep_tracker.xml.
  2. בכרטיסייה Text, מחפשים את הקוד RecyclerView שמוצג למטה:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. RecyclerView ב-id מתוך sleep_list.
android:id="@+id/sleep_list"
  1. ממקמים את RecyclerView כך שיכסה את החלק הנותר של המסך בתוך ConstraintLayout. לשם כך, יש להגביל את החלק העליון של RecyclerView ללחצן התחלה, בחלק התחתון של הלחצן ניקוי וכל צד בהורה. יש להגדיר את הרוחב והגובה של הפריסה ל-0dp ב-Layout Editor או ב-XML באמצעות הקוד הבא:
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toTopOf="@+id/clear_button"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/stop_button"
  1. הוספת מנהל פריסה ל-XML של RecyclerView. לכל RecyclerView יש צורך במנהל פריסה שמסביר לו כיצד למקם פריטים ברשימה. Android מספק LinearLayoutManager, שמגדיר כברירת מחדל את הפריטים ברשימה אנכית של שורות ברוחב מלא.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. עוברים לכרטיסייה עיצוב ושימו לב שהמגבלות הנוספות גרמו לRecyclerView להתרחב ולמלא את השטח הזמין.

שלב 2: יוצרים את הפריסה של פריט הרשימה ואת הבעלים של תצוגת טקסט

ה-RecyclerView הוא רק מאגר. בשלב זה, יוצרים את הפריסה ואת התשתית עבור הפריטים שיוצגו בתוך RecyclerView.

כדי להגיע אל RecyclerView לעבוד מהר ככל האפשר, בהתחלה משתמשים בפריט רשימה פשוט שמציג את איכות השינה כמספר בלבד. לשם כך, צריך בעל תצוגה, TextItemViewHolder. נדרשת גם תצוגה מפורטת, TextView, לנתונים. (בשלב מאוחר יותר, תוכלו לקבל מידע נוסף על בעלי תצוגה ועל אופן הפריסה של כל נתוני השינה).

  1. יצירת קובץ פריסה בשם text_item_view.xml. לא משנה במה אתם משתמשים כרכיב הבסיס, כי אתם מחליפים את קוד התבנית.
  2. ב-text_item_view.xml, מוחקים את כל הקוד הנתון.
  3. צריך להוסיף TextView עם מרווח פנימי 16dp בהתחלה ובסיום, וגודל טקסט של 24sp. יש לוודא שהרוחב תואם להורה, והגובה עוטף את התוכן. מאחר שהתצוגה הזו מוצגת בתוך RecyclerView, אין צורך למקם אותה ב-ViewGroup.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:textSize="24sp"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"
    android:layout_width="match_parent"       
    android:layout_height="wrap_content" />
  1. פתיחת Util.kt יש לגלול לסוף ולהוסיף את ההגדרה המוצגת למטה, שיוצרת את הכיתה TextItemViewHolder. צריך להציב את הקוד בחלק התחתון של הקובץ, אחרי תו הסגירה האחרון. הקוד מופיע בUtil.kt כי בעל התצוגה המפורטת הזו הוא זמני, והחלפת אותו מאוחר יותר.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. אם מוצגת בקשה, מייבאים את android.widget.TextView ואת androidx.recyclerview.widget.RecyclerView.

שלב 3: יצירת SleepNightAdapter

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

  1. בחבילה של sleeptracker, יש ליצור כיתה חדשה מ-Kotlin שנקראת SleepNightAdapter.
  2. משנים את SleepNightAdapterהשיעור RecyclerView.Adapter. הכיתה נקראת SleepNightAdapter כי היא מתאימה את אובייקט SleepNight לרכיב ש-RecyclerView יכול להשתמש בו. המתאם צריך לדעת באיזה בעל תצוגה להשתמש, לכן כדאי לעבור ב-TextItemViewHolder. מייבאים את הרכיבים הנחוצים כשמוצגת בקשה לעשות זאת, ואז מופיעה הודעת שגיאה כי יש שיטות שחובה להטמיע.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. ברמה העליונה של SleepNightAdapter, יוצרים משתנה מסוג listOf SleepNight לשמירת הנתונים.
var data =  listOf<SleepNight>()
  1. בשיטה SleepNightAdapter, מבטלים את getItemCount() כדי להחזיר את הגודל של רשימת לילות השינה ב-data. הערך RecyclerView צריך לדעת כמה פריטים יש למתאם כדי להציג, והוא עושה זאת על ידי התקשרות ל-getItemCount().
override fun getItemCount() = data.size
  1. ב-SleepNightAdapter, מבטלים את הפונקציה onBindViewHolder(), כפי שמוצג למטה.

    פונקציית onBindViewHolder() נקראת על ידי RecyclerView כדי להציג את הנתונים של פריט פריט אחד במיקום שצוין. כלומר, לשיטה onBindViewHolder() יש שני ארגומנטים: בעל תצוגה ומיקום הנתונים לקישור. עבור האפליקציה הזו, הבעלים הוא TextItemViewHolder, והמיקום הוא המיקום ברשימה.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. בתוך onBindViewHolder(), יוצרים משתנה של פריט אחד במיקום נתון בנתונים.
 val item = data[position]
  1. לנכס ViewHolder שיצרת יש נכס בשם textView. בתוך onBindViewHolder(), מגדירים את ה-text של textView למספר באיכות השינה. הקוד הזה מציג רק רשימת מספרים, אבל הדוגמה הפשוטה הזו מאפשרת לכם לראות איך המתאם מעביר את הנתונים אל מסך התצוגה ואל המסך.
holder.textView.text = item.sleepQuality.toString()
  1. בSleepNightAdapter, צריך לבטל וליישם את onCreateViewHolder(). התכונה הזו נקראת כאשר RecyclerView צריך בעל תצוגה כדי לייצג פריט.

    הפונקציה הזו מקבלת שני פרמטרים ומחזירה ViewHolder. הפרמטר parent, שהוא קבוצת הצפיות שמחזיקה את בעל התצוגה, הוא תמיד RecyclerView. נעשה שימוש בפרמטר viewType כשיש כמה תצוגות מפורטות באותו RecyclerView. לדוגמה, אם תוסיפו רשימה של צפיות בטקסט, תמונה וסרטון באותו RecyclerView, הפונקציה onCreateViewHolder() תצטרך לדעת באיזה סוג תצוגה להשתמש.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. ב-onCreateViewHolder(), יוצרים מכונה של LayoutInflater.

    ניפוי הפריסה לפריסה יודעים איך ליצור תצוגות מפורטות מפריסות XML. ה-context מכיל מידע על האופן שבו ניתן לנפח את התצוגה באופן תקין. במתאם עבור תצוגת מיחזור, אתה תמיד מעביר בהקשר של קבוצת התצוגה parent, שהיא RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. באפליקציה onCreateViewHolder(), יש ליצור את view על ידי שליחת בקשה ל-layoutinflater לניפוח שלהם.

    העברת הפריסה של ה-XML לתצוגה המפורטת, וקבוצת parent של התצוגה המפורטת לתצוגה המפורטת. הארגומנט השלישי, הבוליאני הוא attachToRoot. הארגומנט הזה צריך להיות false, כי RecyclerView מוסיף עבורך את הפריט הזה להיררכיית התצוגה כשמגיע הזמן שלו.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. בעוד onCreateViewHolder(), יש להחזיר TextItemViewHolder שבוצעה עם view.
return TextItemViewHolder(view)
  1. המתאם צריך ליידע את RecyclerView כשהdata השתנה, כי ל-RecyclerView אין מידע לגבי הנתונים. צריך לדעת רק את בעלי התצוגה שהמתאם מספק לה.

    כדי להודיע ל-RecyclerView מתי הנתונים המוצגים, יש להוסיף רכיב משנה מותאם אישית למשתנה data בראש המחלקה SleepNightAdapter. בכלי ההגדרה, נותנים ל-data ערך חדש ואז מתקשרים אל notifyDataSetChanged() כדי להפעיל מחדש את שרטוט הרשימה עם הנתונים החדשים.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

שלב 4: מודיעים ל-Recycler על המתאם

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

  1. פתיחת SleepTrackerFragment.kt
  2. ב-onCreateview(), יוצרים מתאם. יש להזין את הקוד הזה לאחר יצירת מודל ViewModel, ולפני ההצהרה return.
val adapter = SleepNightAdapter()
  1. משייכים את adapter אל RecyclerView.
binding.sleepList.adapter = adapter
  1. יש לנקות את הפרויקט ולבנות אותו מחדש כדי לעדכן את האובייקט binding.

    אם עדיין מופיעות שגיאות בסביבות binding.sleepList או binding.FragmentSleepTrackerBinding, יש לבטל את השמירה של קובצי המטמון ולהפעיל אותם מחדש. (יש לבחור באפשרות קובץ > ביטול מטמון / הפעלה מחדש.)

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

שלב 5: שליפת נתונים למתאם

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

  1. פתיחת SleepTrackerViewModel
  2. מחפשים את המשתנה nights, שבו נשמרים כל לילות השינה. הנתונים האלה מוצגים. כדי להגדיר את המשתנה nights צריך לקרוא ל-getAllNights() במסד הנתונים.
  3. יש להסיר את private מ-nights, כי עליך ליצור צופה שצריך לגשת למשתנה הזה. ההצהרה אמורה להיראות כך:
val nights = database.getAllNights()
  1. בחבילה של database, פותחים את SleepDatabaseDao.
  2. מוצאים את הפונקציה getAllNights(). חשוב לשים לב שהפונקציה הזו מחזירה רשימה של ערכי SleepNight בתור LiveData. פירוש הדבר הוא שהמשתנה nights מכיל LiveData שעודכן על ידי Room, ואפשר לצפות ב-nights כדי לדעת מתי הוא משתנה.
  3. פתיחת SleepTrackerFragment
  4. ב-onCreateView(), מתחת ליצירת ה-adapter, יוצרים צופה במשתנה nights.

    אם תציינו את קטע הקוד של viewLifecycleOwner לציון הבעלים של מחזור החיים, תוכלו לוודא שהצופה הזה יהיה פעיל רק כאשר RecyclerView מוצג במסך.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. בתוך כל צופה, בכל פעם שמתקבל ערך שאינו Null (עבור nights), יש להקצות את הערך data של המתאם. זהו הקוד המלא לצופה והגדרת הנתונים:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. בונים את הקוד ומפעילים אותו.

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

שלב 6: בדיקת אופן השימוש החוזר של בעלי הצפיות

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

מאחר שבעלי התצוגה האלה ממוחזרים, יש לוודא ש-onBindViewHolder() מגדיר או מאפס התאמות אישיות שפריטים קודמים הגדירו בבעלים.

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

  1. בכיתה SleepNightAdapter, מוסיפים את הקוד הבא בסוף onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. מריצים את האפליקציה.
  2. יש להוסיף חלק מנתוני איכות השינה הנמוכים, והמספר אדום.
  3. כדאי להוסיף דירוגים גבוהים לאיכות שינה עד שיופיע מספר גבוה בצבע אדום על המסך.

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

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

    בשני התנאים הבוטים, כל אחד מהפריטים ישתמש בצבע הטקסט הנכון בכל פריט.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. מפעילים את האפליקציה והמספרים צריכים להיות תמיד בצבע הנכון.

מעולה! עכשיו יש לך RecyclerView עם פונקציונליות מלאה.

במשימה הזו מחליפים את בעל התצוגה הפשוטה, שיש לו נתונים נוספים בלילה של שינה.

הViewHolder הפשוט שהוספת אל Util.kt מכסה רק TextView בTextItemViewHolder.

class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

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

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

שלב 1: יוצרים את פריסת הפריט

בשלב זה, יוצרים את קובץ הפריסה של פריט אחד. הפריסה כוללת ConstraintLayout עם ImageView לאיכות השינה, TextView למשך השינה ו-TextView לאיכות של הטקסט. מכיוון שכבר ביצעת פריסות, יש להעתיק ולהדביק את קוד ה-XML שסופק.

  1. יצירת קובץ משאב חדש לפריסה והענקת לו שם list_item_sleep_night.
  2. יש להחליף את כל הקוד שבקובץ בקוד שבהמשך. לאחר מכן תצטרכו להכיר את הפריסה שיצרתם.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content">

   <ImageView
       android:id="@+id/quality_image"
       android:layout_width="@dimen/icon_size"
       android:layout_height="60dp"
       android:layout_marginStart="16dp"
       android:layout_marginTop="8dp"
       android:layout_marginBottom="8dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       tools:srcCompat="@drawable/ic_sleep_5" />

   <TextView
       android:id="@+id/sleep_length"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginStart="8dp"
       android:layout_marginTop="8dp"
       android:layout_marginEnd="16dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toEndOf="@+id/quality_image"
       app:layout_constraintTop_toTopOf="@+id/quality_image"
       tools:text="Wednesday" />

   <TextView
       android:id="@+id/quality_string"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginTop="8dp"
       app:layout_constraintEnd_toEndOf="@+id/sleep_length"
       app:layout_constraintHorizontal_bias="0.0"
       app:layout_constraintStart_toStartOf="@+id/sleep_length"
       app:layout_constraintTop_toBottomOf="@+id/sleep_length"
       tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
  1. עוברים לכרטיסייה עיצוב ב-Android Studio. בתצוגת העיצוב, הפריסה נראית כמו צילום המסך שמימין. בתצוגת השרטוט, היא נראית כמו צילום המסך שמשמאל.

שלב 2: יצירת HoldViewer

  1. פתיחת SleepNightAdapter.kt
  2. יש ליצור כיתה במסגרת SleepNightAdapter שנקראת ViewHolder ולהאריך אותה ב-RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. בתוך ViewHolder, קבלת הפניות לצפיות. נדרשת הפניה לתצוגות המפורטות שViewHolder יעדכן. בכל פעם שתקשרו את ה-ViewHolder הזה, עליך לגשת לתמונה ולתצוגות הטקסט. (יש להמיר את הקוד הזה כדי להשתמש בשיוך נתונים מאוחר יותר).
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)

שלב 3: שימוש ב-ViewHolder ב-SleepNightAdapter

  1. בהגדרה SleepNightAdapter, במקום ב-TextItemViewHolder, יש להשתמש ב-SleepNightAdapter.ViewHolder שיצרת.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

עדכון onCreateViewHolder():

  1. יש לשנות את החתימה של onCreateViewHolder() כדי להחזיר את ViewHolder.
  2. יש לשנות את תכונות הניפוי של הפריסה כדי להשתמש במשאב הפריסה הנכון, list_item_sleep_night.
  3. יש להסיר את ההעברה אל TextView.
  4. במקום להחזיר TextItemViewHolder, יש להחזיר ViewHolder.

    הנה הפונקציה onCreateViewHolder() שהסתיימה:
    override fun onCreateViewHolder(
            parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = 
            LayoutInflater.from(parent.context)
        val view = layoutInflater
                .inflate(R.layout.list_item_sleep_night, 
                         parent, false)
        return ViewHolder(view)
    }

עדכון onBindViewHolder():

  1. יש לשנות את החתימה של onBindViewHolder() כך שהפרמטר holder יהיה ViewHolder במקום TextItemViewHolder.
  2. בתוך onBindViewHolder(), יש למחוק את כל הקוד, מלבד ההגדרה של item.
  3. יש להגדיר val res שכולל הפניה אל resources לתצוגה המפורטת הזו.
val res = holder.itemView.context.resources
  1. מגדירים את הטקסט של תצוגת הטקסט sleepLength בהתאם למשך הזמן. מעתיקים את הקוד שבהמשך, שמפעיל פונקציית פורמט שכלולה בקוד למתחילים.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. זוהי שגיאה כי יש להגדיר את convertDurationToFormatted(). יש לפתוח את Util.kt ולבטל את הוספת הקוד והייבוא המשויך אליו. (בוחרים קוד > הערה עם הערות שורה.)
  2. בחזרה בעוד onBindViewHolder(), יש להשתמש ב-convertNumericQualityToString() כדי להגדיר את האיכות.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. ייתכן שתצטרכו לייבא את הפונקציות האלה באופן ידני.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. יש להגדיר את הסמל הנכון לאיכות. הסמל החדש ic_sleep_active נמסר לך בקוד למתחילים.
holder.qualityImage.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
})
  1. הנה הפונקציה onBindViewHolder() המעודכנת המעודכנת, שמגדירה את כל הנתונים עבור ViewHolder:
   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = data[position]
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.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
        })
    }
  1. מריצים את האפליקציה. המסך צריך להיראות כמו צילום המסך שבהמשך, ומציג את הסמל של איכות השינה, יחד עם טקסט למשך השינה ואיכות השינה.

ה-RecyclerView שלך הושלם! למדת איך להטמיע Adapter וViewHolder, והרכיבתם אותם כדי להציג רשימה עם RecyclerView Adapter.

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

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

שלב 1: העברה מחדש של onBindViewHolder()

בשלב זה, מוסיפים מחדש את הקוד ומעבירים את כל הפונקציונליות של בעלי התצוגה המפורטת ל-ViewHolder. מטרת החישוב מחדש היא לא לשנות את מראה האפליקציה למשתמש, אלא להקל על מפתחים לעבוד על הקוד. למזלנו, ל-Android Studio יש כלים שיעזרו לכם.

  1. ב-SleepNightAdapter, ב-onBindViewHolder() צריך לבחור הכול מלבד ההצהרה כדי להצהיר על המשתנה item.
  2. לוחצים לחיצה ימנית ובוחרים באפשרות refactor > חילוץ > Function.
  3. נותנים שם לפונקציה bind ומאשרים את הפרמטרים המוצעים. לוחצים על אישור.

    הפונקציה bind() ממוקמת מתחת ל-onBindViewHolder().
    private fun bind(holder: ViewHolder, item: SleepNight) {
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.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
        })
    }
  1. העברת הסמן על המילה holder של הפרמטר holder ב-bind(). יש ללחוץ על Alt+Enter (Option+Enter ב-Mac) כדי לפתוח את תפריט הכוונה. בוחרים באפשרות ממיר פרמטר לנמען כדי להמיר אותו לפונקציית תוסף עם החתימה הבאה:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. יש לחתוך ולהדביק את הפונקציה bind() ב-ViewHolder.
  2. הפיכת bind() לגלוי לכול.
  3. אם יש צורך, מייבאים את bind() למתאם.
  4. מכיוון שעכשיו היא נמצאת בViewHolder, יש לך אפשרות להסיר את החלק של ViewHolder מהחתימה. זהו הקוד הסופי של הפונקציה bind() במחלקה ViewHolder.
fun bind(item: SleepNight) {
   val res = itemView.context.resources
   sleepLength.text = convertDurationToFormatted(
           item.startTimeMilli, item.endTimeMilli, res)
   quality.text = convertNumericQualityToString(
           item.sleepQuality, res)
   qualityImage.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: גורם מחדש ב-CreateViewHolder

השיטה onCreateViewHolder() במתאם מנפחת כרגע את התצוגה ממשאב הפריסה של ViewHolder. עם זאת, האינפלציה לא קשורה למתאם ולכל מה שקשור לViewHolder. הניפוח אמור להתבצע ב-ViewHolder.

  1. ב-onCreateViewHolder(), בוחרים את כל הקוד בגוף הפונקציה.
  2. לוחצים לחיצה ימנית ובוחרים באפשרות refactor > חילוץ > Function.
  3. נותנים שם לפונקציה from ומאשרים את הפרמטרים המוצעים. לוחצים על אישור.
  4. מציבים את הסמן על שם הפונקציה from. יש ללחוץ על Alt+Enter (Option+Enter ב-Mac) כדי לפתוח את תפריט הכוונה.
  5. בוחרים העברה לאובייקט נלווה. הפונקציה from() צריכה להיות באובייקט נלווה כדי שניתן יהיה לקרוא אותה ברמה ViewHolder, ולא במופע ב-ViewHolder.
  6. העברת האובייקט companion לכיתה ViewHolder.
  7. הפיכת from() לגלוי לכול.
  8. ב-onCreateViewHolder(), אפשר לשנות את ההצהרה return כדי להחזיר את התוצאה של קריאה ל-from() בכיתת ViewHolder.

    השיטות המלאות onCreateViewHolder() ו-from() צריכות להיראות כמו הקוד הבא, והקוד שלך צריך לבנות ולפעול ללא שגיאות.
    override fun onCreateViewHolder(parent: ViewGroup, viewType: 
Int): ViewHolder {
        return ViewHolder.from(parent)
    }
companion object {
   fun from(parent: ViewGroup): ViewHolder {
       val layoutInflater = LayoutInflater.from(parent.context)
       val view = layoutInflater
               .inflate(R.layout.list_item_sleep_night, parent, false)
       return ViewHolder(view)
   }
}
  1. יש לשנות את החתימה של הכיתה ViewHolder כדי שהבונה יהיה פרטי. כי ל-from() יש עכשיו שיטה שמחזירה מופע חדש של ViewHolder, לכן אין סיבה להתקשר יותר ל-constructor של ViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. מריצים את האפליקציה. האפליקציה אמורה לבנות ולהפעיל את אותה פעולה כמו קודם לכן – התוצאה הרצויה אחרי החישוב מחדש.

פרויקט ב-Android Studio: RecyclerViewFundamentals

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

כדי להציג את הנתונים שלך בRecyclerView, צריך את החלקים הבאים:

  • RecyclerView
    כדי ליצור מופע של RecyclerView, צריך להגדיר רכיב <RecyclerView> בקובץ הפריסה.
  • Layout Manager
    RecyclerView משתמש ב-LayoutManager כדי לארגן את פריסת הפריטים ב-RecyclerView, כמו למשל פריסת הרשת או רשימה לינארית.

    ב-<RecyclerView> בקובץ הפריסה, מגדירים את המאפיין app:layoutManager כמנהל הפריסה (למשל, LinearLayoutManager או GridLayoutManager).

    אפשר להגדיר את LayoutManager גם עבור RecyclerView באופן פרוגרמטי. (שיטה זו תקפה במעבדה מאוחרת יותר של קוד Lab.)
  • פריסה לכל פריט
    אפשר ליצור פריסה הכוללת פריט נתונים אחד בקובץ XML.
  • מתאם
    יש ליצור מתאם שמכין את הנתונים ואת האופן שבו הם יוצגו בViewHolder. משייכים את המתאם אל RecyclerView.

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

    המתאם דורש ממך ליישם את השיטות הבאות:
    getItemCount() כדי להחזיר את מספר הפריטים.
    onCreateViewHolder() כדי להחזיר את ViewHolder עבור פריט ברשימה.
    onBindViewHolder() כדי להתאים את הנתונים לצפיות של פריט ברשימה.

  • ViewHolder
    פריט ViewHolder כולל את פרטי התצוגה של פריט אחד מהפריסה של הפריט.
  • השיטה onBindViewHolder() במתאם מתאימה את הנתונים לתצוגות המפורטות. תמיד אפשר לבטל את השיטה הזו. בדרך כלל, onBindViewHolder() מנפח את הפריסה של פריט, ומעביר את הנתונים לתצוגות בפריסה.
  • לRecyclerView אין מידע לגבי הנתונים, ולכן Adapter צריך להודיע לRecyclerView על שינוי הנתונים. יש להשתמש ב-notifyDataSetChanged() כדי להודיע ל-Adapter שהנתונים השתנו.

קורס אוניברסיטה:

התיעוד של מפתח Android:

בקטע הזה מפורטות מטלות שיעורי בית אפשריות לתלמידים שעובדים עם קוד Lab הזה, במסגרת קורס בהדרכת מורה. למורה יש אפשרות לבצע את הפעולות הבאות:

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

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

אם אתם עובדים בעצמכם על שיעור הקוד הזה, אתם מוזמנים להשתמש במטלות שיעורי הבית האלה כדי לבחון את הידע שלכם.

מענה על השאלות האלה

שאלה 1

איך RecyclerView מציג פריטים? יש לבחור בכל האפשרויות הרלוונטיות.

▬ הצגת פריטים ברשימה או ברשת.

הוספה של גלילה אנכית או אופקית.

▬ גלילה באלכסון במכשירים גדולים יותר, כמו טאבלטים.

עלות אפשר להשתמש בפריסות בהתאמה אישית במקרים שבהם רשימה או רשת לא מספיקים לשימוש.

שאלה 2

מהם היתרונות של השימוש ב-RecyclerView? יש לבחור בכל האפשרויות הרלוונטיות.

הוספה ביעילות מוצגות רשימות גדולות.

▢ עדכון הנתונים באופן אוטומטי.

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

▬ שימוש חוזר בתצוגה שגוללת במסך כדי להציג את הפריט הבא שגולל במסך.

שאלה 3

מהן כמה מהסיבות לשימוש במתאמים? יש לבחור בכל האפשרויות הרלוונטיות.

▬ הפרדת חששות מאפשרת לשנות ולבדוק בקלות את הקוד.

הוספה RecyclerView היא נתון בלתי מוגבל לנתונים המוצגים.

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

▬ האפליקציה תפעל מהר יותר.

שאלה 4

איזו מהאפשרויות הבאות נכונה לגבי ViewHolder? יש לבחור בכל האפשרויות הרלוונטיות.

▦ הפריסה של ViewHolder מוגדרת בקובצי פריסה של XML.

▬ יש ViewHolder לכל יחידת נתונים במערך הנתונים.

הוספה יכולה להיות לך יותר ממדינה אחת (ViewHolder) בRecyclerView.

Adapter מקשרים נתונים אל ViewHolder.

מעבר לשיעור הבא: 7.2: DiffUטיל ושיוך נתונים באמצעות RecyclerView