ה-codelab הזה הוא חלק מהקורס Android Kotlin Fundamentals. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא יסודות Kotlin ל-Android.
מבוא
בשיעור הזה תלמדו איך להשתמש ב-RecyclerView כדי להציג רשימות של פריטים. בהמשך לסדרת ה-codelab הקודמת בנושא אפליקציה למעקב אחרי שינה, נלמד דרך טובה ורב-תכליתית יותר להצגת נתונים באמצעות RecyclerView עם ארכיטקטורה מומלצת.
מה שכדאי לדעת
חשוב שתכירו את:
- בניית ממשק משתמש (UI) בסיסי באמצעות פעילות, רכיבים ותצוגות.
- ניווט בין פרגמנטים ושימוש ב-
safeArgsכדי להעביר נתונים בין פרגמנטים. - שימוש במודלים של תצוגה, במפעלים של מודלים של תצוגה, בטרנספורמציות וב-
LiveDataובאובייקטים שלהם. - יצירת מסד נתונים
Room, יצירת DAO והגדרת ישויות. - שימוש ב-coroutines למשימות שקשורות למסד נתונים ולמשימות אחרות לטווח ארוך.
מה תלמדו
- איך משתמשים ב-
RecyclerViewעםAdapterו-ViewHolderכדי להציג רשימת פריטים.
הפעולות שתבצעו:
- משנים את האפליקציה TrackMySleepQuality מהשיעור הקודם כך שתשתמש ב-
RecyclerViewכדי להציג נתונים של איכות השינה.
ב-codelab הזה תבנו את החלק RecyclerView באפליקציה שעוקבת אחרי איכות השינה. האפליקציה משתמשת במסד נתונים של Room כדי לאחסן את נתוני השינה לאורך זמן.
אפליקציית מעקב השינה למתחילים כוללת שני מסכים שמיוצגים על ידי רכיבי Fragment, כמו שמוצג באיור שלמטה.

במסך הראשון, שמוצג בצד ימין, יש לחצנים להתחלת המעקב ולהפסקתו. במסך הזה מוצגים גם כל נתוני השינה של המשתמש. הלחצן ניקוי מוחק באופן סופי את כל הנתונים שהאפליקציה אספה על המשתמש. במסך השני, שמוצג בצד שמאל, בוחרים את דירוג איכות השינה.
האפליקציה הזו משתמשת בארכיטקטורה פשוטה עם בקר ממשק משתמש, 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.
- מורידים את האפליקציה RecyclerViewFundamentals-Starter מ-GitHub.
- מבצעים Build ומריצים את האפליקציה. שימו לב איך הנתונים מוצגים כטקסט פשוט.
- פותחים את קובץ הפריסה
fragment_sleep_tracker.xmlבכרטיסייה Design ב-Android Studio. - בחלונית Component Tree, מוחקים את
ScrollView. הפעולה הזו תמחק גם אתTextViewשנמצא בתוךScrollView. - בחלונית Palette, גוללים ברשימת סוגי הרכיבים בצד ימין עד שמגיעים אל Containers ובוחרים אותה.
- גוררים
RecyclerViewמהחלונית Palette לחלונית Component Tree. ממקמים אתRecyclerViewבתוךConstraintLayout.

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

- פותחים את קובץ המודול
build.gradle, גוללים לסוף ורושמים את התלות החדשה, שדומה לקוד שבהמשך:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
- מעבר חזרה אל
fragment_sleep_tracker.xml. - בכרטיסייה Text (טקסט), מחפשים את הקוד
RecyclerViewשמופיע בהמשך:
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />- אני רוצה לתת ל
RecyclerViewidשלsleep_list.
android:id="@+id/sleep_list"- ממקמים את
RecyclerViewכך שימלא את החלק הנותר של המסך בתוךConstraintLayout. כדי לעשות את זה, מגבילים את החלק העליון שלRecyclerViewללחצן התחלה, את החלק התחתון ללחצן ניקוי, וכל צד לאלמנט האב. מגדירים את הרוחב והגובה של הפריסה ל-0dp בכלי לעריכת פריסות או ב-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"- מוסיפים מנהל פריסה ל-XML של
RecyclerView. לכלRecyclerViewצריך להיות מנהל פריסה שמגדיר איך למקם פריטים ברשימה. מערכת Android מספקתLinearLayoutManager, שמוצגים בה כברירת מחדל הפריטים ברשימה אנכית של שורות ברוחב מלא.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"- עוברים לכרטיסייה עיצוב ורואים שהאילוצים שנוספו גרמו להרחבת
RecyclerViewכדי למלא את השטח הזמין.

