Android Kotlin Fundamentals 08.2: بارگیری و نمایش تصاویر از اینترنت

این کد لبه بخشی از دوره آموزشی Android Kotlin Fundamentals است. اگر به ترتیب روی کدها کار کنید، بیشترین ارزش را از این دوره خواهید گرفت. همه کد لبه های دوره در صفحه فرود کد لبه های Android Kotlin Fundamentals فهرست شده اند.

مقدمه

در آزمایشگاه کد قبلی، یاد گرفتید که چگونه داده ها را از یک وب سرویس دریافت کنید و پاسخ را به یک شی داده تجزیه کنید. در این نرم افزار کد، شما بر اساس آن دانش برای بارگیری و نمایش عکس ها از URL وب ایجاد می کنید. همچنین می‌توانید نحوه ساخت RecyclerView را مجدداً مرور کنید و از آن برای نمایش شبکه‌ای از تصاویر در صفحه نمای کلی استفاده کنید.

آنچه از قبل باید بدانید

  • نحوه ایجاد و استفاده از قطعات
  • نحوه استفاده از مولفه‌های معماری از جمله مدل‌های مشاهده، کارخانه‌های مشاهده مدل، تبدیل‌ها و LiveData .
  • نحوه بازیابی JSON از یک وب سرویس REST و تجزیه آن داده ها به اشیاء Kotlin با استفاده از کتابخانه های Retrofit و Moshi .
  • نحوه ساخت یک طرح شبکه با RecyclerView .
  • Adapter ، ViewHolder و DiffUtil چگونه کار می کنند.

چیزی که یاد خواهید گرفت

  • نحوه استفاده از کتابخانه Glide برای بارگیری و نمایش تصویر از URL وب.
  • نحوه استفاده از RecyclerView و آداپتور شبکه برای نمایش شبکه ای از تصاویر.
  • نحوه رسیدگی به خطاهای احتمالی هنگام دانلود و نمایش تصاویر.

کاری که خواهی کرد

  • برنامه MarsRealEstate را تغییر دهید تا URL تصویر را از داده های ویژگی Mars دریافت کنید و از Glide برای بارگیری و نمایش آن تصویر استفاده کنید.
  • یک انیمیشن بارگیری و نماد خطا به برنامه اضافه کنید.
  • از RecyclerView برای نمایش شبکه ای از تصاویر دارایی مریخ استفاده کنید.
  • وضعیت و رسیدگی به خطا را به RecyclerView اضافه کنید.

در این کد لبه (و کدهای مرتبط)، شما با اپلیکیشنی به نام MarsRealEstate کار می کنید که املاکی را برای فروش در مریخ نشان می دهد. این برنامه برای بازیابی و نمایش داده های دارایی، از جمله جزئیاتی مانند قیمت و اینکه آیا ملک برای فروش یا اجاره موجود است یا خیر، به یک سرور اینترنتی متصل می شود. تصاویری که هر ملک را نشان می دهد، عکس های واقعی از مریخ است که از مریخ نوردهای ناسا گرفته شده است.

نسخه برنامه ای که در این لبه کد می سازید، صفحه نمای کلی را پر می کند که شبکه ای از تصاویر را نمایش می دهد. تصاویر بخشی از داده‌های دارایی هستند که برنامه شما از وب سرویس املاک و مستغلات Mars دریافت می‌کند. برنامه شما از کتابخانه Glide برای بارگیری و نمایش تصاویر و از RecyclerView برای ایجاد طرح بندی شبکه ای برای تصاویر استفاده می کند. برنامه شما همچنین خطاهای شبکه را به خوبی مدیریت می کند.

