Android Kotlin Fundamentals 07.1: RecyclerView Fundamentals

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

مقدمه

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

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

باید با:

  • ساخت یک رابط کاربری اولیه (UI) با استفاده از یک فعالیت، قطعات و نماها.
  • پیمایش بین قطعات و استفاده از safeArgs برای انتقال داده ها بین قطعات.
  • با استفاده از مدل‌های view، کارخانه‌های مدل، تبدیل‌ها و LiveData و ناظران آن‌ها را مشاهده کنید.
  • ایجاد پایگاه داده Room ، ایجاد DAO و تعریف موجودیت ها.
  • استفاده از کوروتین ها برای وظایف پایگاه داده و سایر کارهای طولانی مدت.

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

  • نحوه استفاده از RecyclerView با Adapter و ViewHolder برای نمایش لیستی از موارد.

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

  • برنامه TrackMySleepQuality را از درس قبلی تغییر دهید تا از RecyclerView برای نمایش داده های کیفیت خواب استفاده کنید.

در این کد لبه، شما بخش RecyclerView یک برنامه را می سازید که کیفیت خواب را ردیابی می کند. این برنامه از پایگاه داده Room برای ذخیره داده های خواب در طول زمان استفاده می کند.

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

اولین صفحه نمایش داده شده در سمت چپ دارای دکمه هایی برای شروع و توقف ردیابی است. این صفحه همچنین تمام داده های خواب کاربر را نشان می دهد. دکمه Clear تمام داده هایی را که برنامه برای کاربر جمع آوری کرده است برای همیشه حذف می کند. صفحه دوم که در سمت راست نشان داده شده است، برای انتخاب رتبه بندی کیفیت خواب است.

این برنامه از معماری ساده شده با کنترلر رابط کاربری، ViewModel و LiveData استفاده می کند. این برنامه همچنین از پایگاه داده Room استفاده می کند تا داده های خواب را پایدار کند.

لیست شب های خواب نمایش داده شده در صفحه اول کاربردی است، اما زیبا نیست. این برنامه از یک قالب‌بندی پیچیده برای ایجاد رشته‌های متنی برای نمای متن و اعداد برای کیفیت استفاده می‌کند. همچنین، این طرح مقیاس ندارد. پس از رفع تمام این مشکلات در این کد لبه، برنامه نهایی دارای همان عملکرد است و صفحه اصلی به شکل زیر است:

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

برای پشتیبانی از همه این موارد استفاده، اندروید ویجت RecyclerView را ارائه می کند.

بزرگترین مزیت RecyclerView این است که برای لیست های بزرگ بسیار کارآمد است:

  • به طور پیش فرض، RecyclerView فقط برای پردازش یا ترسیم مواردی که در حال حاضر روی صفحه قابل مشاهده هستند کار می کند. به عنوان مثال، اگر لیست شما دارای هزار عنصر است اما تنها 10 عنصر قابل مشاهده است، RecyclerView فقط به اندازه کافی کار می کند تا 10 مورد را روی صفحه بکشد. هنگامی که کاربر پیمایش می کند، RecyclerView متوجه می شود که چه آیتم های جدیدی باید روی صفحه باشند و به اندازه کافی برای نمایش آن موارد کار می کند.
  • هنگامی که یک مورد از صفحه خارج می شود، نماهای مورد بازیافت می شود. این بدان معناست که مورد با محتوای جدیدی پر شده است که روی صفحه نمایش می رود. این رفتار RecyclerView باعث صرفه جویی زیادی در زمان پردازش می شود و به پیمایش روان لیست ها کمک می کند.
  • هنگامی که یک مورد تغییر می کند، به جای ترسیم مجدد کل لیست، RecyclerView می تواند آن یک مورد را به روز کند. این یک افزایش کارایی بزرگ در هنگام نمایش لیست موارد پیچیده است!

در دنباله نشان داده شده در زیر، می بینید که یک نما با داده پر شده است، ABC . پس از اینکه نما از صفحه خارج شد، RecyclerView از نمای برای داده‌های جدید، XYZ استفاده مجدد می‌کند.

الگوی آداپتور

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

الگوی آداپتور در مهندسی نرم افزار به یک شی کمک می کند تا با API دیگری کار کند. RecyclerView از یک آداپتور برای تبدیل داده های برنامه به چیزی که RecyclerView می تواند نمایش دهد، بدون تغییر نحوه ذخیره و پردازش داده ها توسط برنامه استفاده می کند. برای برنامه ردیاب خواب، آداپتوری می‌سازید که داده‌های پایگاه داده Room را به چیزی که RecyclerView می‌داند چگونه نمایش دهد، بدون تغییر ViewModel تطبیق می‌دهد.

پیاده سازی RecyclerView

برای نمایش داده های خود در RecyclerView ، به بخش های زیر نیاز دارید:

  • داده برای نمایش
  • یک نمونه RecyclerView که در فایل طرح بندی شما تعریف شده است تا به عنوان محفظه نماها عمل کند.
  • طرحی برای یک مورد از داده ها.
    اگر همه آیتم های لیست یکسان به نظر می رسند، می توانید از یک طرح برای همه آنها استفاده کنید، اما این اجباری نیست. طرح بندی آیتم باید جدا از طرح بندی قطعه ایجاد شود، به طوری که بتوان یک نمای آیتم را در هر زمان ایجاد کرد و با داده ها پر کرد.
  • یک مدیر چیدمان
    مدیر طرح‌بندی، سازمان‌دهی (طرح‌بندی) اجزای رابط کاربری را در یک نما مدیریت می‌کند.
  • نگهدارنده دید
    نگهدارنده view کلاس 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 را در تب Design در اندروید استودیو باز کنید.
  4. در قسمت Component Tree ، ScrollView را حذف کنید. این عمل همچنین TextView را که در داخل ScrollView قرار دارد حذف می کند.
  5. در صفحه پالت ، در لیست انواع مؤلفه در سمت چپ حرکت کنید تا Containers را پیدا کنید، سپس آن را انتخاب کنید.
  6. یک RecyclerView را از قسمت پالت به قسمت Component Tree بکشید. RecyclerView را در داخل ConstraintLayout قرار دهید.

  1. اگر گفتگوی باز شد که از شما می‌پرسد آیا می‌خواهید یک وابستگی اضافه کنید، روی OK کلیک کنید تا به 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 را به دکمه Start ، پایین را به دکمه Clear و هر طرف را به والد محدود کنید. با استفاده از کد زیر، عرض و ارتفاع طرح‌بندی را در Layout Editor یا XML روی ۰ dp تنظیم کنید:
    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. یک layout manager به RecyclerView XML اضافه کنید. هر RecyclerView به یک مدیر طرح نیاز دارد که به او بگوید چگونه موارد را در لیست قرار دهد. Android یک LinearLayoutManager ارائه می‌کند که به‌طور پیش‌فرض موارد را در یک لیست عمودی از ردیف‌های عرض کامل قرار می‌دهد.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. به تب Design بروید و توجه کنید که محدودیت های اضافه شده باعث شده است که RecyclerView برای پر کردن فضای موجود گسترش یابد.

مرحله 2: چیدمان آیتم لیست و نگهدارنده نمای متن را ایجاد کنید

RecyclerView فقط یک ظرف است. در این مرحله، چیدمان و زیرساختی را برای مواردی که در داخل RecyclerView نمایش داده می شوند ایجاد می کنید.

برای رسیدن به یک RecyclerView در سریع ترین زمان ممکن، ابتدا از یک آیتم فهرست ساده استفاده می کنید که کیفیت خواب را فقط به صورت عدد نشان می دهد. برای این کار، به یک view نگهدار، TextItemViewHolder نیاز دارید. همچنین برای داده ها به یک نمای، یک TextView نیاز دارید. (در مرحله بعد، در مورد نگهدارنده‌های view و نحوه چیدمان تمام داده‌های خواب بیشتر می‌آموزید.)

  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 می رود زیرا این نگهدارنده view موقت است و بعداً آن را جایگزین می کنید.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. اگر از شما خواسته شد، android.widget.TextView و androidx.recyclerview.widget.RecyclerView وارد کنید.

مرحله 3: SleepNightAdapter را ایجاد کنید

وظیفه اصلی در پیاده سازی RecyclerView ایجاد آداپتور است. شما یک نگهدارنده نمای ساده برای نمای آیتم و یک طرح برای هر آیتم دارید. اکنون می توانید یک آداپتور ایجاد کنید. آداپتور یک نگهدارنده view ایجاد می کند و آن را با داده هایی برای نمایش RecyclerView پر می کند.

  1. در بسته sleeptracker ، یک کلاس Kotlin جدید به نام SleepNightAdapter کنید.
  2. کلاس SleepNightAdapter را گسترش دهید RecyclerView.Adapter . این کلاس SleepNightAdapter نامیده می شود زیرا یک شی SleepNight را با چیزی که RecyclerView می تواند استفاده کند تطبیق می دهد. آداپتور باید بداند که از چه نگهدارنده view استفاده کند، بنابراین 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() دو آرگومان می گیرد: یک view holder و یک موقعیت داده برای اتصال. برای این برنامه، دارنده TextItemViewHolder است و موقعیت موقعیت در لیست است.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. در داخل onBindViewHolder() یک متغیر برای یک آیتم در یک موقعیت مشخص در داده ایجاد کنید.
 val item = data[position]
  1. ViewHolder که ایجاد کردید دارای خاصیتی به نام textView است. در داخل onBindViewHolder() ، text textView را روی عدد با کیفیت خواب تنظیم کنید. این کد فقط لیستی از اعداد را نمایش می دهد، اما این مثال ساده به شما امکان می دهد ببینید که چگونه آداپتور داده ها را به نگهدارنده view و روی صفحه نمایش می برد.
holder.textView.text = item.sleepQuality.toString()
  1. در SleepNightAdapter ، onCreateViewHolder() را نادیده گرفته و پیاده سازی کنید، که زمانی فراخوانی می شود که RecyclerView برای نمایش یک آیتم به یک view نگهدارنده نیاز دارد.

    این تابع دو پارامتر می گیرد و یک ViewHolder برمی گرداند. پارامتر parent ، که گروه view است که نگهدارنده view را نگه می دارد، همیشه RecyclerView است. پارامتر viewType زمانی استفاده می شود که چندین نما در یک RecyclerView وجود داشته باشد. برای مثال، اگر فهرستی از نماهای متن، یک تصویر و یک ویدیو را در همان RecyclerView قرار دهید، تابع onCreateViewHolder() باید بداند که از چه نوع نما استفاده می‌کند.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. در onCreateViewHolder() یک نمونه از LayoutInflater ایجاد کنید.

    بادکننده layout می داند که چگونه از طرح بندی های XML نماها ایجاد کند. context حاوی اطلاعاتی در مورد نحوه درست کردن نما است. در یک آداپتور برای نمای Recycler، شما همیشه در زمینه گروه نمای parent ، که RecyclerView است، عبور می کنید.
val layoutInflater = LayoutInflater.from(parent.context)
  1. در onCreateViewHolder() با درخواست از layoutinflater آن را ایجاد view .

    از طرح XML برای view و گروه view parent برای view عبور کنید. آرگومان سوم، بولی، attachToRoot است. این آرگومان باید false ، زیرا RecyclerView این مورد را به سلسله مراتب view برای شما اضافه می کند.
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: به RecyclerView در مورد آداپتور بگویید

RecyclerView باید در مورد آداپتور مورد استفاده برای دریافت دارندگان view اطلاعات داشته باشد.

  1. SleepTrackerFragment.kt را باز کنید.
  2. در onCreateview() یک آداپتور ایجاد کنید. این کد را بعد از ایجاد مدل ViewModel و قبل از عبارت return قرار دهید.
val adapter = SleepNightAdapter()
  1. adapter با RecyclerView کنید.
binding.sleepList.adapter = adapter
  1. پروژه خود را تمیز کرده و دوباره بسازید تا شیء binding را به روز کنید.

    اگر همچنان خطاهایی در اطراف binding.sleepList یا binding.FragmentSleepTrackerBinding مشاهده کردید، حافظه پنهان را باطل کنید و دوباره راه اندازی کنید. ( File > Invalidate Caches / Restart را انتخاب کنید.)

    اگر اکنون برنامه را اجرا کنید، هیچ خطایی وجود ندارد، اما با ضربه زدن روی Start و سپس Stop هیچ داده ای نمایش داده نمی شود.

مرحله 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. در داخل مشاهده‌گر، هر زمان که یک مقدار غیر تهی دریافت کردید (برای nights )، مقدار را به data آداپتور اختصاص دهید. این کد تکمیل شده برای مشاهده گر و تنظیم داده ها است:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. کد خود را بسازید و اجرا کنید.

    اگر آداپتور شما کار می کند، اعداد کیفیت خواب را به صورت فهرستی مشاهده خواهید کرد. بعد از اینکه روی Start ضربه بزنید، اسکرین شات سمت چپ -1 را نشان می‌دهد. تصویر صفحه سمت راست، پس از ضربه زدن روی Stop و انتخاب رتبه‌بندی کیفیت، عدد به‌روزرسانی‌شده کیفیت خواب را نشان می‌دهد.

مرحله 6: نحوه بازیافت دارندگان view را بررسی کنید

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

از آنجایی که این نگهدارنده‌های view بازیافت می‌شوند، مطمئن شوید که onBindViewHolder() هر گونه سفارشی‌سازی‌هایی را که آیتم‌های قبلی ممکن است روی یک view دارنده تنظیم کرده باشند، تنظیم یا بازنشانی کند.

به عنوان مثال، می‌توانید رنگ متن را در نگهدارنده‌های نمایشی که دارای رتبه‌بندی کیفیت کمتر یا مساوی 1 هستند و نشان‌دهنده خواب ضعیف هستند، روی قرمز تنظیم کنید.

  1. در کلاس SleepNightAdapter ، کد زیر را در انتهای onBindViewHolder() اضافه کنید.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. برنامه را اجرا کنید.
  2. مقداری داده با کیفیت خواب پایین اضافه کنید و عدد قرمز است.
  3. رتبه‌بندی‌های بالا را برای کیفیت خواب اضافه کنید تا زمانی که یک عدد بالای قرمز روی صفحه مشاهده کنید.

    همانطور که RecyclerView از نگهدارنده‌های view استفاده مجدد می‌کند، در نهایت از یکی از دارندگان نمای قرمز برای رتبه‌بندی با کیفیت بالا استفاده مجدد می‌کند. امتیاز بالا به اشتباه به رنگ قرمز نمایش داده می شود.

  1. برای رفع این مشکل، یک عبارت else اضافه کنید تا اگر کیفیت آن کمتر یا مساوی یک نباشد، رنگ را روی مشکی تنظیم کنید.

    با هر دو شرط صریح، دارنده view از رنگ متن صحیح برای هر مورد استفاده می کند.
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 دارنده view انجام دهد. RecyclerView از itemView زمانی استفاده می‌کند که یک آیتم را برای نمایش روی صفحه‌نمایش متصل می‌کند، هنگام ترسیم تزئینات اطراف یک نمای مانند یک حاشیه، و برای پیاده‌سازی قابلیت دسترسی.

مرحله 1: طرح بندی مورد را ایجاد کنید

در این مرحله شما فایل layout را برای یک آیتم ایجاد می کنید. این طرح شامل یک 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. به تب Design در Android Studio بروید. در نمای طراحی، چیدمان شما مانند عکس صفحه سمت چپ زیر است. در نمای طرح، مانند تصویر سمت راست به نظر می رسد.

مرحله 2: ViewHolder را ایجاد کنید

  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 برای این view است.
val res = holder.itemView.context.resources
  1. متن نمای متن sleepLength را روی مدت زمان تنظیم کنید. کد زیر را کپی کنید، که تابع قالب بندی ارائه شده با کد شروع را فراخوانی می کند.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. این یک خطا می دهد، زیرا convertDurationToFormatted() باید تعریف شود. Util.kt را باز کنید و کد و واردات مربوط به آن را از نظر خارج کنید. ( کد > نظر با نظرات خط را انتخاب کنید.)
  2. به onBindViewHolder() ، از convertNumericQualityToString 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 را پیاده سازی کنید، و آنها را برای نمایش لیستی با یک Adapter RecyclerView کنار هم قرار دادید.

کد شما تاکنون روند ایجاد آداپتور و نگهدارنده نمایش را نشان می دهد. با این حال، شما می توانید این کد را بهبود بخشید. کد برای نمایش و کد برای مدیریت دارندگان view با هم مخلوط شده اند و onBindViewHolder() جزئیات مربوط به نحوه به روز رسانی ViewHolder را می داند.

در یک برنامه تولیدی، ممکن است چندین نگهدارنده نمای، آداپتورهای پیچیده‌تر و چندین توسعه‌دهنده در حال تغییر داشته باشید. شما باید کد خود را طوری ساختار دهید که هر چیزی که مربوط به یک ویو است فقط در نگهدارنده view باشد.

مرحله 1: Refactor onBindViewHolder()

در این مرحله، کد را تغییر می‌دهید و تمام عملکردهای نگهدارنده view را به ViewHolder . هدف از این refactoring تغییر ظاهر برنامه برای کاربر نیست، بلکه کار کردن روی کد را برای توسعه‌دهندگان آسان‌تر و ایمن‌تر می‌کند. خوشبختانه اندروید استودیو ابزارهایی برای کمک به شما دارد.

  1. در SleepNightAdapter ، در onBindViewHolder() همه چیز را انتخاب کنید به جز عبارتی که item متغیر را اعلام کنید.
  2. کلیک راست کنید، سپس Refactor > Extract > Function را انتخاب کنید.
  3. تابع bind را نامگذاری کنید و پارامترهای پیشنهادی را بپذیرید. روی OK کلیک کنید.

    تابع 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 در مک) را فشار دهید تا منوی قصد باز شود. برای تبدیل این تابع به یک تابع پسوندی که دارای امضای زیر است، Convert parametr to گیرنده را انتخاب کنید:
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 Refactor کنید

onCreateViewHolder() در آداپتور در حال حاضر نمای را از منبع چیدمان برای ViewHolder می دهد. با این حال، تورم ربطی به آداپتور ندارد، و همه چیز به ViewHolder مربوط می شود. تورم باید در ViewHolder اتفاق بیفتد.

  1. در onCreateViewHolder() تمام کدهای موجود در بدنه تابع را انتخاب کنید.
  2. کلیک راست کنید، سپس Refactor > Extract > Function را انتخاب کنید.
  3. تابع را from نامگذاری کنید و پارامترهای پیشنهادی را بپذیرید. روی OK کلیک کنید.
  4. مکان نما را روی نام تابع from . Alt+Enter ( Option+Enter در مک) را فشار دهید تا منوی قصد باز شود.
  5. انتقال به شی همراه را انتخاب کنید. تابع from() باید در یک شیء همراه باشد تا بتوان آن را در کلاس ViewHolder کرد نه در یک نمونه ViewHolder .
  6. شیء companion را به کلاس ViewHolder کنید.
  7. ساخت from() public.
  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 جدید را برمی گرداند، دیگر دلیلی وجود ندارد که کسی سازنده ViewHolder را فراخوانی کند.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. برنامه را اجرا کنید. برنامه شما باید مانند قبل ساخته و اجرا شود که نتیجه مطلوب پس از refactoring است.

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

  • نمایش لیست یا شبکه ای از داده ها یکی از رایج ترین کارهای رابط کاربری در اندروید است. RecyclerView طراحی شده است تا حتی در هنگام نمایش لیست های بسیار بزرگ نیز کارآمد باشد.
  • RecyclerView فقط کارهای لازم برای پردازش یا ترسیم مواردی را انجام می دهد که در حال حاضر روی صفحه قابل مشاهده هستند.
  • هنگامی که یک مورد از صفحه خارج می شود، نمای آن بازیافت می شود. این بدان معناست که مورد با محتوای جدیدی پر شده است که روی صفحه نمایش می رود.
  • الگوی آداپتور در مهندسی نرم افزار به یک شی کمک می کند تا با یک API دیگر کار کند. RecyclerView از یک آداپتور برای تبدیل داده های برنامه به چیزی که می تواند نمایش دهد، بدون نیاز به تغییر نحوه ذخیره و پردازش داده ها توسط برنامه استفاده می کند.

برای نمایش داده های خود در RecyclerView ، به بخش های زیر نیاز دارید:

  • RecyclerView
    برای ایجاد یک نمونه از RecyclerView ، عنصر <RecyclerView> را در فایل طرح بندی تعریف کنید.
  • LayoutManager
    یک RecyclerView از LayoutManager برای سازماندهی طرح بندی آیتم ها در RecyclerView استفاده می کند، مانند چیدمان آنها در یک شبکه یا در یک لیست خطی.

    در <RecyclerView> در فایل layout، ویژگی app:layoutManager را روی مدیر طرح (مانند LinearLayoutManager یا GridLayoutManager ) تنظیم کنید.

    همچنین می توانید LayoutManager را برای یک RecyclerView به صورت برنامه نویسی تنظیم کنید. (این تکنیک در کدهای بعدی پوشش داده شده است.)
  • چیدمان برای هر مورد
    یک طرح بندی برای یک مورد از داده ها در یک فایل طرح بندی XML ایجاد کنید.
  • آداپتور
    آداپتوری ایجاد کنید که داده ها و نحوه نمایش آنها را در ViewHolder می کند. آداپتور را با RecyclerView کنید.

    هنگامی که RecyclerView اجرا می شود، از آداپتور برای نحوه نمایش داده ها بر روی صفحه استفاده می کند.

    آداپتور از شما می خواهد که روش های زیر را پیاده سازی کنید:
    getItemCount() برای برگرداندن تعداد آیتم ها.
    onCreateViewHolder() برای بازگرداندن ViewHolder برای یک آیتم در لیست.
    onBindViewHolder() برای تطبیق داده ها با نماهای یک آیتم در لیست.

  • ViewHolder
    یک ViewHolder حاوی اطلاعات نمای برای نمایش یک مورد از طرح بندی مورد است.
  • onBindViewHolder() در آداپتور داده ها را با view ها تطبیق می دهد. شما همیشه این روش را نادیده می گیرید. به طور معمول، onBindViewHolder() طرح بندی یک آیتم را افزایش می دهد و داده ها را در view ها در طرح قرار می دهد.
  • از آنجایی که RecyclerView چیزی در مورد داده ها نمی داند، Adapter باید زمانی که داده ها تغییر می کند به RecyclerView اطلاع دهد. از notifyDataSetChanged() برای اطلاع دادن به Adapter مبنی بر تغییر داده ها استفاده کنید.

دوره بی ادبی:

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

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

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

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

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

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

سوال 1

RecyclerView چگونه موارد را نمایش می دهد؟ همه موارد کاربردی را انتخاب کنید.

▢ موارد را در یک لیست یا یک شبکه نمایش می دهد.

▢ به صورت عمودی یا افقی پیمایش می کند.

▢ در دستگاه های بزرگتر مانند تبلت ها به صورت مورب پیمایش می کند.

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

سوال 2

مزایای استفاده از RecyclerView چیست؟ همه موارد کاربردی را انتخاب کنید.

▢ به طور موثر لیست های بزرگ را نمایش می دهد.

▢ به طور خودکار داده ها را به روز می کند.

▢ هنگامی که یک مورد به روز می شود، حذف می شود یا به لیست اضافه می شود، نیاز به رفرش را به حداقل می رساند.

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

سوال 3

برخی از دلایل استفاده از آداپتورها چیست؟ همه موارد کاربردی را انتخاب کنید.

▢ تفکیک نگرانی ها تغییر و آزمایش کد را آسان تر می کند.

RecyclerView نسبت به داده‌هایی که نمایش داده می‌شوند آگنوستیک است.

▢ لایه های پردازش داده نباید به نحوه نمایش داده ها بپردازند.

▢ برنامه سریعتر اجرا می شود.

سوال 4

کدام یک از موارد زیر در مورد ViewHolder است؟ همه موارد کاربردی را انتخاب کنید.

▢ طرح ViewHolder در فایل های طرح بندی XML تعریف شده است.

▢ یک ViewHolder برای هر واحد داده در مجموعه داده وجود دارد.

▢ شما می توانید بیش از یک ViewHolder در RecyclerView داشته باشید.

Adapter داده ها را به ViewHolder متصل می کند.

درس بعدی را شروع کنید: 7.2: DiffUtil و اتصال داده با RecyclerView