שלב 2: יוצרים את פריסת הפריט ברשימה ואת מחזיק תצוגת הטקסט
התג RecyclerView הוא רק מאגר. בשלב הזה יוצרים את הפריסה והתשתית של הפריטים שיוצגו בתוך RecyclerView.
כדי להגיע ל-RecyclerView עובד כמה שיותר מהר, בשלב הראשון משתמשים בפריט רשימה פשוט שמציג רק את איכות השינה כמספר. לשם כך, צריך מחזיק תצוגה, TextItemViewHolder. צריך גם תצוגה מפורטת, TextView, של הנתונים. (בשלב מאוחר יותר, תקבלו מידע נוסף על מחזיקי תצוגה ועל אופן הצגת כל נתוני השינה).
- יוצרים קובץ פריסה בשם
text_item_view.xml. לא משנה מה משמש כרכיב הבסיסי, כי אתם תחליפו את קוד התבנית. - ב-
text_item_view.xml, מוחקים את כל הקוד שמופיע. - מוסיפים
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" />- פתיחת
Util.kt. גוללים לסוף ומוסיפים את ההגדרה שמופיעה בהמשך, שיוצרת את המחלקהTextItemViewHolder. מציבים את הקוד בתחתית הקובץ, אחרי הסוגר המסולסל האחרון. הקוד מוכנס ל-Util.ktכי מחזיק התצוגה הזה הוא זמני, ואתם מחליפים אותו בהמשך.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)- אם תתבקשו, מייבאים את
android.widget.TextViewואתandroidx.recyclerview.widget.RecyclerView.
שלב 3: יצירת SleepNightAdapter
המשימה העיקרית בהטמעה של RecyclerView היא יצירת המתאם. יש לכם מחזיק תצוגה פשוט לתצוגת הפריט, ופריסה לכל פריט. עכשיו אפשר ליצור מתאם. המתאם יוצר מחזיק תצוגה וממלא אותו בנתונים כדי להציג את RecyclerView.
- ב
sleeptrackerpackage, יוצרים מחלקה חדשה של Kotlin בשםSleepNightAdapter. - מפעילים את האפשרות
SleepNightAdapterהארכת השיעורRecyclerView.Adapter. המחלקות נקראותSleepNightAdapterכי הן מתאימות אובייקטSleepNightלמשהו שאפשר להשתמש בו ב-RecyclerView. המתאם צריך לדעת באיזה מחזיק תצוגה להשתמש, ולכן מעבירים אתTextItemViewHolder. מייבאים את הרכיבים הנדרשים כשמוצגת בקשה, ואז מוצגת שגיאה כי יש שיטות חובה שצריך להטמיע.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}- ברמה העליונה של
SleepNightAdapter, יוצרים משתנהlistOfSleepNightכדי לאחסן את הנתונים.
var data = listOf<SleepNight>()- ב-
SleepNightAdapter, מחליפים אתgetItemCount()כדי להחזיר את הגודל של רשימת הלילות של השינה ב-data. ה-RecyclerViewצריך לדעת כמה פריטים יש למתאם כדי להציג אותם, והוא עושה זאת על ידי קריאה ל-getItemCount().
override fun getItemCount() = data.size- ב-
SleepNightAdapter, מחליפים את הפונקציהonBindViewHolder(), כמו שמוצג בהמשך.
הפונקציהonBindViewHolder()מופעלת על ידיRecyclerViewכדי להציג את הנתונים של פריט רשימה אחד במיקום שצוין. לכן, השיטהonBindViewHolder()מקבלת שני ארגומנטים: מחזיק תצוגה ומיקום הנתונים לקשירה. באפליקציה הזו, המחזיק הואTextItemViewHolderוהמיקום הוא המיקום ברשימה.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}- בתוך
onBindViewHolder(), יוצרים משתנה לפריט אחד במיקום נתון בנתונים.
val item = data[position]- ל-
ViewHolderשיצרתם יש מאפיין שנקראtextView. בתוךonBindViewHolder(), מגדירים אתtextשלtextViewלמספר איכות השינה. הקוד הזה מציג רק רשימה של מספרים, אבל הדוגמה הפשוטה הזו מאפשרת לראות איך המתאם מעביר את הנתונים למחזיק התצוגה ולמסך.
holder.textView.text = item.sleepQuality.toString()- ב-
SleepNightAdapter, מחליפים אתonCreateViewHolder()ומטמיעים אותו. הפונקציה הזו מופעלת כש-RecyclerViewצריך מחזיק תצוגה כדי לייצג פריט.
הפונקציה הזו מקבלת שני פרמטרים ומחזירהViewHolder. הפרמטרparent, שהוא קבוצת התצוגה שמכילה את מחזיק התצוגה, הוא תמידRecyclerView. הפרמטרviewTypeמשמש כשיש כמה תצוגות באותוRecyclerView. לדוגמה, אם מציבים רשימה של תצוגות טקסט, תמונה וסרטון באותוRecyclerView, הפונקציהonCreateViewHolder()צריכה לדעת באיזה סוג תצוגה להשתמש.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}- ב-
onCreateViewHolder(), יוצרים מופע שלLayoutInflater.
הכלי layout inflater יודע איך ליצור תצוגות מפריסות XML. השדהcontextמכיל מידע על האופן שבו צריך להגדיל את התצוגה. במתאם של תצוגת recycler, תמיד מעבירים את ההקשר של קבוצת התצוגהparent, שהיאRecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)- ב-
onCreateViewHolder(), יוצרים אתviewעל ידי בקשה מ-layoutinflaterלהגדיל אותו.
מעבירים את פריסת ה-XML לתצוגה ואת קבוצת התצוגהparentלתצוגה. הארגומנט השלישי, בוליאני, הואattachToRoot. הארגומנט הזה צריך להיותfalse, כיRecyclerViewמוסיף את הפריט הזה להיררכיית התצוגה כשמגיע הזמן.
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView- ב
onCreateViewHolder(), החזרתTextItemViewHolderשבוצעה באמצעותview.
return TextItemViewHolder(view)- המתאם צריך לעדכן את
RecyclerViewכשdataמשתנה, כיRecyclerViewלא יודע כלום על הנתונים. הוא יודע רק על מחזיקי התצוגה שהמתאם מעביר אליו.
כדי להודיע ל-RecyclerViewמתי הנתונים שהוא מציג השתנו, מוסיפים פונקציית setter מותאמת אישית למשתנהdataשנמצא בחלק העליון של המחלקהSleepNightAdapter. בפונקציית ההגדרה, נותנים למאפייןdataערך חדש, ואז קוראים לפונקציהnotifyDataSetChanged()כדי להפעיל את הציור מחדש של הרשימה עם הנתונים החדשים.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}שלב 4: מעדכנים את RecyclerView לגבי ה-Adapter
ה-RecyclerView צריך לדעת על המתאם שבו הוא משתמש כדי לקבל מחזיקי תצוגה.
- פתיחת
SleepTrackerFragment.kt. - ב-
onCreateview(), יוצרים מתאם. ממקמים את הקוד הזה אחרי יצירת המודלViewModelולפני ההצהרהreturn.
val adapter = SleepNightAdapter()- משייכים את
adapterאלRecyclerView.
binding.sleepList.adapter = adapter- מנקים את הפרויקט ובונים אותו מחדש כדי לעדכן את האובייקט
binding.
אם עדיין מופיעות שגיאות שקשורות ל-binding.sleepListאו ל-binding.FragmentSleepTrackerBinding, צריך לבטל את התוקף של מטמונים ולהפעיל מחדש. (בוחרים באפשרות File > Invalidate Caches / Restart).
אם מריצים את האפליקציה עכשיו, לא יופיעו שגיאות, אבל לא יוצגו נתונים כשתקישו על Start ואז על Stop.
שלב 5: העברת נתונים למתאם
עד עכשיו יש לכם מתאם ודרך להעביר נתונים מהמתאם אל RecyclerView. עכשיו צריך להעביר נתונים מהמערכת ViewModel למתאם.
- פתיחת
SleepTrackerViewModel. - מחפשים את המשתנה
nightsשבו מאוחסנים כל נתוני הלילות של השינה, שהם הנתונים שיוצגו. המשתנהnightsמוגדר על ידי קריאה שלgetAllNights()במסד הנתונים. - מסירים את
privateמ-nights, כי תיצור משתנה מסוג observer שצריך לגשת למשתנה הזה. ההצהרה שלכם צריכה להיראות כך:
val nights = database.getAllNights()- בחבילה
database, פותחים אתSleepDatabaseDao. - מחפשים את הפונקציה
getAllNights(). שימו לב שהפונקציה הזו מחזירה רשימה של ערכיSleepNightבתורLiveData. המשמעות היא שהמשתנהnightsמכיל את הערךLiveDataשמתעדכן על ידיRoom, ואפשר לעקוב אחריnightsכדי לדעת מתי הוא משתנה. - פתיחת
SleepTrackerFragment. - ב-
onCreateView(), מתחת ליצירה שלadapter, יוצרים observer במשתנהnights.
אם מספקים אתviewLifecycleOwnerשל הפראגמנט כבעלים של מחזור החיים, אפשר לוודא שהאובייקט הזה של Observer פעיל רק כש-RecyclerViewמוצג במסך.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})- בתוך האובייקט observer, בכל פעם שמתקבל ערך שאינו null (עבור
nights), צריך להקצות את הערך ל-dataשל המתאם. זהו הקוד המלא של האובייקט observer והגדרת הנתונים:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})- כותבים ומריצים את הקוד.
אם המתאם פועל, תראו את המספרים של איכות השינה ברשימה. בצילום המסך שמימין, אחרי שמקישים על התחלה, מופיע הערך -1. בצילום המסך משמאל אפשר לראות את המספר המעודכן של איכות השינה אחרי שמקישים על הפסקה ובוחרים דירוג איכות.

