محمدرضا فکری
محمدرضا فکری
خواندن ۱۱ دقیقه·۲ سال پیش

ریسایکلرویو در اندروید کاتلین | RecyclerView in Android Kotlin

سلام!

امیدوارم حالتون عالی باشه!

تو این مقاله بیاین باهم به ساختار ساده و اولیه ریسایکلرویو بپردازیم! و بعدا یکمی حرفه ای تر بشیم!

خب، اینو بگم که اگه اولتونه، ممکنه براتون ریسایکلرویو سخت باشه ولی با تمرین و تکرار خدای ریسایکلرویو میشین! این از من قول!



شروع کنیم..

آقا، این RecylerView چیه؟

به تعریف developers :

RecyclerView is the ViewGroup that contains the views corresponding to your data. It's a view itself, so you add RecyclerView to your layout the way you would add any other UI element. Each individual element in the list is defined by a view holder object.

ترجمه:

ریسایکلرویو(RecyclerView) ویو گروپی(ViewGroup) است که که حاوی نماهای مربوط به داده های شما است. این خودش ی ویو هستش، بنابر این RecyclerView را طوری که هر عنصر رابط کاربری(UI) را اضافه می کنید، به طرح بندی خود اضافه می کنید. هر عنصر جداگانه در لیست توسط یک شی نگهدارنده view تعریف می شود.

خب، بیاین برای خوب درک کردنش با ی مثال بریم جلو:

"قراره ی چندتا دیتا رو که مقدارشونو نمیدونیم، از سرور نشون بدیم"

من میگم بیایم با Postman دیتا ها رو بخونیم و دستی بیایم یکی یکی با LinearLayout و CardView و ... جمعش کنیم!

اوپس!! مگه این ممکنه؟! نه! خیلی طولانی و خسته کننده و اصلا غیر ممکنه، چندتا دیتا رو میتونی نشون بدی؟

اینجاس که ریسایکلرویو به کمک ما میاد و از دستمون میگیره و میگه محمدرضا یکمی منطقی فکر کن پسر!

و بعد میگه: ببین، مگه نمیخوای دیتا های زیاد رو زیر هم نشون بدی؟ من اینجام دیگه! من میتونم برات زیر هم و کنار هم نشون بدم! هر چقدر که آیتم بهم بدی نشون میدم! فقط ی چندتا شرط دارم :-)


اپ مورد نظرمون تو این مقاله
اپ مورد نظرمون تو این مقاله





خب، اوکی، گرفتیم چی شد! ریسایکلرویو خیلی ویو خوبی هستش و اما فرصت طلب!! ی چندتا شرط داره :-)

بیاین ببینیم چیه؟

  1. تعریف کردن در فایل xml
  2. تعریف کردن ویو برای ریسایکلرویو (آیتم های ما چطوری نشون داده بشن؟)
  3. ساخت یک دیتا کلاس (DataClass)
  4. ساحت ادپتر / آداپتر (Adapter)

توجه داشته باشیم که همه ویو هایی که قراره روشون عملیاتی انجام بشه (مثل اعمال عکس، کلیک شدن و ...) باید id داشته باشند

بیاین با ی مثال عملی ادامه بدیم. میخواهیم اسم چند مکانی رو با عکس نشون بدیم.

بیاین این 4 خان رو باهم بریم جلو، گفتیم اولین کار تعریف کردن در فایل xml که می خواهید استفاده کنید

خان اول: ساختن ویو در فایل مورد نظر

(من خودم برای مثال در activity_main.xml ساختم)

<androidx.appcompat.widget.LinearLayoutCompat xmlns:android=&quothttp://schemas.android.com/apk/res/android&quot xmlns:tools=&quothttp://schemas.android.com/tools&quot android:layout_width=&quotmatch_parent&quot android:layout_height=&quotmatch_parent&quot android:gravity=&quotcenter&quot android:orientation=&quotvertical&quot android:padding=&quot8dp&quot tools:context=&quot.MainActivity&quot> <androidx.recyclerview.widget.RecyclerView android:id=&quot@+id/recycler__main&quot android:layout_width=&quotmatch_parent&quot android:layout_height=&quotmatch_parent&quot android:paddingBottom=&quot8dp&quot tools:listitem=&quot@layout/item_recycler&quot /> </androidx.appcompat.widget.LinearLayoutCompat>