نمایش یک عکس از یک URL وب ممکن است ساده به نظر برسد، اما کمی مهندسی وجود دارد تا آن را به خوبی کار کند. تصویر باید دانلود، بافر، و از فرمت فشرده آن به تصویری که اندروید بتواند از آن استفاده کند، رمزگشایی شود. تصویر باید در حافظه پنهان، حافظه نهان مبتنی بر ذخیره سازی یا هر دو ذخیره شود. همه اینها باید در رشته‌های پس‌زمینه با اولویت پایین اتفاق بیفتد تا رابط کاربری پاسخگو باقی بماند. همچنین، برای بهترین عملکرد شبکه و CPU، ممکن است بخواهید همزمان بیش از یک تصویر را واکشی و رمزگشایی کنید. یادگیری نحوه بارگذاری موثر تصاویر از شبکه می تواند به خودی خود یک رمز نگاری باشد.

خوشبختانه، می‌توانید از یک کتابخانه توسعه‌یافته در جامعه به نام Glide برای دانلود، بافر، رمزگشایی و کش کردن تصاویر خود استفاده کنید. Glide کار بسیار کمتری نسبت به زمانی که مجبور بودید همه این کارها را از ابتدا انجام دهید، برای شما باقی می گذارد.

Glide اساساً به دو چیز نیاز دارد:

  • نشانی اینترنتی تصویری که می‌خواهید بارگیری کنید و نشان دهید.
  • یک شی ImageView برای نمایش آن تصویر.

در این کار، نحوه استفاده از Glide را برای نمایش یک تصویر از وب سرویس املاک یاد می گیرید. شما تصویری را که نشان دهنده اولین ویژگی Mars در لیست ویژگی هایی است که وب سرویس برمی گرداند، نمایش می دهید. در اینجا اسکرین شات های قبل و بعد آمده است:

مرحله 1: وابستگی Glide را اضافه کنید

  1. برنامه MarsRealEstate را از آخرین کد لبه باز کنید. (اگر برنامه را ندارید می توانید MarsRealEstateNetwork را از اینجا دانلود کنید.)
  2. برنامه را اجرا کنید تا ببینید چه کاری انجام می دهد. (جزئیات متنی یک ویژگی را نشان می دهد که به طور فرضی در مریخ موجود است.)
  3. build.gradle (ماژول: برنامه) را باز کنید.
  4. در بخش dependencies ، این خط را برای کتابخانه Glide اضافه کنید:
implementation "com.github.bumptech.glide:glide:$version_glide"


توجه داشته باشید که شماره نسخه قبلاً به طور جداگانه در فایل Gradle پروژه تعریف شده است.

  1. روی Sync Now کلیک کنید تا پروژه را با وابستگی جدید بازسازی کنید.

مرحله 2: مدل view را به روز کنید

سپس کلاس OverviewViewModel را به‌روزرسانی می‌کنید تا داده‌های زنده برای یک ویژگی مریخ را شامل شود.

  1. overview/OverviewViewModel.kt باز کنید. درست در زیر LiveData برای _response ، داده‌های زنده داخلی (قابل تغییر) و خارجی (غیرقابل تغییر) را برای یک شی MarsProperty اضافه کنید.

    کلاس MarsProperty ( com.example.android.marsrealestate.network.MarsProperty ) را در صورت درخواست وارد کنید.
private val _property = MutableLiveData<MarsProperty>()

val property: LiveData<MarsProperty>
   get() = _property
  1. در متد 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}"
 }
  1. فایل res/layout/fragment_overview.xml باز کنید. در عنصر <TextView> ، android:text تغییر دهید تا به مولفه imgSrcUrl از property LiveData متصل شود:
android:text="@{viewModel.property.imgSrcUrl}"
  1. برنامه را اجرا کنید. TextView فقط URL تصویر را در اولین ویژگی Mars نمایش می دهد. تمام کاری که تاکنون انجام داده اید این است که مدل view و داده های زنده را برای آن URL تنظیم کرده اید.

مرحله 3: یک آداپتور اتصال ایجاد کنید و با Glide تماس بگیرید