שלב 6: בודקים איך מחזרים את מחזיקי התצוגה
RecyclerView recycles view holders, which means that it reuses them. כשמגללים תצוגה אל מחוץ למסך, RecyclerView משתמש מחדש בתצוגה עבור התצוגה שעומדת להיכנס למסך.
מחזיקי התצוגה האלה ממוחזרים, ולכן חשוב לוודא ש-onBindViewHolder() מגדיר או מאפס את ההתאמות האישיות שפריטים קודמים אולי הגדירו במחזיק התצוגה.
לדוגמה, אפשר להגדיר את צבע הטקסט לאדום ב-view holders שמכילים דירוגי איכות שקטנים מ-1 או שווים ל-1 ומייצגים שינה לא טובה.
- בכיתה
SleepNightAdapter, מוסיפים את הקוד הבא בסוףonBindViewHolder().
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}- מפעילים את האפליקציה.
- אם מוסיפים נתונים של שינה באיכות נמוכה, המספר יהיה אדום.
- מוסיפים דירוגים גבוהים לאיכות השינה עד שמופיע על המסך מספר אדום גבוה.
מכיוון ש-RecyclerViewמשתמש מחדש בבעלי תצוגה, בסופו של דבר הוא ישתמש מחדש באחד מבעלי התצוגה האדומים לדירוג איכות גבוה. הדירוג הגבוה מוצג בטעות באדום.

