Lab Lab זה הוא חלק מקורס Android Kotlin Fundamentals. כדי להפיק את המקסימום מהקורס הזה, יש לפעול ברצף לפי קודי שיעור ה-Lab. כל שיעורי Lab של הקורסים מופיעים בדף הנחיתה של Lab Kotlin Fundamentals ל-Android Lab.
מבוא
במעבדה הקודמת, למדתם איך לקבל נתונים משירות אינטרנט ולנתח את התגובה לאובייקט נתונים. במעבדה זו, אתם בונים את הידע הזה כדי לטעון ולהציג תמונות מכתובת URL באינטרנט. כמו כן, אפשר לחזור על אופן היצירה של RecyclerView
ולהשתמש בו כדי להציג רשת של תמונות בדף הסקירה הכללית.
מה כבר צריך לדעת
- איך ליצור קטעי קוד ולהשתמש בהם.
- כיצד להשתמש ברכיבי ארכיטקטורה, כולל מודלים של תצוגה, מפעלי תצוגה, טרנספורמציות ו-
LiveData
. - איך לאחזר קובץ JSON משירות אינטרנט של REST ולנתח את הנתונים האלה לאובייקטים של Kotlin באמצעות הספריות Retrofit ו-Moshi.
- איך ליצור פריסת רשת עם
RecyclerView
. - איך
Adapter
,ViewHolder
וDiffUtil
פועלים.
מה תלמדו
- איך להשתמש בספרייה של החלקה כדי לטעון ולהציג תמונה מכתובת URL.
- איך להשתמש ב
RecyclerView
ובמתאם רשת כדי להציג רשת תמונות. - כיצד לטפל בשגיאות פוטנציאליות במהלך הורדה והצגה של תמונות.
מה צריך לעשות
- יש לשנות את אפליקציית MarsRealEstate כדי לקבל את כתובת ה-URL של התמונה מנתוני הנכס Mars, ולהשתמש בהחלקה כדי לטעון ולהציג את התמונה הזו.
- הוספת אנימציה של טעינה וסמל שגיאה לאפליקציה.
- אפשר להשתמש ב-
RecyclerView
כדי להציג רשת של תמונות מאדים. - הוספה של סטטוס טיפול ושגיאות ל-
RecyclerView
.
ב-Codelab הזה (וב-codelabs קשורים) אתם עובדים עם אפליקציה שנקראת MarsRealEstate, שמציגה נכסים למכירה במאדים. האפליקציה מתחברת לשרת אינטרנט כדי לאחזר ולהציג נתוני נכסים, כולל פרטים כמו המחיר והאם הנכס זמין למכירה או להשכרה. התמונות שמייצגות כל נכס הן תמונות מהחיים במאדים שצולמו על ידי רכבי הירח במאדים.
גרסת האפליקציה של גרסת ה-codelab הזו ממלאת את דף הסקירה הכללית, שבו מוצגות רשת תמונות. התמונות הן חלק מנתוני הנכס שהאפליקציה שלך מקבלת משירות הנדל"ן באינטרנט של Mars. האפליקציה תשתמש בספרייה בהחלקה כדי להציג ולהציג את התמונות, וב-RecyclerView
כדי ליצור את פריסת הרשת של התמונות. האפליקציה תטפל גם בשגיאות רשת באופן חינני.
הצגת תמונה מכתובת אתר עשויה להישמע פשוטה, אבל יש קצת הנדסה טובה כדי שהיא תעבוד כמו שצריך. צריך להוריד את התמונה, לאחסן אותה ולפענח אותה בפורמט הדחוס שלה, כדי להשתמש בה בפורמט Android. התמונה צריכה להיות במטמון למטמון בזיכרון, למטמון מבוסס אחסון או לשניהם. כל זה צריך לקרות בשרשורים שבעדיפות נמוכה כדי שממשק המשתמש יישאר רספונסיבי. כמו כן, כדי לשפר את הביצועים ברשת ובמעבד, מומלץ לאחזר ולפענח יותר מתמונה אחת בבת אחת. למידה איך לטעון תמונות באופן אפקטיבי מהרשת יכולה להיות קוד Lab בלבד.
למרבה המזל, תוכלו להשתמש בספרייה שפותחה על ידי הקהילה בשם החלקה כדי להוריד את הנתונים, לאחסן במאגר את הנתונים שלהם, לפענח אותם ולאחסן אותם במטמון. בהחלקה אתם מקבלים הרבה פחות עבודה מאשר אם צריך לעשות את כל זה מאפס.
המטרה בהחלקה היא:
- כתובת ה-URL של התמונה שרוצים לטעון ולהציג.
- אובייקט
ImageView
כדי להציג את התמונה הזו.
במשימה הזו תלמדו איך להשתמש בהחלקה כדי להציג תמונה יחידה משירות הנדל"ן באינטרנט. אתם מציגים את התמונה שמייצגת את הנכס הראשון של מאדים ברשימת הנכסים ששירות האינטרנט מחזיר. לפניכם צילומי המסך שלפני ואחרי:
שלב 1: הוספת תלות בהחלקה
- פותחים את אפליקציית MarsRealEstate ממעבדת הקוד האחרונה. (אם אין לך את האפליקציה, אפשר להוריד את MarsRealEstateNetwork).
- מפעילים את האפליקציה כדי לראות מה היא עושה. (הוא מציג פרטי טקסט של נכס שזמין באופן היפותטי למאדים).
- פותחים את build.gradle (מודול: אפליקציה).
- בקטע
dependencies
, מוסיפים את השורה הזו לספריית ההחלקה:
implementation "com.github.bumptech.glide:glide:$version_glide"
שימו לב שמספר הגרסה כבר מוגדר בנפרד בקובץ Gradle של הפרויקט.
- לוחצים על Sync Now (ביצוע סנכרון) כדי לבנות מחדש את הפרויקט בהתאם לתלות החדשה.
שלב 2: עדכון מודל התצוגה
לאחר מכן עליך לעדכן את הכיתה ב-OverviewViewModel
כך שתכלול נתונים בזמן אמת עבור נכס מאדים יחיד.
- פתיחת
overview/OverviewViewModel.kt
מתחת ל-LiveData
של_response
, מוסיפים נתונים פנימיים (חלליים) וחיצוניים (לא משתנים) לאובייקטMarsProperty
יחיד.
ייבוא של הכיתהMarsProperty
(com.example.android.marsrealestate.network.MarsProperty
) לפי בקשה.
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property
- בשיטה
getMarsRealEstateProperties()
, מאתרים את השורה בתוך בלוקtry/catch {}
שמגדירה את_response.value
כמספר הנכסים. יש להוסיף את הבדיקה שמוצגת בהמשך. אם אובייקטים מסוגMarsProperty
זמינים, הבדיקה הזו מגדירה את הערך של_property
LiveData
לנכס הראשון ב-listResult
.
if (listResult.size > 0) {
_property.value = listResult[0]
}
בלוק try/catch {}
המלא נראה עכשיו:
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
- פותחים את הקובץ
res/layout/fragment_overview.xml
. ברכיב<TextView>
, משנים את הערך שלandroid:text
לקישור לרכיבimgSrcUrl
שלproperty
LiveData
:
android:text="@{viewModel.property.imgSrcUrl}"
- הפעלת האפליקציה. ב-
TextView
מוצגת רק כתובת ה-URL של התמונה בנכס Mars הראשון. כל מה שעשית עד עכשיו הוגדר את מודל התצוגה ואת הנתונים הפעילים עבור כתובת ה-URL הזו.
שלב 3: יוצרים מתאם מחייב ומתקשרים להחלקה
עכשיו יש לכם כתובת URL של תמונה להצגה, והגיע הזמן להתחיל לעבוד עם התכונה 'החלקה' כדי לטעון את התמונה. בשלב זה, צריך להשתמש במתאם איגוד כדי להעביר את כתובת ה-URL ממאפיין XML שמשויך ל-ImageView
, ולהשתמש בהחלקה כדי לטעון את התמונה. מתאמים לאיגוד הם שיטות של תוספים שפועלות בין תצוגה מפורטת לבין נתוני איגוד כדי לספק התנהגות מותאמת אישית כאשר הנתונים משתנים. במקרה הזה, ההתנהגות המותאמת אישית היא לבצע קריאה ב-Glide כדי לטעון תמונה מכתובת URL אל ImageView
.
- פתיחת
BindingAdapters.kt
הקובץ הזה יכלול את המתאמים המשמשים אותך באפליקציה. - יוצרים פונקציה ב-
bindImage()
שמשתמשת בImageView
ובString
כפרמטרים. מוסיפים הערות לפונקציה באמצעות@BindingAdapter
. ההערה@BindingAdapter
מציינת ששיוך הנתונים מחייב הפעלה של מתאם איגוד זה כשפריט XML כולל את המאפייןimageUrl
.
יש לייבא אתandroidx.databinding.BindingAdapter
ואתandroid.widget.ImageView
לפי בקשה.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}
- בתוך הפונקציה
bindImage()
, מוסיפים בלוקlet {}
לארגומנטimgUrl
:
imgUrl?.let {
}
- בתוך הבלוק
let {}
, מוסיפים את השורה שבהמשך כדי להמיר את מחרוזת כתובת ה-URL (מ-XML) לאובייקטUri
. מייבאים אתandroidx.core.net.toUri
כשתתבקשו.
אתם רוצים שהאובייקט הסופיUri
ישתמש בסכימת HTTPS, כי השרת שממנו אתם שולפים את התמונות מחייב סכימה זו. כדי להשתמש בסכימת ה-HTTPS, יש לצרף אתbuildUpon.scheme("https")
לכלי לבנייתtoUri
. השיטהtoUri()
היא פונקציית תוסף Kotlin מספריית הליבה של KTX ל-Android, כך שנראה פשוט כחלק מהמחלקהString
.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
- עדיין בתוך
let {}
, יש להתקשר אלGlide.with()
כדי לטעון את התמונה מהאובייקטUri
אלImageView
. כשמייבאים בקשה,com.bumptech.glide.Glide
מייבאים.
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)
שלב 4: עדכון הפריסה ומקטעים
אף על פי שמערכת Glide טען את התמונה, עדיין אין מה לראות. השלב הבא הוא לעדכן את הפריסה ואת קטעי הקוד של ImageView
כדי להציג את התמונה.
- פתיחת
res/layout/gridview_item.xml
זהו קובץ משאב לפריסה שבו תשתמשו לכל פריט ב-RecyclerView
בהמשך ב-Codelab. אתם משתמשים בו באופן זמני כדי להציג רק את התמונה היחידה. - מעל האלמנט
<ImageView>
, מוסיפים רכיב<data>
לאיגוד הנתונים ומקשרים למחלקהOverviewViewModel
:
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
- יש להוסיף מאפיין
app:imageUrl
לאלמנטImageView
כדי להשתמש במתאם החדש לטעינת תמונות:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
- פתיחת
overview/OverviewFragment.kt
בשיטהonCreateView()
, צריך להגיב על השורה שמנפחת את הכיתהFragmentOverviewBinding
ולהקצה אותה למשתנה המחייב. זהו מצב זמני בלבד. אתה חזור אליה מאוחר יותר.
//val binding = FragmentOverviewBinding.inflate(inflater)
- במקום זאת, יש להוסיף קו לניפוח של הכיתה
GridViewItemBinding
. מייבאים אתcom.example.android.marsrealestate. databinding.GridViewItemBinding
כשמוצגת בקשה לעשות זאת.
val binding = GridViewItemBinding.inflate(inflater)
- הפעילו את האפליקציה. עכשיו אמורה להופיע תמונה של התמונה מה-
MarsProperty
הראשון ברשימת התוצאות.
שלב 5: מוסיפים תמונות פשוטות של שגיאות וטעינה
החלקה יכולה לשפר את חוויית המשתמש על ידי הצגת תמונה placeholder בזמן טעינת התמונה ותמונת שגיאה אם הטעינה נכשלה, למשל אם התמונה חסרה או פגומה. בשלב זה, מוסיפים את הפונקציונליות הזו למתאם המחייב ולפריסה.
- פותחים את
res/drawable/ic_broken_image.xml
ולוחצים על הכרטיסייה תצוגה מקדימה בצד שמאל. בתמונת השגיאה, אתם משתמשים בסמל התמונה הפגומה. הקובץ זמין בספריית הסמלים המובְנים. ניתן לצייר את הווקטור הזה באמצעות המאפייןandroid:tint
כדי לצבוע את הסמל בצבע אפור.
- פתיחת
res/drawable/loading_animation.xml
ניתן לשרטט זאת כאנימציה ומוגדרת עם התג<animate-rotate>
. האנימציה מסובבת את התמונה שניתן לצייר בה,loading_img.xml
, סביב נקודת המרכז. (האנימציה לא מופיעה בתצוגה המקדימה).
- חוזרים לקובץ
BindingAdapters.kt
. בשיטהbindImage()
, יש לעדכן את השיחה ל-Glide.with()
כדי להתקשר לפונקציהapply()
ביןload()
ל-into()
. מייבאים אתcom.bumptech.glide.request.RequestOptions
כשמתבקשים.
הקוד הזה מגדיר את תמונת ה-placeholder לטעינה, במהלך טעינה (אתloading_animation
אפשר לצייר). הקוד גם מגדיר תמונה לשימוש במקרה שטעינת התמונה נכשלה (ניתן לשרטט אתbroken_image
). השיטה המלאה שלbindImage()
נראית עכשיו:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri =
imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
- מריצים את האפליקציה. בהתאם למהירות החיבור לרשת, ייתכן שבזמן הטעינה תוצג תמונת הטעינה בזמן שמתבצעת הורדה או הצגה של תמונת הנכס. עם זאת, לא תראו את סמל התמונה הפגומה, גם אם תכבו את הרשת – תפתרו את הבעיה בחלק האחרון של הקוד.
האפליקציה טוענת עכשיו את פרטי הנכס מהאינטרנט. על סמך נתונים מהפריט הראשון מסוג MarsProperty
, יצרת נכס LiveData
במודל התצוגה המפורטת. בנוסף, השתמשת בכתובת ה-URL של התמונה מנתוני הנכס האלה כדי לאכלס ImageView
. אבל המטרה היא שבאפליקציה תוצג רשת של תמונות, ולכן רוצים להשתמש ב-RecyclerView
עם GridLayoutManager
.
שלב 1: עדכון מודל התצוגה
בשלב זה, למודל התצוגה יש _property
LiveData
שמכיל אובייקט MarsProperty
אחד – הראשון ברשימת התגובות משירות האינטרנט. בשלב הזה, יש לשנות את ה-LiveData
כדי לשמור את הרשימה המלאה של אובייקטים מסוג MarsProperty
.
- פתיחת
overview/OverviewViewModel.kt
- משנים את המשתנה הפרטי
_property
ל-_properties
. יש לשנות את הסוג הזה כרשימה של אובייקטים מסוגMarsProperty
.
private val _properties = MutableLiveData<List<MarsProperty>>()
- יש להחליף את הנתונים החיצוניים של
property
בשידור חי ב-properties
. יש להוסיף גם את הרשימה הזו לסוגLiveData
כאן:
val properties: LiveData<List<MarsProperty>>
get() = _properties
- יש לגלול למטה אל השיטה
getMarsRealEstateProperties()
. בתוך הבלוקtry {}
, מחליפים את כל הבדיקה שהוספת במשימה הקודמת בשורה הבאה. מכיוון שהמשתנהlistResult
מכיל רשימה שלMarsProperty
אובייקטים, אפשר להקצות אותו לערך_properties.value
במקום לבדוק אם הוא מגיב בהצלחה.
_properties.value = listResult
בלוק try/catch
כולו נראה עכשיו:
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
_properties.value = listResult
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
שלב 2: עדכון הפריסות ומקטעים
השלב הבא הוא לשנות את הפריסה והמקטעים של האפליקציה כך שישתמשו בתצוגת מיחזור ובפריסת רשת, במקום בתצוגת תמונה יחידה.
- פתיחת
res/layout/gridview_item.xml
משנים את קישור הנתונים מ-OverviewViewModel
ל-MarsProperty
ומשנים את השם ל-"property"
.
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
- ב-
<ImageView>
, משנים את המאפייןapp:imageUrl
כך שיתייחס לכתובת ה-URL של התמונה באובייקטMarsProperty
:
app:imageUrl="@{property.imgSrcUrl}"
- פתיחת
overview/OverviewFragment.kt
ב-onCreateview()
, יש לבטל את הסימון של השורה שמנפחת אתFragmentOverviewBinding
. מחיקה או מחיקה של הקו שמנפח אתGridViewBinding
. השינויים האלה מבטלים את השינויים הזמניים שביצעתם במשימה האחרונה.
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)
- פתיחת
res/layout/fragment_overview.xml
מחיקת כל הרכיב<TextView>
. - במקום זאת יש להוסיף את הרכיב
<RecyclerView>
הזה, שמשתמש בפריסה שלGridLayoutManager
ובפריסה שלgrid_view_item
עבור פריט בודד:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
שלב 3: מוסיפים את המתאם של רשת התמונות
לפריסה של fragment_overview
יש RecyclerView
, ולפריסה של grid_view_item
יש ImageView
אחד. בשלב זה, צריך להכפיל את הנתונים ל-RecyclerView
באמצעות מתאם RecyclerView
.
- פתיחת
overview/PhotoGridAdapter.kt
- יש ליצור את המחלקה
PhotoGridAdapter
עם הפרמטרים של ה-constructor שמוצגים בהמשך. הכיתהPhotoGridAdapter
מרחיבה אתListAdapter
. ה-constructor שלו צריך את סוג הפריט, בעל התצוגה וההטמעה שלDiffUtil.ItemCallback
.
יש לייבא את הכיתותandroidx.recyclerview.widget.ListAdapter
ו-com.example.android.marsrealestate.network.MarsProperty
לפי הצורך. בשלבים הבאים, עליך ליישם את החלקים החסרים האחרים של הרכיב הזה שיוצר שגיאות.
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}
- יש ללחוץ במקום כלשהו בכיתה
PhotoGridAdapter
וללחוץ עלControl+i
כדי ליישם את השיטותListAdapter
, שהןonCreateViewHolder()
ו-onBindViewHolder()
.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}
- בסוף ההגדרה של הכיתה
PhotoGridAdapter
, אחרי השיטות שהוספת עכשיו, מוסיפים הגדרת אובייקט נלווית ל-DiffCallback
, כפי שמוצג למטה.
ייבוא שלandroidx.recyclerview.widget.DiffUtil
לפי בקשה.
האובייקטDiffCallback
מרחיב אתDiffUtil.ItemCallback
עם סוג האובייקט שברצונך להשוות —MarsProperty
.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
- יש ללחוץ על
Control+i
כדי ליישם את שיטות ההשוואה לפריט הזה, שהןareItemsTheSame()
ו-areContentsTheSame()
.
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }
- עבור השיטה
areItemsTheSame()
, יש להסיר את הפעולה לביצוע. יש להשתמש באופרטור השווה להשוואה של Kotlin'===
, שמחזירה אתtrue
אם האובייקטים ל-oldItem
ול-newItem
זהים.
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}
- בשביל
areContentsTheSame()
, יש להשתמש באופרטור 'שוויון רגיל' רק עם המזההoldItem
ו-newItem
.
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}
- עדיין בתוך הכיתה
PhotoGridAdapter
, מתחת לאובייקט הנלווה, יש להוסיף הגדרת כיתה פנימית עבורMarsPropertyViewHolder
, שמגדילה אתRecyclerView.ViewHolder
.
מייבאים אתandroidx.recyclerview.widget.RecyclerView
ואתcom.example.android.marsrealestate.databinding.GridViewItemBinding
כשמוצגת בקשה.
צריך את המשתנהGridViewItemBinding
כדי לקשר אתMarsProperty
לפריסה. לכן צריך להעביר את המשתנה ל-MarsPropertyViewHolder
. מכיוון שסיווג הבסיסViewHolder
דורש תצוגה מפורטת בבונה, אתה מעביר אותה בתצוגת הבסיס של הקישור.
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}
- ב-
MarsPropertyViewHolder
, יוצרים שיטה מסוגbind()
שמקבלת אובייקטMarsProperty
כארגומנט ומגדירהbinding.property
לאובייקט הזה. צריך להתקשר אלexecutePendingBindings()
אחרי הגדרת הנכס, כך שהעדכון יתבצע באופן מיידי.
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}
- ב-
onCreateViewHolder()
, מסירים את הפעולה לביצוע ומוסיפים את השורה המוצגת למטה. מייבאים אתandroid.view.LayoutInflater
כשמוצגת בקשה לעשות זאת.
שיטתonCreateViewHolder()
צריכה להחזירMarsPropertyViewHolder
חדש, שנוצר על ידי ניפוח שלGridViewItemBinding
ושימוש ב-LayoutInflater
מהקשרViewGroup
של ההורה שלך.
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))
- בשיטה
onBindViewHolder()
, יש להסיר את הפעולה לביצוע ולהוסיף את השורות המוצגות למטה. כאן עליך להתקשר אלgetItem()
כדי לקבל את האובייקטMarsProperty
המשויך למיקוםRecyclerView
, ולאחר מכן להעביר את הנכס הזה לשיטתbind()
ב-MarsPropertyViewHolder
.
val marsProperty = getItem(position)
holder.bind(marsProperty)
שלב 4: מוסיפים את המתאם המקשר ומחברים את החלקים
לבסוף, יש להשתמש ב-BindingAdapter
כדי לאתחל את PhotoGridAdapter
באמצעות רשימת אובייקטים של MarsProperty
. שימוש ב-BindingAdapter
להגדרת הנתונים של RecyclerView
יגרום לכך שקישור הנתונים יצפה באופן אוטומטי ב-LiveData
עבור רשימת האובייקטים של MarsProperty
. לאחר מכן, המתאם המקשר נקרא אוטומטית כשרשימת MarsProperty
משתנה.
- פתיחת
BindingAdapters.kt
- בסוף הקובץ, מוסיפים שיטה
bindRecyclerView()
שמקבלתRecyclerView
ורשימה של אובייקטים ב-MarsProperty
כארגומנטים. יש להוסיף הערה לשיטה הזו באמצעות@BindingAdapter
.
ייבוא שלandroidx.recyclerview.widget.RecyclerView
ושלcom.example.android.marsrealestate.network.MarsProperty
לפי בקשה.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}
- בתוך הפונקציה
bindRecyclerView()
, מעבירים את הנתונים מסוגrecyclerView.adapter
אלPhotoGridAdapter
ומתקשרים אלadapter.submitList()
עם הנתונים. כךRecyclerView
יוכל לראות רשימה חדשה.
מייבאים את com.example.android.marsrealestate.overview.PhotoGridAdapter
כשמוצגת בקשה לעשות זאת.
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
- פתיחת
res/layout/fragment_overview.xml
יש להוסיף את המאפייןapp:listData
לאלמנטRecyclerView
ולהגדיר אותו כ-viewmodel.properties
באמצעות קישור נתונים.
app:listData="@{viewModel.properties}"
- פתיחת
overview/OverviewFragment.kt
בonCreateView()
, ממש לפני הקריאה אלsetHasOptionsMenu()
, יש להפעיל את המתאםRecyclerView
ב-binding.photosGrid
לאובייקטPhotoGridAdapter
חדש.
binding.photosGrid.adapter = PhotoGridAdapter()
- מפעילים את האפליקציה. אמורה להופיע רשת של
MarsProperty
תמונות. בזמן הגלילה כדי לראות תמונות חדשות, האפליקציה מציגה את סמל התקדמות הטעינה לפני הצגת התמונה עצמה. אם מפעילים את מצב טיסה, תמונות שעדיין לא נטענו יופיעו כסמלים של תמונות שבורות.
אפליקציית MarsRealEstate מציגה את סמל התמונה הפגומה כשאי אפשר לאחזר את התמונה. אבל כשאין רשת, האפליקציה מציגה מסך ריק.
זו חוויית משתמש מעולה. במשימה הזו אתם מוסיפים טיפול בסיסי לשגיאות, כדי לתת למשתמש מושג טוב יותר מה קורה. אם האינטרנט לא יהיה זמין, סמל השגיאה יוצג באפליקציה. בזמן שהאפליקציה מאחזרת את הרשימה MarsProperty
, האפליקציה תציג את האנימציה של הטעינה.
שלב 1: הוספת סטטוס למודל התצוגה
כדי להתחיל, יוצרים LiveData
במודל התצוגה, כדי לייצג את הסטטוס של בקשת האינטרנט. יש שלושה מצבים שכדאי להביא בחשבון – טעינה, הצלחה וכישלון. מצב הטעינה מתרחש בזמן ההמתנה לנתונים בשיחה ב-await()
.
- פתיחת
overview/OverviewViewModel.kt
בחלק העליון של הקובץ (לאחר הייבוא, לפני הגדרת הכיתה), מוסיפיםenum
כדי לייצג את כל הסטטוסים הזמינים:
enum class MarsApiStatus { LOADING, ERROR, DONE }
- שינוי השם של ההגדרה הפעילה ושל הגדרת הנתונים בזמן אמת של
_response
בכל השיעורים ב-OverviewViewModel
ל-_status
. מאחר שהוספת תמיכה ב-_properties
LiveData
מוקדם יותר ב-codelab הזה, לא נעשה שימוש בתגובה המלאה לשירות אינטרנט. יש צורך בערךLiveData
כדי לעקוב אחר הסטטוס הנוכחי, כדי שתהיה לך אפשרות לשנות את השם של המשתנים הקיימים.
בנוסף, יש לשנות את הסוגים מ-String
ל-MarsApiStatus.
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status
- גוללים למטה לשיטה
getMarsRealEstateProperties()
ומעדכנים כאן גם את_response
ל_status
. משנים את המחרוזת ב"Success"
למצבMarsApiStatus.DONE
, ואת המחרוזת"Failure"
ל-MarsApiStatus.ERROR
. - צריך להוסיף סטטוס
MarsApiStatus.LOADING
בחלק העליון של הבלוקtry {}
, לפני הקריאה אלawait()
. זהו הסטטוס הראשוני כאשר הקורטי פועל, וממתינים לנתונים. בלוקtry/catch {}
המלא נראה עכשיו:
try {
_status.value = MarsApiStatus.LOADING
var listResult = getPropertiesDeferred.await()
_status.value = MarsApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}
- אחרי מצב השגיאה בבלוק
catch {}
, מגדירים את_properties
LiveData
כרשימה ריקה. פעולה זו מנקה אתRecyclerView
.
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}
שלב 2: מוסיפים מתאם מחייב לסטטוס PhotoView
עכשיו יש לכם סטטוס במודל התצוגה, אבל הוא פשוט קבוצת מדינות. איך מציגים אותו באפליקציה עצמה? בשלב זה משתמשים ב-ImageView
המחובר לקישור נתונים כדי להציג סמלים של מצבי הטעינה והשגיאה. כאשר האפליקציה במצב טעינה או במצב שגיאה, ה-ImageView
אמור להיות גלוי. בסיום הטעינה של האפליקציה, האפליקציה ImageView
תהיה גלויה.
- פתיחת
BindingAdapters.kt
יש להוסיף מתאם מחייב חדש בשםbindStatus()
שיש בו ערךImageView
וערךMarsApiStatus
כארגומנטים. מייבאים אתcom.example.android.marsrealestate.overview.MarsApiStatus
כשמוצגת בקשה לעשות זאת.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}
- יש להוסיף
when {}
בתוך השיטהbindStatus()
כדי לעבור בין הסטטוסים השונים.
when (status) {
}
- בתוך ה-
when {}
, מוסיפים נרתיק למצב הטעינה (MarsApiStatus.LOADING
). למצב הזה, מגדירים את ה-ImageView
כגלויים ומקצים לו את אנימציית הטעינה. זוהי אותה אנימציה שבה השתמשת בהחלקה במשימה הקודמת. מייבאים אתandroid.view.View
כשמוצגת בקשה לעשות זאת.
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}
- יש להוסיף רישיות למצב השגיאה, שהוא
MarsApiStatus.ERROR
. בדומה למה שעשית במצבLOADING
, יש להגדיר את הסטטוסImageView
כ'גלוי' ולהשתמש שוב בשגיאות החיבור.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}
- יש להוסיף כיסוי למצב הסיום, שהוא
MarsApiStatus.DONE
. כאן קיבלת תגובה מוצלחת, לכן יש להשבית את הרשאות הגישה לסטטוסImageView
כדי להסתיר אותה.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}
שלב 3: הוספת הפריסה ל-ImageView
- פתיחת
res/layout/fragment_overview.xml
מתחת לרכיבRecyclerView
, בתוך ה-ConstraintLayout
, יש להוסיף את ה-ImageView
המוצגים למטה.
ל-ImageView
הזה יש אילוצים כמוRecyclerView
. עם זאת, הרוחב והגובה משתמשים ב-wrap_content
כדי למרכז את התמונה במקום למתוח את התמונה כך שתמלא את התצוגה. שימו לב גם למאפייןapp:marsApiStatus
, שבו התצוגה המפורטת מתקשרת אלBindingAdapter
כאשר נכס הסטטוס במודל התצוגה משתנה.
<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marsApiStatus="@{viewModel.status}" />
- יש להפעיל את מצב הטיסה באמולטור או במכשיר כדי לדמות חיבור לרשת חסרה. מקבצים את האפליקציה ומפעילים אותה, ושימו לב שמופיעה תמונת השגיאה:
- יש להקיש על הלחצן 'הקודם' כדי לסגור את האפליקציה ולהשבית את מצב הטיסה. יש להשתמש במסך העדכני ביותר כדי להחזיר את האפליקציה. בהתאם למהירות החיבור לרשת, ייתכן שיופיע סמל טעינה קצר במיוחד כאשר האפליקציה מבצעת שאילתה בשירות האינטרנט לפני שהתמונות נטענות.
פרויקט ב-Android Studio: MarsRealEstateGrid
- כדי לפשט את תהליך ניהול התמונות, אפשר להוריד את האפליקציה, להשתמש בספריית ההחלקה כדי להוריד תמונות, לאחסן אותן, לפענח אותן ולאחסן במטמון את האפליקציה.
- ב-החלקה יש צורך בשני דברים כדי לטעון תמונה מהאינטרנט: כתובת URL של תמונה, ואובייקט
ImageView
כדי להוסיף את התמונה. כדי לציין את האפשרויות האלה, צריך להשתמש בשיטותload()
ו-into()
בהחלקה. - מתאמים לחייב הם שיטות תוסף המוקפות בין תצוגה מפורטת לבין נתוני איגוד של תצוגה מפורטת זו. מתאמים לאיגוד מספקים התנהגות מותאמת אישית כאשר הנתונים משתנים, למשל, כדי להתקשר להחלקה כדי לטעון תמונה מכתובת URL אל
ImageView
. - מתאמים לכריכה הם שיטות הערות עם ההערה
@BindingAdapter
. - כדי להוסיף אפשרויות לבקשת ההחלקה, צריך להשתמש בשיטה
apply()
. לדוגמה, אפשר להשתמש ב-apply()
עםplaceholder()
כדי לציין פריט גרפי שניתן לטעינה, ולהשתמש ב-apply()
עםerror()
כדי לציין שגיאה שניתן לשרטט. - כדי ליצור רשת תמונות, יש להשתמש ב-
RecyclerView
עםGridLayoutManager
. - כדי לעדכן את רשימת הנכסים כאשר היא משתנה, יש להשתמש במתאם מחייב בין
RecyclerView
לבין הפריסה.
קורס אוניברסיטה:
התיעוד של מפתח Android:
אחר:
בקטע הזה מפורטות מטלות שיעורי בית אפשריות לתלמידים שעובדים עם קוד Lab הזה, במסגרת קורס בהדרכת מורה. למורה יש אפשרות לבצע את הפעולות הבאות:
- אם צריך, מקצים שיעורי בית.
- ספרו לתלמידים איך מגישים מטלות בשיעורי בית.
- לתת ציונים למטלות שיעורי הבית.
המורים יכולים להשתמש בהצעות האלה כמה שפחות, ומומלץ להקצות להן כל שיעורי בית שדעתם מתאימה להם.
אם אתם עובדים בעצמכם על שיעור הקוד הזה, אתם מוזמנים להשתמש במטלות שיעורי הבית האלה כדי לבחון את הידע שלכם.
מענה על השאלות הבאות
שאלה 1
באיזו שיטת החלקה השתמשת כדי לציין את ImageView
שיכלול את התמונה שנטענה?
▬ into()
▬ with()
▬ imageview()
▬ apply()
שאלה 2
איך אתם מציינים תמונת placeholder שתוצג בעת הטעינה?
הוספה יש להשתמש בשיטה into()
עם אפשרות לשרטוט.
▬ להשתמש ב-RequestOptions()
ולהתקשר לשיטת placeholder()
עם שרטוט.
▬ להקצות את המאפיין Glide.placeholder
למקום שניתן לשרטוט.
▬ להשתמש ב-RequestOptions()
ולהתקשר לשיטת loadingImage()
עם שרטוט.
שאלה 3
איך אפשר לציין ששיטה היא מתאם מחייב?
הוספה יש להתקשר אל השיטה setBindingAdapter()
במכשיר LiveData
.
הוספה יש להוסיף את השיטה לקובץ Kotlin בשם BindingAdapters.kt
.
▬ להשתמש במאפיין android:adapter
בפריסת XML.
הוספה של הערות באמצעות השיטה עם @BindingAdapter
.
מעבר לשיעור הבא:
קישורים למעבדות אחרות של הקוד בקורס הזה זמינים בדף הנחיתה של Android Kotlin Fundamentals Codelabs.