اکنون URL یک تصویر را برای نمایش دارید، و زمان آن رسیده است که برای بارگذاری آن تصویر، کار با Glide را شروع کنید. در این مرحله، از یک آداپتور binding برای گرفتن URL از یک ویژگی XML مرتبط با ImageView استفاده می‌کنید و از Glide برای بارگیری تصویر استفاده می‌کنید. آداپتورهای اتصال روش‌های توسعه‌ای هستند که بین نما و داده‌های محدود قرار می‌گیرند تا رفتار سفارشی را هنگام تغییر داده ارائه دهند. در این مورد، رفتار سفارشی فراخوانی Glide برای بارگیری تصویر از URL در ImageView است.

  1. BindingAdapters.kt را باز کنید. این فایل آداپتورهای اتصالی را که در سرتاسر برنامه استفاده می‌کنید نگه می‌دارد.
  2. یک تابع bindImage() ایجاد کنید که یک ImageView و یک String به عنوان پارامتر می گیرد. تابع را با @BindingAdapter حاشیه نویسی کنید. حاشیه نویسی @BindingAdapter به binding داده می گوید که می خواهید این آداپتور binding زمانی که یک مورد XML دارای ویژگی imageUrl باشد، اجرا شود.

    در صورت درخواست androidx.databinding.BindingAdapter و android.widget.ImageView را وارد کنید.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. در تابع bindImage() یک بلوک let {} برای آرگومان imgUrl اضافه کنید:
imgUrl?.let { 
}
  1. در داخل بلوک let {} ، خط نشان داده شده در زیر را اضافه کنید تا رشته URL (از XML) به یک شی Uri تبدیل شود. در صورت درخواست androidx.core.net.toUri را وارد کنید.

    شما می‌خواهید که شی نهایی Uri از طرح HTTPS استفاده کند، زیرا سروری که تصاویر را از آن می‌کشید به آن طرح نیاز دارد. برای استفاده از طرح HTTPS، buildUpon.scheme("https") را به سازنده toUri اضافه کنید. متد toUri() یک تابع پسوند Kotlin از کتابخانه هسته Android KTX است، بنابراین به نظر می رسد که بخشی از کلاس String است.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
  1. همچنان در داخل let {} ، Glide.with() را فراخوانی کند تا تصویر را از شی Uri در ImageView بارگیری کند. در صورت درخواست، com.bumptech.glide.Glide را وارد کنید.
Glide.with(imgView.context)
       .load(imgUri)
       .into(imgView)

مرحله 4: طرح و قطعات را به روز کنید

اگرچه Glide تصویر را بارگذاری کرده است، هنوز چیزی برای دیدن وجود ندارد. گام بعدی این است که طرح و قطعات را با ImageView به روز کنید تا تصویر نمایش داده شود.

  1. res/layout/gridview_item.xml باز کنید. این فایل منبع طرح بندی است که برای هر آیتم در RecyclerView بعداً در Codelab استفاده خواهید کرد. شما در اینجا به طور موقت از آن برای نشان دادن تنها یک تصویر استفاده می کنید.
  2. در بالای عنصر <ImageView> ، یک عنصر <data> برای اتصال داده اضافه کنید و به کلاس OverviewViewModel متصل شوید:
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
  1. برای استفاده از آداپتور اتصال بارگذاری تصویر جدید، یک ویژگی app:imageUrl به عنصر ImageView اضافه کنید:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. overview/OverviewFragment.kt باز کنید. در متد onCreateView() خطی را که کلاس FragmentOverviewBinding باد می کند و آن را به متغیر binding اختصاص می دهد، نظر دهید. این فقط موقتی است. شما بعداً به آن باز خواهید گشت
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. به جای آن یک خط برای افزایش کلاس GridViewItemBinding اضافه کنید. com.example.android.marsrealestate. databinding.GridViewItemBinding در صورت درخواست.
val binding = GridViewItemBinding.inflate(inflater)
  1. برنامه را اجرا کنید. اکنون باید عکس تصویر اولین MarsProperty را در لیست نتایج ببینید.

مرحله 5: تصاویر بارگیری و خطای ساده را اضافه کنید