- כדי לפתור את הבעיה, מוסיפים הצהרת
elseלהגדרת הצבע לשחור אם האיכות לא קטנה או שווה לאחד.
כששני התנאים מוגדרים באופן מפורש, מחזיק התצוגה ישתמש בצבע הטקסט הנכון לכל פריט.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
} else {
// reset
holder.textView.setTextColor(Color.BLACK) // black
}- מריצים את האפליקציה, והמספרים צריכים להיות בצבע הנכון.
מעולה! עכשיו יש לכם 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 שסופק.
- יוצרים קובץ חדש של משאב פריסה ונותנים לו את השם
list_item_sleep_night. - מחליפים את כל הקוד בקובץ בקוד שלמטה. לאחר מכן, כדאי להכיר את הפריסה שיצרתם.
<?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>- עוברים לכרטיסייה Design (עיצוב) ב-Android Studio. בתצוגת העיצוב, הפריסה נראית כמו צילום המסך שמופיע בצד ימין למטה. בתצוגת התוכנית, הוא נראה כמו צילום המסך בצד שמאל.

שלב 2: יצירת ViewHolder
- פתיחת
SleepNightAdapter.kt. - יוצרים כיתה בתוך
SleepNightAdapterשנקראתViewHolderוגורמים לה להרחיב אתRecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}- בתוך
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
- בהגדרה של
SleepNightAdapter, במקוםTextItemViewHolder, משתמשים ב-SleepNightAdapter.ViewHolderשיצרתם עכשיו.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {עדכון onCreateViewHolder():
- משנים את החתימה של
onCreateViewHolder()כדי להחזיר אתViewHolder. - משנים את ה-layout inflator כך שישתמש במשאב הפריסה הנכון,
list_item_sleep_night. - מסירים את ההעברה אל
TextView. - במקום להחזיר
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():
- משנים את החתימה של
onBindViewHolder()כך שהפרמטרholderיהיהViewHolderבמקוםTextItemViewHolder. - בתוך
onBindViewHolder(), מוחקים את כל הקוד, מלבד ההגדרה שלitem. - מגדירים
valresשמכיל הפניה אלresourcesשל התצוגה הזו.
val res = holder.itemView.context.resources- מגדירים את הטקסט של תצוגת הטקסט
sleepLengthלמשך הזמן. מעתיקים את הקוד שבהמשך, שקורא לפונקציית עיצוב שמסופקת עם קוד ההתחלה.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)- הפעולה הזו מחזירה שגיאה, כי צריך להגדיר את
convertDurationToFormatted(). פותחים אתUtil.ktומבטלים את ההערה של הקוד והייבוא שמשויך אליו. (בוחרים באפשרות קוד > הוספת הערות בשורות). - חוזרים אל
onBindViewHolder()ומשתמשים ב-convertNumericQualityToString()כדי להגדיר את האיכות.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)- יכול להיות שתצטרכו לייבא את הפונקציות האלה באופן ידני.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString- הגדרת הסמל הנכון לאיכות. סמל
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
})- הנה הפונקציה המעודכנת
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
})
}- מריצים את האפליקציה. המסך אמור להיראות כמו בצילום המסך שלמטה, עם סמל איכות השינה, וגם טקסט של משך השינה ואיכות השינה.