به کد بالا خوب دقت کن، ببین ما ی چیزی داریم به نام listitem که میاد ویوی که میخواهیم توش دیتاهامونو ست کنیم رو برای تست نشون میده. چرا تست؟ خب، چون از tools میاد.

دقت کنید، من padding و اینا رو خودم دادم، شما هم میتونید تغییراتی رو اضافه کنید و براخودتون ی چیز دیگه بسازین.

خان دوم: ساختن آیتم ویو (item_view.xml)

(این فایل میگه داده های ما چطوری نشون داده بشن!)

یک فایل با نام item_recycler بسازین:

<?xml version=&quot1.0&quot encoding=&quotutf-8&quot?> <com.google.android.material.card.MaterialCardView xmlns:android=&quothttp://schemas.android.com/apk/res/android&quot xmlns:app=&quothttp://schemas.android.com/apk/res-auto&quot xmlns:tools=&quothttp://schemas.android.com/tools&quot android:layout_width=&quotmatch_parent&quot android:layout_height=&quotwrap_content&quot android:layout_marginStart=&quot4dp&quot android:layout_marginTop=&quot8dp&quot android:layout_marginEnd=&quot4dp&quot android:layout_marginBottom=&quot2dp&quot app:cardCornerRadius=&quot8dp&quot app:strokeColor=&quot@color/gray_light&quot app:strokeWidth=&quot2dp&quot app:cardElevation=&quot0dp&quot> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width=&quotmatch_parent&quot android:layout_height=&quotwrap_content&quot android:padding=&quot16dp&quot> <androidx.appcompat.widget.AppCompatImageView android:id=&quot@+id/img_show_nature&quot android:layout_width=&quotmatch_parent&quot android:scaleType=&quotcenterCrop&quot android:padding=&quot8dp&quot android:layout_height=&quotwrap_content&quot app:layout_constraintEnd_toEndOf=&quotparent&quot app:layout_constraintStart_toStartOf=&quotparent&quot app:layout_constraintTop_toTopOf=&quotparent&quot tools:srcCompat=&quot@drawable/ic_launcher_foreground&quot /> <androidx.appcompat.widget.AppCompatTextView android:id=&quot@+id/txt_desc_nature&quot android:layout_width=&quotwrap_content&quot android:layout_height=&quotwrap_content&quot android:layout_marginTop=&quot16dp&quot android:textColor=&quot@color/black&quot android:textSize=&quot18sp&quot android:textStyle=&quotbold&quot app:layout_constraintEnd_toEndOf=&quot@+id/img_show_nature&quot app:layout_constraintStart_toStartOf=&quot@+id/img_show_nature&quot app:layout_constraintTop_toBottomOf=&quot@+id/img_show_nature&quot tools:text=&quotUser Name&quot /> </androidx.constraintlayout.widget.ConstraintLayout> </com.google.android.material.card.MaterialCardView>

بیاین ببینیم چی ساختیم:

activity_main.xml
activity_main.xml
item_recycler.xml
item_recycler.xml



و اما خان سوم: درست کردن دیتا کلاس

(ما می خواهیم یک عکس و یک توضیح نشون بدیم، پس دوتا variable داریم.)

یک دیتا کلاس با نام NatureShow می سازیم.

برای ساختن دیتا کلاس بجای ساختن یک کلاس ساده، Data class رو انتخاب کنید. یا خودتون دستی بسازید.

data class NatureShow( val desc: String, val imgUrl: String )


خان چهارم: قدم اصلی، Adapter

خب، قبل اینکه بریم سراغ کدنویسی، رو ادپتر ور بریم، ببینیم چیه؟

لپ تاپ داری؟ یا نمیدونم، مانیتوری که بهش ی آداپتور وصل شده باشه، یا همون گوشی خودمون که باهاش شارژ میکنیم.

ادپتری یا همون آداپتری که تو برنامه نویسی برای هندل کردن ریسایکلرویو داریم، مثل آداپتور گوشی میمونه.

آداپتور شارژر میاد و برق رو میگیره و برق رو از 220w به حدی میاره که گوشی نترکه و شارژ کنه :-))

وقتی برق واردش میشه (آداپتور) ی پراسسی رو میگذرونه و روش ی عملیاتی میشه و میرسه به جایگاه شارژ گوشی. خب، ایول! این آداپتر ما چی میشه این وسط؟

آداپتر هم مثل همونه، میاد داده رو میگیره و توخودش داده ها رو ست میکنه و ی پراسسی رو میگذرونه و تو فایل کاتلینی ازش استفاده میکنی (ست کردن).

خب، یکمی بریم تو کد و صحبت کنیم.

برای ساختنش ی کلاسی میسازی و اسمشو در رابطه ریساکلرویو و یا دیتا ها میزاری.

(من اسمشو NatureAdapter گذاشتم)

class NatureAdapter() { }

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

class NatureAdapter(private val data: ArrayList<NatureShow>) { }

از نوع private val ساختم تا تو کل کلاس بتونم ازش استفاده کنم. از نوع ArrayList با جنریک NatureShow قرار میدیم تا زا دیتا کلاسمون اینجا استفاده کنیم.

خب، همین؟ نه بابا، هنوز مونده!

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

اداپتر ما هنوز به عنوان ادپتر شناخته نشده! عجب... چیکار کنیم؟ باید چیز هایی رو ایمپلمنت (implement) کنیم :-))

قبل آکولاد ها میام دونقطه یا کالم { : } میزارم و برای اینکه بگم ادپتر رو برام بده، از RecyclerView استفاده میکنم و بعد ی نقطه (dot) میزارم و Adapter رو مینویسم.

class NatureAdapter(private val data: ArrayList<NatureShow>) : RecyclerView.Adapter { /* ... */}

همین؟ نه، عجله چرا؟

براکت { < > } ها رو باز و بسته میزارم. به اینا تو این محل دااش جنریک میگیم! بعدش پرانتز باز و بسته!

class NatureAdapter(private val data: ArrayList<NatureShow>) : RecyclerView.Adapter<>()'{ /* ... */}

این مواقعه که میتونی ی چندتا ارور ببینی، نگران نباش، کارمون هنوز تموم نشده، برا همینه :-)

خب، الان ی لحظه وایسا و به کد نگاه کن...

دو تا ارور داری، یکی دااش جنریک میده و یکی هم زیر اسم کلاس قرمز کشیده و اگه Alt + Enter بزنی میبینی که ی İmplement داره. نه، هنوز ی لحظه وایسا، هنوز زوده. چون قراره ی کلاس ViewHolder بسازیم.

خب، بریم بسازیم!

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

خب، محمدرضا، این دااش جنریک داره گیر میده، این میون چیکار کنیم براش؟ ناسلامتی دااش محله هاا!
بله بله، الان داریم دااشمونو راه میدیم. همراه باش، اول قراره دااشمونو راه میدیم.

ی inner class به اسم NatureViewHolder میسازم (اسمش در رابطه با ادپتر باشه)

محمدرضا، اینم ورودی میخواد؟ آره! چون قراره ازش بعدا استفاده کنیم.

تو ورودیش itemView از نوع View میگیرم و میایم کلاس RecyclerView.ViewHolder() رو براش ایمپلمنت (ارث بری هم میشه گفت) میکنیم و می بینیم که RecyclerView.ViewHolder() تو ورودیش ی ویو میخواد و بهش پاس میدیم و می بینیم که بله! ارور های NatureViewHolder رفتن.

همم..، پس برای RecyclerView.ViewHolder() اومدیم itemView رو تعریف کردیم؟ خب، هم آره و هم نه. نه برای اینه که فقط برای این نیستش. تو جلو ازش استفاده میکنیم.


class NatureAdapter(private val data: ArrayList<NatureShow>) : RecyclerView.Adapter { inner class NatureViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {/*...*/} }

خب، میتونیم به کلاس NatureAdapter برگردیم و چیزایی که نیاز داریم رو implement می کنیم.

سه تا تابع onCreateViewHolder, onBindViewHolder, getItemCount رو می بینیم.

ی لحظه، برا implement میرفتیم رو جایی که ارور میداد و Alt + Enter میزدیم و implement رو اتنخاب میکردیم، آره؟
بله! البته اینم بگم که هر اروری implement نمیخواد! اونایی که implementation میخواد رو باید implement کرد.
*حس جواد خیابانی رو بهم داد این*

خب، به کد ها ی نگاهی بنداز! ببین...

اینم بگم، اون TODO هارو میتونی پاک کنی، به ارور هایی که بعد پاک کردن میاد، دقت نکن! حل میشه

ببین، من معمولا میام تابع های onCreateViewHolder, onBindViewHolder رو زیر هم میندازم و در آخر هم getItemCount.

خب، بیا با getİtemCount شروع کنیم، میایم آکولاد ها رو پاک میکنیم و به جاش = میزاریم.

الان کارمون مثل موسیقی experience میره جلو!
حال میکنی؟
من خودم الان دارم به همین موسیقی گوش میدم! از Ludovico Einaudi
برنامه نویسی هم کد زدن نیس! لذت هم هستش! نیست؟ در کل انسانیم و کامپیوتر نیستیم و بکوب کار کنیم و استراحت و لذتی از کار نداشته باشیم.
لذت ببر... :-)

خب، ادامه بدیم، بعدش data.size رو مینویسم. سایز (اندازه) دیتا رو بهش پاس میدم.

override fun getItemCount(): Int = data.size
این یکی از خوبی های کاتلینه که بتونیم از = بجای return و آکولاد باز و بسته استفاده کنیم.

خب، بریم به سراغ توابع بالایی. بریم سراغ onCreateViewHolder. به وقت چای خوری تو مهمونی رسیدیم خبر نداری!!

میرم و زیر کلاس ادپترم binding رو lateinit var تعریف میکنم.

این lateinit var میگه این variable منو بیا بعدا پر کن! آره...

private lateinit var binding: ItemRecyclerBinding

(از نوع private گذاشتم و تا فقط تو این کلاس ازش استفاده کنم!)

به اسم binding نگاه کن! ی بار هم کنترل رو بگیر و روش کلیک کن! و به اسم فایلی که رفته نگاه کن!

اسم فایلی که رفته: item_recycler.xml

-عجب... اسم فایلی هستش که قراره دیتاهامون توش نشون داده بشن!
-بله...

توی onCreateViewHolder میایم و binding رو پر میکنیم و return میکنیم.

یادت میاد گفتم itemView رو برای چی ساختیم و کجا ازش استفاده میکنیم؟
حالا اینجارو باش!

توی onCreateViewHolder :

binding = ItemRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)

ولی همینطوری ریترن نمیکنیم! به نوع return تابعمون نگاهی بکن، NatureViewHolder هستش!

عجب...! خب، خب؟

میای زیر کد پر کردن binding میگی:

return NatureViewHolder(binding.root)

این item_recycler.xml رو بهمون برمیگردونه، باور نمیکنی کنترل رو بگیر و رو root کلیک کن!

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NatureViewHolder { binding = ItemRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false) return NatureViewHolder(binding.root) }
حاجی، میتونی اون پر کردن binding رو برام بگی که چرا اینجوریه؟ چرا مثل اکتیویتی نیس پر کردنش؟
اولا، من حاجی نیستم، ببین، ما اینجا به layoutİnflater دسترسی نداریم و براهمین میایم و layoutInflater رو دستی میسازیم و بهش میدیم.
ناگفته نماند که اون return انجام دادیم به درخواست NatureViewHolder هستش که از ما View میخواست.

توی NatureViewHolder میایم و ی تابعی میسازیم به نام bind که میاد و آیتم های مارو bind میکنه،

حتما اینم ورودی داره ؟
آره?

یک آیتم از نوع NatureShow میگیرم (برای ست کردن مقادیر)

inner class NatureViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(data: NatureShow) { /* ... */ } }

برمیگردم به onBindViewHolder و به تابعی که توی تابع bind گفتیم بهش آیتم رو پاس میدم. ببین:

override fun onBindViewHolder(holder: NatureViewHolder, position: Int) { holder.bind(data[position]) }

به holder توجه کن! از نوع NatureViewHolder هستش.

و position رو هم باش!

ببین دیتا رو مینویسی و آرایه position رو بده. گرفتی چی شد؟ data یه لیست از آرایه ای از BatureShow هستش و این position مساویه با ی خونه از آرایه که هر دفعه (به دفعات آیتم هایی که بهش میدیم) آپدیت میکنه.

این بالایی رو ی چندبار بخون :-)


خب، محمدرضا همین؟ دیگه آداپتر تموم؟
بله!! این از ساختنش :-)
یعنی چی؟ تموم نیس؟
نه، ازش قراره استفاده کنیم.

خب، خان های ساختن Adapter تمومه و وقت وقت استفادس!

قبل اینکه بریم، ی چیزی درباره ریسایکلرویو بگم:
من میگم این ریسایکلرویو خیلی باهوش و کاربردیه!
چرا؟
ببین این نمیاد هر دفعه شی بسازه! ببین مثلا سه تا از آیتم ها میاد و برات نشون داده میشه. برای مثال activity_main.xml رو باز کن و به بخش Design نگاه کن.سه تا برات نشون داده؟ برا من سه تاس، بر فرض مثال، میایم و میگیم این گوشی مونه یا امولاتورمونه. میاد سه تا رو میسازه و بقیه رو فقط آیتماشو عوض میکنه!
عجبب...، چقدر باهوشانه و کاربردی!

خب، بیا این کارو به اتمام برسونیم.

تو MainActivity.kt یا کلاس کاتلینی که تو لیوتش ریسایکلرویو رو تعریف کردی برو ی چندتا دیتای دستی بساز!

val data = arrayListOf<NatureShow>(/* ... */)

یه شی از ادپتر بساز و بهش پاس بده:

val adapter = NatureAdapter(data)

بعدش ست کن تو ریسایکلرویو.

binding.recyclerMain.adapter = adapter

یادته میگفتم میتونیم کنار هم و زیر هم نشون بدیم آیتم ها رو؟ اینجا اینکارو میکنیم.

binding.recyclerMain.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
فقط، من از binding استفاده کردم ها!
تو گیتهاب گفتم چطوری ادد کنی

توضیح کد بالا:

میایم روی ریسایکلرویو و میگیم لیوت منجر تو LinearLayoutmanager باشه.

اول context رو بهش پاس میدیم. اگه تو فرگمنت هستی requireContext یا context رو بدی، تو اکتیویتی میتونی this رو بدی، چون توی Main-thread هستی و به کانتکست ها دسترسی داری!

و بعدش بهش false میدی تا reverseLayout یا همون attchToRoot خودمنو فالس باشه و قابل اسکرول باشه!

و در آخر هم ران کن و حالشو ببر!



مرسی که تا اینجا اومدی! دمت گرم!! دوست دارم حسابی!!

لینک گیتهاب

بیشتر



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

درباره طرز نوشتن هم بهم بگو، اگه خوب نیس یا کافی نیس بهم بگو تا بهتر کنم :-)

میخوام برای بعد با رتروفیت بیام ریسایکلرویو رو راه بندازم و بعد ها با RxJava و عرضا به حضور شما

Kotlin-Flow و Kotlin-Coroutines و ...


محمدرضا فکری 1402.02.01

androidبرنامه نویسیکاتلینریسایکلرویو
عاشق دنیای تکنولوژی | در حال تبدیل رویا ها به واقعیت ...
شاید از این پست‌ها خوشتان بیاید