Glide می‌تواند تجربه کاربر را با نشان دادن یک تصویر نگهدارنده حین بارگیری تصویر و یک تصویر خطا در صورت عدم بارگیری، به عنوان مثال اگر تصویر گم شده یا خراب باشد، بهبود بخشد. در این مرحله، این قابلیت را به آداپتور صحافی و طرح بندی اضافه می کنید.

  1. res/drawable/ic_broken_image.xml را باز کنید و روی برگه Preview در سمت راست کلیک کنید. برای تصویر خطا، از نماد تصویر شکسته استفاده می‌کنید که در کتابخانه نماد داخلی موجود است. این رسم برداری از ویژگی android:tint برای رنگ کردن آیکون خاکستری استفاده می کند.

  1. res/drawable/loading_animation.xml باز کنید. این drawable یک انیمیشن است که با تگ <animate-rotate> تعریف شده است. انیمیشن یک تصویر قابل ترسیم، loading_img.xml در اطراف نقطه مرکزی می چرخاند. (شما انیمیشن را در پیش نمایش نمی بینید.)

  1. به فایل BindingAdapters.kt برگردید. در متد bindImage() ، فراخوانی را به Glide.with() به روز کنید تا تابع apply() بین load() و into() فراخوانی شود. در صورت درخواست com.bumptech.glide.request.RequestOptions را وارد کنید.

    این کد تصویر بارگیری مکان‌نما را برای استفاده در حین بارگذاری تنظیم می‌کند (قابلیت ترسیم 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)
    }
}
  1. برنامه را اجرا کنید. بسته به سرعت اتصال شبکه خود، ممکن است به طور خلاصه تصویر بارگیری را به عنوان Glide مشاهده کنید و تصویر ویژگی را نمایش دهد. اما شما هنوز آیکون تصویر شکسته را نخواهید دید، حتی اگر شبکه خود را خاموش کنید - این مشکل را در آخرین قسمت از Codelab حل می کنید.

اکنون برنامه شما اطلاعات دارایی را از اینترنت بارگیری می کند. با استفاده از داده‌های اولین مورد فهرست MarsProperty ، یک ویژگی LiveData در مدل view ایجاد کرده‌اید، و از URL تصویر از داده‌های ویژگی برای پر کردن ImageView استفاده کرده‌اید. اما هدف این است که برنامه شما شبکه‌ای از تصاویر را نمایش دهد، بنابراین می‌خواهید از RecyclerView با GridLayoutManager استفاده کنید.

مرحله 1: مدل view را به روز کنید

در حال حاضر مدل view دارای یک _property LiveData است که یک شی MarsProperty در خود نگه می دارد—اولین مورد در لیست پاسخ از سرویس وب. در این مرحله، LiveData را تغییر می دهید تا کل لیست اشیاء MarsProperty را در خود نگه دارد.

  1. overview/OverviewViewModel.kt باز کنید.
  2. متغیر private _property را به _properties تغییر دهید. نوع را به لیستی از اشیاء MarsProperty تغییر دهید.
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. داده های زنده property خارجی را با properties جایگزین کنید. لیست را در اینجا نیز به نوع LiveData اضافه کنید:
 val properties: LiveData<List<MarsProperty>>
        get() = _properties
  1. به سمت پایین به متد 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: چیدمان ها و قطعات را به روز کنید

گام بعدی این است که طرح‌بندی و قطعات برنامه را تغییر دهید تا از نمای بازیافت‌کننده و طرح‌بندی شبکه‌ای استفاده کنید، نه از نمای تک تصویر.

  1. res/layout/gridview_item.xml باز کنید. اتصال داده را از OverviewViewModel به MarsProperty تغییر دهید و نام متغیر را به "property" تغییر دهید.
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. در <ImageView> ، ویژگی app:imageUrl را تغییر دهید تا به URL تصویر در شیء MarsProperty اشاره شود:
app:imageUrl="@{property.imgSrcUrl}"
  1. overview/OverviewFragment.kt باز کنید. در onCreateview() خطی را که FragmentOverviewBinding افزایش می‌دهد از نظر خارج کنید. خطی را که GridViewBinding افزایش می‌دهد، حذف یا نظر دهید. این تغییرات تغییرات موقتی را که در آخرین کار انجام داده اید، خنثی می کند.
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)
  1. res/layout/fragment_overview.xml باز کنید. کل عنصر <TextView> را حذف کنید.
  2. به جای آن این عنصر <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 متصل می کنید.

  1. overview/PhotoGridAdapter.kt باز کنید.
  2. کلاس PhotoGridAdapter با پارامترهای سازنده نشان داده شده در زیر ایجاد کنید. کلاس PhotoGridAdapter ListAdapter گسترش می دهد که سازنده آن به نوع آیتم لیست، نگهدارنده view و اجرای DiffUtil.ItemCallback نیاز دارد.

    در صورت درخواست، کلاس‌های androidx.recyclerview.widget.ListAdapter و com.example.android.marsrealestate.network.MarsProperty را وارد کنید. در مراحل زیر، سایر قسمت های گمشده این سازنده که خطا تولید می کنند را پیاده سازی می کنید.
class PhotoGridAdapter : ListAdapter<MarsProperty,
        PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {

}
  1. روی هر نقطه از کلاس 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") 
}
  1. در پایان تعریف کلاس PhotoGridAdapter ، پس از متدهایی که به تازگی اضافه کرده اید، یک تعریف شی همراه برای DiffCallback اضافه کنید، همانطور که در زیر نشان داده شده است.

    در صورت درخواست androidx.recyclerview.widget.DiffUtil را وارد کنید.

    شی DiffCallback DiffUtil.ItemCallback را با نوع شی ای که می خواهید مقایسه کنید گسترش می دهد - MarsProperty .
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. 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") }
  1. برای متد areItemsTheSame() ، TODO را حذف کنید. از عملگر برابری ارجاعی کاتلین ( === ) استفاده کنید، که اگر ارجاعات شیء oldItem و newItem یکسان باشد، true برمی گرداند.
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. برای areContentsTheSame() از عملگر برابری استاندارد فقط روی شناسه oldItem و newItem استفاده کنید.
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. هنوز در داخل کلاس PhotoGridAdapter ، در زیر شیء همراه، یک تعریف کلاس داخلی برای MarsPropertyViewHolder اضافه کنید، که RecyclerView.ViewHolder گسترش می دهد.

    androidx.recyclerview.widget.RecyclerView و com.example.android.marsrealestate.databinding.GridViewItemBinding را در صورت درخواست وارد کنید.

    برای اتصال MarsProperty به چیدمان به متغیر GridViewItemBinding نیاز دارید، بنابراین متغیر را به MarsPropertyViewHolder منتقل کنید. از آنجا که کلاس ViewHolder پایه به یک view در سازنده خود نیاز دارد، شما به آن نمای ریشه binding ارسال می کنید.
class MarsPropertyViewHolder(private var binding: 
                   GridViewItemBinding):
       RecyclerView.ViewHolder(binding.root) {

}
  1. در MarsPropertyViewHolder ، یک متد bind() ایجاد کنید که یک شی MarsProperty را به عنوان آرگومان می گیرد و binding.property برای آن شی تنظیم می کند. پس از تنظیم ویژگی، executePendingBindings() را فراخوانی کنید، که باعث می شود به روز رسانی بلافاصله اجرا شود.
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. در onCreateViewHolder() TODO را حذف کرده و خط زیر را اضافه کنید. در صورت درخواست android.view.LayoutInflater را وارد کنید.

    متد onCreateViewHolder() باید یک MarsPropertyViewHolder جدید را برگرداند که با باد کردن GridViewItemBinding و استفاده از LayoutInflater از زمینه ViewGroup والدین شما ایجاد شده است.
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. در متد onBindViewHolder() TODO را حذف کرده و خطوط زیر را اضافه کنید. در اینجا شما getItem() را فراخوانی می کنید تا شی MarsProperty مرتبط با موقعیت فعلی RecyclerView را دریافت کنید و سپس آن ویژگی را به متد bind() در MarsPropertyViewHolder منتقل کنید.
val marsProperty = getItem(position)
holder.bind(marsProperty)

مرحله 4: آداپتور صحافی را اضافه کنید و قطعات را وصل کنید

در نهایت، از BindingAdapter برای مقداردهی اولیه PhotoGridAdapter با لیست اشیاء MarsProperty استفاده کنید. استفاده از BindingAdapter برای تنظیم داده های RecyclerView باعث می شود که اتصال داده ها به طور خودکار LiveData برای لیست اشیاء MarsProperty مشاهده کند. سپس با تغییر لیست MarsProperty ، آداپتور binding به طور خودکار فراخوانی می شود.

  1. BindingAdapters.kt را باز کنید.
  2. در انتهای فایل، یک متد bindRecyclerView() اضافه کنید که یک RecyclerView و لیستی از اشیاء MarsProperty را به عنوان آرگومان می گیرد. آن روش را با @BindingAdapter حاشیه نویسی کنید.

    androidx.recyclerview.widget.RecyclerView و com.example.android.marsrealestate.network.MarsProperty را در صورت درخواست وارد کنید.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. در تابع bindRecyclerView() recyclerView.adapter را به PhotoGridAdapter فرستاده و با داده ها adapter.submitList() فراخوانی کنید. این به RecyclerView می گوید که یک لیست جدید در دسترس است.

در صورت درخواست، com.example.android.marsrealestate.overview.PhotoGridAdapter را وارد کنید.

val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
  1. res/layout/fragment_overview.xml باز کنید. ویژگی app:listData را به عنصر RecyclerView اضافه کنید و با استفاده از data binding آن را روی viewmodel.properties تنظیم کنید.
app:listData="@{viewModel.properties}"
  1. overview/OverviewFragment.kt باز کنید. در onCreateView() ، درست قبل از فراخوانی setHasOptionsMenu() ، آداپتور RecyclerView را در binding.photosGrid به یک شی PhotoGridAdapter راه اندازی کنید.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. برنامه را اجرا کنید. شما باید شبکه ای از تصاویر MarsProperty را ببینید. همانطور که برای دیدن تصاویر جدید پیمایش می کنید، برنامه قبل از نمایش خود تصویر، نماد پیشرفت بارگیری را نشان می دهد. اگر حالت هواپیما را روشن کنید، تصاویری که هنوز بارگذاری نشده اند به عنوان نمادهای تصویر شکسته ظاهر می شوند.

برنامه MarsRealEstate زمانی که تصویری قابل واکشی نیست، نماد تصویر شکسته را نمایش می دهد. اما وقتی شبکه وجود ندارد، برنامه یک صفحه خالی را نشان می دهد.

این یک تجربه کاربری عالی نیست. در این کار، مدیریت خطای اولیه را اضافه می‌کنید تا به کاربر ایده بهتری از آنچه اتفاق می‌افتد ارائه دهید. اگر اینترنت در دسترس نباشد، برنامه نماد خطای اتصال را نشان می دهد. در حالی که برنامه در حال دریافت لیست MarsProperty است، برنامه انیمیشن بارگیری را نشان می دهد.

مرحله 1: اضافه کردن وضعیت به مدل view

برای شروع، شما یک LiveData در مدل view ایجاد می کنید تا وضعیت درخواست وب را نشان دهد. سه حالت وجود دارد که باید در نظر گرفت: بارگیری، موفقیت و شکست. حالت بارگیری زمانی اتفاق می‌افتد که منتظر داده‌های موجود در فراخوانی await() هستید.

  1. overview/OverviewViewModel.kt باز کنید. در بالای فایل (بعد از وارد کردن، قبل از تعریف کلاس)، یک enum اضافه کنید تا تمام وضعیت های موجود را نشان دهد:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. تعاریف داده‌های زنده داخلی و خارجی _response در کلاس OverviewViewModel را به _status تغییر نام دهید. از آنجایی که قبلاً در این کد لبه از _properties LiveData پشتیبانی کردید، پاسخ سرویس وب کامل استفاده نشده است. برای پیگیری وضعیت فعلی به LiveData در اینجا نیاز دارید، بنابراین می توانید نام متغیرهای موجود را تغییر دهید.

همچنین انواع را از String به MarsApiStatus.

private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus>
   get() = _status
  1. به سمت پایین به متد getMarsRealEstateProperties() بروید و _response به _status را در اینجا نیز به روز کنید. رشته "Success" را به حالت MarsApiStatus.DONE و رشته "Failure" را به MarsApiStatus.ERROR تغییر دهید.
  2. یک وضعیت MarsApiStatus.LOADING را به بالای بلوک try {} ، قبل از تماس to 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
}
  1. پس از حالت خطا در بلوک catch {} ، _properties LiveData روی یک لیست خالی تنظیم کنید. این RecyclerView را پاک می کند.
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _properties.value = ArrayList()
}

مرحله 2: یک آداپتور اتصال برای وضعیت ImageView اضافه کنید

اکنون شما یک وضعیت در مدل view دارید، اما این فقط مجموعه ای از حالت ها است. چگونه آن را در خود برنامه نشان می دهید؟ در این مرحله، از ImageView ، متصل به داده‌های اتصال، برای نمایش نمادها برای حالت‌های بارگیری و خطا استفاده می‌کنید. هنگامی که برنامه در حالت بارگیری یا خطا است، ImageView باید قابل مشاهده باشد. وقتی بارگیری برنامه تمام شد، ImageView باید نامرئی باشد.

  1. BindingAdapters.kt را باز کنید. یک آداپتور اتصال جدید به نام bindStatus() اضافه کنید که یک ImageView و یک مقدار MarsApiStatus را به عنوان آرگومان می گیرد. com.example.android.marsrealestate.overview.MarsApiStatus را در صورت درخواست وارد کنید.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. برای جابجایی بین وضعیت‌های مختلف، یک when {} در متد bindStatus() اضافه کنید.
when (status) {

}
  1. در داخل when {} ، یک مورد برای وضعیت بارگیری اضافه کنید ( MarsApiStatus.LOADING ). برای این حالت، ImageView روی قابل مشاهده تنظیم کنید و انیمیشن بارگیری را به آن اختصاص دهید. این همان انیمیشن قابل ترسیمی است که برای Glide در کار قبلی استفاده کردید. در صورت درخواست android.view.View را وارد کنید.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. یک مورد برای حالت خطا اضافه کنید که MarsApiStatus.ERROR است. مشابه کاری که برای حالت LOADING انجام دادید، وضعیت ImageView را روی قابل مشاهده تنظیم کنید و از ترسیم خطای اتصال مجدد استفاده کنید.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. یک مورد برای حالت انجام شده اضافه کنید که MarsApiStatus.DONE است. در اینجا شما یک پاسخ موفق دارید، بنابراین نمایان بودن وضعیت ImageView را خاموش کنید تا آن را پنهان کنید.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

مرحله 3: وضعیت ImageView را به طرح اضافه کنید

  1. res/layout/fragment_overview.xml باز کنید. در زیر عنصر RecyclerView ، داخل ConstraintLayout ، ImageView را که در زیر نشان داده شده است اضافه کنید.

    این ImageView دارای محدودیت های مشابه با RecyclerView است. با این حال، عرض و ارتفاع از wrap_content برای وسط تصویر استفاده می‌کنند تا اینکه تصویر را کشیده و نمای را پر کند. همچنین به صفت app:marsApiStatus توجه کنید که وقتی ویژگی وضعیت در مدل view تغییر می‌کند، نمای 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}" />
  1. برای شبیه سازی اتصال شبکه از دست رفته، حالت هواپیما را در شبیه ساز یا دستگاه خود روشن کنید. برنامه را کامپایل و اجرا کنید و متوجه شوید که تصویر خطا ظاهر می شود:

  1. برای بستن برنامه روی دکمه برگشت ضربه بزنید و حالت هواپیما را خاموش کنید. برای بازگرداندن برنامه از صفحه نمایش اخیر استفاده کنید. بسته به سرعت اتصال شبکه شما، ممکن است زمانی که برنامه قبل از شروع بارگیری تصاویر، سرویس وب را درخواست می کند، یک چرخش بارگذاری بسیار مختصر مشاهده کنید.

پروژه اندروید استودیو: MarsRealEstateGrid

  • برای ساده‌سازی فرآیند مدیریت تصاویر، از کتابخانه Glide برای دانلود، بافر، رمزگشایی و ذخیره‌سازی تصاویر در برنامه خود استفاده کنید.
  • Glide برای بارگذاری یک تصویر از اینترنت به دو چیز نیاز دارد: URL یک تصویر، و یک شی ImageView برای قرار دادن تصویر. برای مشخص کردن این گزینه‌ها، از متدهای load() و into() با Glide استفاده کنید.
  • آداپتورهای صحافی روش‌های توسعه‌ای هستند که بین یک view و داده‌های محدود آن view قرار می‌گیرند. آداپتورهای اتصال رفتار سفارشی را هنگام تغییر داده‌ها ارائه می‌کنند، به عنوان مثال، برای فراخوانی Glide برای بارگیری تصویر از URL در ImageView .
  • آداپتورهای صحافی روش‌های افزودنی هستند که با حاشیه‌نویسی @BindingAdapter حاشیه‌نویسی می‌شوند.
  • برای افزودن گزینه به درخواست Glide، از متد apply() استفاده کنید. به عنوان مثال، از apply() با placeholder() برای تعیین یک drawable در حال بارگذاری و از apply() با error() برای تعیین خطایی قابل ترسیم استفاده کنید.
  • برای تولید شبکه ای از تصاویر، از RecyclerView با GridLayoutManager استفاده کنید.
  • برای به‌روزرسانی لیست ویژگی‌ها هنگام تغییر، از یک آداپتور اتصال بین RecyclerView و طرح‌بندی استفاده کنید.

دوره بی ادبی:

مستندات توسعه دهنده اندروید:

دیگر:

این بخش، تکالیف احتمالی را برای دانش‌آموزانی که در این آزمایشگاه کد به عنوان بخشی از دوره‌ای که توسط یک مربی هدایت می‌شود، کار می‌کنند، فهرست می‌کند. این وظیفه مربی است که موارد زیر را انجام دهد:

  • در صورت نیاز تکالیف را تعیین کنید.
  • نحوه ارسال تکالیف را با دانش آموزان در میان بگذارید.
  • تکالیف را نمره دهید.

مربیان می‌توانند از این پیشنهادات به اندازه‌ای که می‌خواهند استفاده کنند، و باید با خیال راحت هر تکلیف دیگری را که فکر می‌کنند مناسب است، محول کنند.

اگر به تنهایی از طریق این کدها کار می کنید، از این تکالیف برای آزمایش دانش خود استفاده کنید.

به این سوالات پاسخ دهید

سوال 1

از کدام روش Glide برای نشان دادن ImageView که حاوی تصویر بارگذاری شده است استفاده می کنید؟

into()

with()

imageview()

apply()

سوال 2

چگونه می‌توانید یک تصویر مکان‌نما را مشخص کنید تا هنگام بارگیری Glide نشان داده شود؟

▢ از متد into() با یک drawable استفاده کنید.

▢ از RequestOptions() استفاده کنید و متد placeholder() را با یک drawable فراخوانی کنید.

▢ ویژگی Glide.placeholder را به یک drawable اختصاص دهید.

▢ از RequestOptions() استفاده کنید و متد loadingImage() با یک drawable فراخوانی کنید.

سوال 3

چگونه نشان می دهید که یک روش یک آداپتور اتصال است؟

▢ متد setBindingAdapter() را در LiveData فراخوانی کنید.

▢ متد را در یک فایل Kotlin به نام BindingAdapters.kt قرار دهید.

▢ از ویژگی android:adapter در طرح XML استفاده کنید.

▢ روش را با @BindingAdapter حاشیه نویسی کنید.

درس بعدی را شروع کنید: 8.3 فیلتر کردن و نمایش جزئیات با داده های اینترنتی

برای پیوند به سایر کدهای این دوره، به صفحه فرود کد لبه‌های کد پایه Android Kotlin Fundamentals مراجعه کنید.