השלמתם את RecyclerView! למדתם איך להטמיע Adapter ו-ViewHolder, ואיך לשלב ביניהם כדי להציג רשימה עם RecyclerView Adapter.
הקוד שלכם עד עכשיו מציג את התהליך של יצירת מתאם ומחזיק תצוגה. עם זאת, אפשר לשפר את הקוד הזה. הקוד להצגת מחזיקי התצוגה והקוד לניהול שלהם מעורבבים, ו-onBindViewHolder() יודע פרטים על אופן העדכון של ViewHolder.
באפליקציה שנמצאת בייצור, יכול להיות שיהיו כמה מחזיקי תצוגות, מתאמים מורכבים יותר וכמה מפתחים שמבצעים שינויים. כדאי לבנות את הקוד כך שכל מה שקשור ל-view holder יהיה רק ב-view holder.
שלב 1: שינוי מבנה הקוד של onBindViewHolder()
בשלב הזה, משנים את מבנה הקוד ומעבירים את כל הפונקציונליות של מחזיק התצוגה אל ViewHolder. המטרה של שינוי המבנה היא לא לשנות את המראה של האפליקציה למשתמש, אלא להקל על המפתחים לעבוד על הקוד ולשפר את האבטחה. למזלנו, ב-Android Studio יש כלים שיכולים לעזור.
- ב-
SleepNightAdapter, ב-onBindViewHolder(), בוחרים הכול חוץ מההצהרה כדי להצהיר על המשתנהitem. - לוחצים לחיצה ימנית ובוחרים באפשרות Refactor > Extract > Function (שינוי מבנה > חילוץ > פונקציה).
- נותנים לפונקציה את השם
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
})
}- מציבים את הסמן על המילה
holderשל הפרמטרholderשלbind(). מקישים עלAlt+Enter(או עלOption+Enterב-Mac) כדי לפתוח את תפריט הכוונות. בוחרים באפשרות המרת הפרמטר למקבל כדי להמיר אותו לפונקציית הרחבה עם החתימה הבאה:
private fun ViewHolder.bind(item: SleepNight) {...}- גוזרים את הפונקציה
bind()ומדביקים אותה ב-ViewHolder. - הופכים את
bind()לגלוי לכולם. - אם צריך, מייבאים את
bind()למתאם. - עכשיו שהיא נמצאת ב-
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: שינוי המבנה של onCreateViewHolder
השיטה onCreateViewHolder() במתאם מרחיבה כרגע את התצוגה ממשאב הפריסה של ViewHolder. עם זאת, האינפלציה לא קשורה למתאם, אלא לViewHolder. האינפלציה צריכה להתרחש בViewHolder.
- ב-
onCreateViewHolder(), בוחרים את כל הקוד בגוף הפונקציה. - לוחצים לחיצה ימנית ובוחרים באפשרות Refactor > Extract > Function (שינוי מבנה > חילוץ > פונקציה).
- נותנים לפונקציה את השם
fromומאשרים את הפרמטרים המוצעים. לוחצים על אישור. - מציבים את הסמן על שם הפונקציה
from. מקישים עלAlt+Enter(או עלOption+Enterב-Mac) כדי לפתוח את תפריט הכוונות. - בוחרים באפשרות העברה לאובייקט משני. הפונקציה
from()צריכה להיות באובייקט נלווה כדי שאפשר יהיה להפעיל אותה במחלקהViewHolder, ולא להפעיל אותה במופעViewHolder. - מעבירים את האובייקט
companionאל המחלקהViewHolder. - הופכים את
from()לגלוי לכולם. - ב-
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)
}
}- משנים את החתימה של המחלקה
ViewHolderכך שהבונה יהיה פרטי. השיטהfrom()מחזירה עכשיו מופע חדש שלViewHolder, ולכן אין יותר סיבה להפעיל את בנאי המחלקה שלViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){- מריצים את האפליקציה. האפליקציה אמורה להיבנות ולרוץ כמו קודם, וזהו התוצאה הרצויה אחרי שינוי המבנה.
פרויקט Android Studio: RecyclerViewFundamentals
- הצגת רשימה או רשת של נתונים היא אחת ממשימות ממשק המשתמש הנפוצות ביותר ב-Android.
RecyclerViewנועד להיות יעיל גם כשמוצגות רשימות גדולות מאוד. -
RecyclerViewמבצע רק את העבודה שנדרשת לעיבוד או לציור של פריטים שמוצגים כרגע במסך. - כשפריט יוצא מהמסך, הצפיות בו ממוחזרות. כלומר, הפריט מלא בתוכן חדש שמופיע במסך.
- תבנית המתאם בהנדסת תוכנה עוזרת לאובייקט לעבוד יחד עם API אחר.
RecyclerViewמשתמש במתאם כדי להפוך את נתוני האפליקציה למשהו שאפשר להציג, בלי לשנות את האופן שבו האפליקציה מאחסנת ומעבדת נתונים.
כדי להציג את הנתונים בRecyclerView, צריך את החלקים הבאים:
- RecyclerView
כדי ליצור מופע שלRecyclerView, מגדירים רכיב<RecyclerView>בקובץ הפריסה. - LayoutManager
RecyclerViewמשתמש ב-LayoutManagerכדי לארגן את הפריסה של הפריטים ב-RecyclerView, למשל כדי לפרוס אותם ברשת או ברשימה לינארית.
ב-<RecyclerView>בקובץ הפריסה, מגדירים את מאפייןapp:layoutManagerלמנהל הפריסה (למשלLinearLayoutManagerאוGridLayoutManager).
אפשר גם להגדיר אתLayoutManagerל-RecyclerViewבאופן פרוגרמטי. (הטכניקה הזו מוסברת ב-codelab בהמשך). - פריסה של כל פריט
יוצרים פריסה של פריט נתונים אחד בקובץ פריסה בפורמט XML. - מתאם
יוצרים מתאם שמכין את הנתונים ומגדיר איך הם יוצגו בViewHolder. משייכים את המתאם אלRecyclerView.
כשמפעילים אתRecyclerView, הוא משתמש במתאם כדי להבין איך להציג את הנתונים במסך.
כדי להשתמש במתאם, צריך להטמיע את השיטות הבאות:
–getItemCount()כדי להחזיר את מספר הפריטים.
–onCreateViewHolder()כדי להחזיר אתViewHolderשל פריט ברשימה.
–onBindViewHolder()כדי להתאים את הנתונים לתצוגות של פריט ברשימה. - ViewHolder
ViewHolderמכיל את פרטי התצוגה של פריט אחד מפריסת הפריט. - השיטה
onBindViewHolder()במתאם מתאימה את הנתונים לתצוגות. תמיד מבטלים את השיטה הזו. בדרך כלל,onBindViewHolder()מרחיב את הפריסה של פריט ומציב את הנתונים בתצוגות בפריסה. - מכיוון ש-
RecyclerViewלא יודע דבר על הנתונים,Adapterצריך לעדכן אתRecyclerViewכשהנתונים האלה משתנים. משתמשים ב-notifyDataSetChanged()כדי להודיע ל-Adapterשהנתונים השתנו.
קורס ב-Udacity:
מסמכי תיעוד למפתחי Android:
בקטע הזה מפורטות אפשרויות למשימות ביתיות לתלמידים שעובדים על ה-Codelab הזה כחלק מקורס בהנחיית מדריך. המורה צריך:
- אם צריך, מקצים שיעורי בית.
- להסביר לתלמידים איך להגיש מטלות.
- בודקים את שיעורי הבית.
אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.
אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.
עונים על השאלות הבאות
שאלה 1
איך מוצגים הפריטים ב-RecyclerView? יש לבחור בכל האפשרויות הרלוונטיות.
▢ הצגת הפריטים בתצוגת רשימה או בתצוגת רשת.
▢ גלילה אנכית או אופקית.
▢ גלילה באלכסון במכשירים גדולים יותר, כמו טאבלטים.
▢ מאפשר פריסות בהתאמה אישית כשפריסה של רשימה או רשת לא מספיקה לתרחיש השימוש.
שאלה 2
מה היתרונות של השימוש ב-RecyclerView? יש לבחור בכל האפשרויות הרלוונטיות.
▢ הצגה יעילה של רשימות גדולות.
▢ עדכון אוטומטי של הנתונים.
▢ מצמצם את הצורך ברענון כשפריט מתעדכן, נמחק או מתווסף לרשימה.
▢ שימוש חוזר בתצוגה שגוללת מחוץ למסך כדי להציג את הפריט הבא שגולל במסך.
שאלה 3
מהן כמה מהסיבות לשימוש במתאמים? יש לבחור בכל האפשרויות הרלוונטיות.
▢ הפרדה בין נושאים מקלה על שינוי קוד ובדיקתו.
▢ RecyclerView לא תלוי בנתונים שמוצגים.
▢ שכבות עיבוד הנתונים לא צריכות להתייחס לאופן שבו הנתונים יוצגו.
▢ האפליקציה תפעל מהר יותר.
שאלה 4
אילו מהמשפטים הבאים נכונים לגבי ViewHolder? יש לבחור בכל האפשרויות הרלוונטיות.
▢ הפריסה ViewHolder מוגדרת בקובצי פריסה של XML.
▢ יש ViewHolder אחד לכל יחידת נתונים בקבוצת הנתונים.
▢ אפשר להוסיף יותר מ-ViewHolder אחד ב-RecyclerView.
▢ Adapter קושר נתונים ל-ViewHolder.
עוברים לשיעור הבא: