RecyclerView بأقصى سرعة: تحليل المكتبات


إيليا نيكراسوف ، ماهاليتيت ، مطور برامج Android لشركة KODE
لمدة عامين ونصف في تطوير Android ، تمكنت من العمل في مشاريع مختلفة تمامًا: من شبكة اجتماعية لسائقي السيارات وبنك لاتفيا إلى نظام المكافآت الفيدرالي والثالثة لشركة النقل. على أي حال ، في كل من هذه المشاريع ، صادفت مهام تتطلب البحث عن حلول غير تقليدية عند تنفيذ القوائم باستخدام فئة RecyclerView.
هذه المقالة - ثمرة التحضير للأداء في DevFest Kaliningrad'18 ، وكذلك التواصل مع الزملاء - ستكون مفيدة بشكل خاص للمبتدئين والمستخدمين الذين استخدموا مكتبة واحدة فقط.


بادئ ذي بدء ، دعنا نتعمق قليلاً في جوهر المشكلة ومصدر الألم ، أي نمو الوظائف في تطوير التطبيق وتعقيد القوائم المستخدمة.


الفصل الأول ، الذي يحلم فيه العميل بطلب ، ونحن - حول متطلبات واضحة


دعونا نتخيل موقفًا عندما يتصل عميل باستوديو يريد تطبيقًا محمولًا لمتجر بطة مطاطية.


يتطور المشروع بسرعة ، وتنشأ أفكار جديدة بانتظام ولا يتم وضعها في خريطة طريق طويلة المدى.


أولاً ، يطلب منا العميل عرض قائمة بالبضائع الموجودة ، وعند النقر فوقه ، قم بملء طلب التسليم. ليس عليك أن تذهب بعيداً للوصول إلى حل: نستخدم المجموعة الكلاسيكية من RecyclerView ، وهو محول بسيط مكتوب ذاتيًا له والنشاط .


بالنسبة للمحول ، نستخدم بيانات متجانسة ، ViewHolder واحدة ومنطق ربط بسيط.


محول مع البط
class DucksClassicAdapter( private val data: List<Duck>, private val onDuckClickAction: (Duck) -> Unit ) : RecyclerView.Adapter<DucksClassicAdapter.ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val rubberDuckView = LayoutInflater.from(parent.context).inflate(R.layout.item_rubber_duck, parent, false) return ViewHolder(rubberDuckView) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val duck = data[position] holder.divider.isVisible = position != 0 holder.rubberDuckImage.apply { Picasso.get() .load(duck.icon) .config(Bitmap.Config.ARGB_4444) .fit() .centerCrop() .noFade() .placeholder(R.drawable.duck_stub) .into(this) } holder.clicksHolder.setOnClickListener { onDuckClickAction.invoke(duck) } } override fun getItemCount() = data.count() class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val rubberDuckImage: ImageView = view.findViewById(R.id.rubberDuckImage) val clicksHolder: View = view.findViewById(R.id.clicksHolder) val divider: View = view.findViewById(R.id.divider) } } 



بمرور الوقت ، لدى العميل فكرة إضافة فئة أخرى من السلع إلى البط المطاطي ، مما يعني أنه سيتعين عليه إضافة نموذج بيانات جديد وتخطيط جديد. ولكن الأهم من ذلك ، سيظهر ViewType آخر في المحول ، حيث يمكنك تحديد ViewHolder الذي تريد استخدامه لعنصر قائمة محدد.


بعد ذلك ، تتم إضافة الرؤوس إلى الفئات ، والتي يمكن بموجبها طي كل فئة وتوسيعها لتبسيط اتجاه المستخدمين في المتجر. هذا بالإضافة إلى ViewType و ViewHolder للرؤوس. بالإضافة إلى ذلك ، سيكون عليك تعقيد المحول ، حيث تحتاج إلى الاحتفاظ بقائمة من المجموعات المفتوحة واستخدامها للتحقق من الحاجة لإخفاء وإظهار هذا العنصر أو ذلك عن طريق النقر على الرأس.


محول مع كل شيء
 class DucksClassicAdapter( private val onDuckClickAction: (Pair<Duck, Int>) -> Unit, private val onSlipperClickAction: (Duck) -> Unit, private val onAdvertClickAction: (Advert) -> Unit ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { var data: List<Duck> = emptyList() set(value) { field = value internalData = data.groupBy { it.javaClass.kotlin } .flatMap { groupedDucks -> val titleRes = when (groupedDucks.key) { DuckSlipper::class -> R.string.slippers RubberDuck::class -> R.string.rubber_ducks else -> R.string.mixed_ducks } groupedDucks.value.let { listOf(FakeDuck(titleRes, it)).plus(it) } } .toMutableList() duckCountsAdapters = internalData.map { duck -> val rubberDuck = (duck as? RubberDuck) DucksCountAdapter( data = (1..(rubberDuck?.count ?: 1)).map { count -> duck to count }, onCountClickAction = { onDuckClickAction.invoke(it) } ) } } private val advert = DuckMockData.adverts.orEmpty().shuffled().first() private var internalData: MutableList<Duck> = mutableListOf() private var duckCountsAdapters: List<DucksCountAdapter> = emptyList() private var collapsedHeaders: MutableSet<Duck> = hashSetOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { VIEW_TYPE_RUBBER_DUCK -> { val view = parent.context.inflate(R.layout.item_rubber_duck, parent) DuckViewHolder(view) } VIEW_TYPE_SLIPPER_DUCK -> { val view = parent.context.inflate(R.layout.item_duck_slipper, parent) SlipperViewHolder(view) } VIEW_TYPE_HEADER -> { val view = parent.context.inflate(R.layout.item_header, parent) HeaderViewHolder(view) } VIEW_TYPE_ADVERT -> { val view = parent.context.inflate(R.layout.item_advert, parent) AdvertViewHolder(view) } else -> throw UnsupportedOperationException("view type $viewType without ViewHolder") } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is HeaderViewHolder -> bindHeaderViewHolder(holder, position) is DuckViewHolder -> bindDuckViewHolder(holder, position) is SlipperViewHolder -> bindSlipperViewHolder(holder, position) is AdvertViewHolder -> bindAdvertViewHolder(holder) } } private fun bindAdvertViewHolder(holder: AdvertViewHolder) { holder.advertImage.showIcon(advert.icon) holder.advertTagline.text = advert.tagline holder.itemView.setOnClickListener { onAdvertClickAction.invoke(advert) } } private fun bindHeaderViewHolder(holder: HeaderViewHolder, position: Int) { val item = getItem(position) as FakeDuck holder.clicksHolder.setOnClickListener { changeCollapseState(item, position) } val arrowRes = if (collapsedHeaders.contains(item)) R.drawable.ic_keyboard_arrow_up_black_24dp else R.drawable.ic_keyboard_arrow_down_black_24dp holder.arrow.setImageResource(arrowRes) holder.title.setText(item.titleRes) } private fun changeCollapseState(item: FakeDuck, position: Int) { val isCollapsed = collapsedHeaders.contains(item) if (isCollapsed) { collapsedHeaders.remove(item) } else { collapsedHeaders.add(item) } // 1 to add items after header val startPosition = position + 1 if (isCollapsed) { internalData.addAll(startPosition - ADVERTS_COUNT, item.items) notifyItemRangeInserted(startPosition, item.items.count()) } else { internalData.removeAll(item.items) notifyItemRangeRemoved(startPosition, item.items.count()) } notifyItemChanged(position) } @SuppressLint("SetTextI18n") private fun bindSlipperViewHolder(holder: SlipperViewHolder, position: Int) { val slipper = getItem(position) as DuckSlipper holder.duckSlipperImage.showIcon(slipper.icon) holder.duckSlipperSize.text = ": ${slipper.size}" holder.clicksHolder.setOnClickListener { onSlipperClickAction.invoke(slipper) } } private fun bindDuckViewHolder(holder: DuckViewHolder, position: Int) { val duck = getItem(position) as RubberDuck holder.rubberDuckImage.showIcon(duck.icon) holder.rubberDuckCounts.adapter = duckCountsAdapters[position - ADVERTS_COUNT] val context = holder.itemView.context holder.rubberDuckCounts.layoutManager = LinearLayoutManager(context, HORIZONTAL, false) } override fun getItemViewType(position: Int): Int { if (position == 0) return VIEW_TYPE_ADVERT return when (getItem(position)) { is FakeDuck -> VIEW_TYPE_HEADER is RubberDuck -> VIEW_TYPE_RUBBER_DUCK is DuckSlipper -> VIEW_TYPE_SLIPPER_DUCK else -> throw UnsupportedOperationException("unknown type for $position position") } } private fun getItem(position: Int) = internalData[position - ADVERTS_COUNT] override fun getItemCount() = internalData.count() + ADVERTS_COUNT class DuckViewHolder(view: View) : RecyclerView.ViewHolder(view) { val rubberDuckImage: ImageView = view.findViewById(R.id.rubberDuckImage) val rubberDuckCounts: RecyclerView = view.findViewById(R.id.rubberDuckCounts) } class SlipperViewHolder(view: View) : RecyclerView.ViewHolder(view) { val duckSlipperImage: ImageView = view.findViewById(R.id.duckSlipperImage) val duckSlipperSize: TextView = view.findViewById(R.id.duckSlipperSize) val clicksHolder: View = view.findViewById(R.id.clicksHolder) } class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) { val title: TextView = view.findViewById(R.id.headerTitle) val arrow: ImageView = view.findViewById(R.id.headerArrow) val clicksHolder: View = view.findViewById(R.id.clicksHolder) } class AdvertViewHolder(view: View) : RecyclerView.ViewHolder(view) { val advertTagline: TextView = view.findViewById(R.id.advertTagline) val advertImage: ImageView = view.findViewById(R.id.advertImage) } } private class FakeDuck( val titleRes: Int, val items: List<Duck> ) : Duck private fun ImageView.showIcon(icon: String, placeHolderRes: Int = R.drawable.duck_stub) { Picasso.get() .load(icon) .config(Bitmap.Config.ARGB_4444) .fit() .centerCrop() .noFade() .placeholder(placeHolderRes) .into(this) } private class DucksCountAdapter( private val data: List<Pair<Duck, Int>>, private val onCountClickAction: (Pair<Duck, Int>) -> Unit ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val view = parent.context.inflate(R.layout.item_duck_count, parent) return CountViewHolder(view) } override fun getItemCount() = data.count() override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { (holder as CountViewHolder).count.apply { val item = data[position] text = item.second.toString() setOnClickListener { onCountClickAction.invoke(item) } } } class CountViewHolder(view: View) : RecyclerView.ViewHolder(view) { val count: TextView = view.findViewById(R.id.count) } } 

أعتقد أنك فهمت النقطة - مثل هذه الكومة الصغيرة تشبه التطور الصحي. وفي المستقبل هناك المزيد والمزيد من المتطلبات الجديدة من العميل: لإصلاح لافتة إعلانية في أعلى القائمة ، لإدراك إمكانية اختيار عدد البط المطلوب. فقط هذه المهام ستتحول في النهاية إلى محولات منتظمة ، والتي يجب كتابتها مرة أخرى من الصفر.


عملية تطوير محول كلاسيكي في تاريخ جيثب

الملخص


في الواقع ، الصورة ليست مشجعة على الإطلاق: يجب أن يتم شحذ المحولات الفردية لحالات محددة. ندرك جميعًا أنه في تطبيق حقيقي هناك العشرات أو حتى المئات من شاشات القوائم هذه. ولا تحتوي على معلومات حول البط ، بل بيانات أكثر تعقيدًا. نعم ، وتصميمهم أكثر تعقيدًا.


ما هو الخطأ في محولاتنا؟


  • من الواضح أنه من الصعب إعادة استخدامها ؛
  • داخل منطق الأعمال يظهر وبمرور الوقت يصبح أكثر وأكثر ؛
  • من الصعب الحفاظ عليها وتوسيعها ؛
  • مخاطر عالية من الأخطاء عند تحديث البيانات ؛
  • تصميم غير واضح.

الفصل الثاني ، حيث يمكن أن يكون كل شيء مختلفًا


من غير الواقعي ومن غير المعقول أن نتخيل تطوير التطبيق لسنوات قادمة. بعد زوجين من هذه الرقصات مع الدف كما هو الحال في الفصل الأخير وكتابة العشرات من المحولات ، سيكون لدى أي شخص سؤال "ربما هناك حلول أخرى؟".



بعد الانتهاء من Github ، وجدنا أن أول مكتبة AdapterDelegates ظهرت في عام 2015 ، وبعد ذلك بعام أضيفت Groupie و Epoxy إلى ترسانة المطورين - كلهم ​​يساعدون في جعل الحياة أسهل ، ولكن لكل منها خصوصياته ومزالقه.


هناك العديد من المكتبات المتشابهة (على سبيل المثال ، FastAdapter) ، لكنني لم أعمل أنا ولا زملائي معهم ، لذلك لن نأخذها في الاعتبار في المقالة.

قبل مقارنة المكتبات ، نحلل بإيجاز الحالة الموضحة أعلاه مع المتجر عبر الإنترنت بشرط استخدام Adapdelegates - من المكتبات المفككة ، وهي أبسط طريقة من وجهة نظر التنفيذ الداخلي والاستخدام (ومع ذلك ، فهي ليست متقدمة في كل شيء ، لذلك عليك إضافة الكثير من الأشياء يدويًا).


لن تنقذنا المكتبة تمامًا من المحول ، ولكن سيتم تشكيلها من الكتل (الطوب) ، والتي يمكننا إضافتها بأمان إلى القائمة أو إزالتها من القائمة وتبديلها.


محول كتلة
 class DucksDelegatesAdapter : ListDelegationAdapter<List<DisplayableItem>>() { init { delegatesManager.addDelegate(RubberDuckDelegate()) } fun setData(items: List<DisplayableItem>) { this.items = items notifyDataSetChanged() } } private class RubberDuckDelegate : AbsListItemAdapterDelegate<RubberDuckItem, DisplayableItem, RubberDuckDelegate.ViewHolder>() { override fun isForViewType(item: DisplayableItem, items: List<DisplayableItem>, position: Int): Boolean { return item is RubberDuckItem } override fun onCreateViewHolder(parent: ViewGroup): ViewHolder { val item = parent.context.inflate(R.layout.item_rubber_duck, parent, false) return ViewHolder(item) } override fun onBindViewHolder(item: RubberDuckItem, viewHolder: ViewHolder, payloads: List<Any>) { viewHolder.apply { rubberDuckImage.showIcon(item.icon) } } class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val rubberDuckImage: ImageView = itemView.findViewById(R.id.rubberDuckImage) } } 

استخدام محول النشاط
 class DucksDelegatesActivity : AppCompatActivity() { private lateinit var ducksList: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_ducks_delegates) ducksList = findViewById(R.id.duckList) ducksList.apply { layoutManager = LinearLayoutManager(this@DucksDelegatesActivity) adapter = createAdapter().apply { showData() } } } fun createAdapter(): DucksDelegatesAdapter { return DucksDelegatesAdapter() } private fun DucksDelegatesAdapter.showData() { setData(getRubberDucks()) } private fun getRubberDucks(): List<DisplayableItem> { return DuckMockData.ducks.orEmpty().map { RubberDuckItem(it.icon) } } } 

من المهمة الأولى ، نرى الفرق: لدينا فئة محول ترث من المكتبة. بالإضافة إلى ذلك - نفس الطوب ، الذي يطلق عليه المندوب والذي نرث منه أيضًا وننفذ جزءًا من المنطق الذي نحتاجه. بعد ذلك ، نضيف مفوضًا إلى المدير - هذا أيضًا فئة مكتبة. وآخر شيء تحتاجه هو إنشاء محول وملئه بالبيانات


لتنفيذ الفئة الثانية من المتجر والعناوين ، سنكتب بضعة مندوبين آخرين ، وتظهر الرسوم المتحركة بفضل فئة DiffUtil .


هنا سوف أشير إلى استنتاج موجز ولكن نهائي: استخدام هذه المكتبة حتى يحل كل المشاكل المدرجة التي نشأت عندما قمنا بتعقيد التطبيق في حالة مع متجر على الإنترنت ، ولكن بدون السلبيات ، في أي مكان آخر.


عملية تطوير المحول باستخدام AdapterDelegates في سجل github

الفصل الثالث ، حيث يزيل المطور النظارات الوردية بمقارنة المكتبات


سوف نتعمق بمزيد من التفاصيل في وظائف وتشغيل كل من المكتبات. بطريقة أو بأخرى ، استخدمت المكتبات الثلاث في مشاريعنا ، اعتمادًا على المهام وتعقيد التطبيق.


الموفدين


نستخدم هذه المكتبة في تطبيق إحدى أكبر شركات الطيران الروسية. نحن بحاجة إلى استبدال قائمة دفع بسيطة بقائمة تحتوي على مجموعات وعدد كبير من المعلمات المختلفة.


يبدو مخطط عمل المكتبة المبسط كما يلي:


الفصل الرئيسي هو "مندوب التفويض" ، "الطوب" المختلفة هم "المندوبون" المسؤولون عن عرض نوع بيانات معين ، وبالطبع القائمة نفسها.


الإيجابيات:


  • بساطة الغمر
  • من السهل إعادة استخدام المحولات ؛
  • عدد قليل من الأساليب والفصول.
  • لا يوجد انعكاس ، إنشاء التعليمات البرمجية وربط البيانات.

السلبيات:


  • تحتاج إلى تنفيذ المنطق بنفسك ، على سبيل المثال تحديث العناصر من خلال DiffUti ل (منذ الإصدار 3.1.0 ، يمكنك استخدام محول AsyncListDifferDelegationAdapter) ؛
  • كود زائدة.

بشكل عام ، تحل هذه المكتبة جميع الصعوبات الرئيسية في توسيع وظائف التطبيق وهي مناسبة لأولئك الذين لم يستخدموا المكتبة من قبل. ولكن لإيقاف ذلك فقط ، لا أنصح.

زقة


غالبًا ما نستخدم Groupie ، الذي تم إنشاؤه قبل عدة سنوات بواسطة Lisa Wray ، بما في ذلك كتابة الطلب لأحد البنوك اللاتفية باستخدامه بالكامل.


لاستخدام هذه المكتبة ، أولاً وقبل كل شيء ، تحتاج إلى التعامل مع التبعيات . بالإضافة إلى الخيار الرئيسي ، يمكنك استخدام العديد من الخيارات للاختيار من بينها:



نتحدث عن شيء واحد ونصف التبعيات اللازمة.


باستخدام مثال لمتجر عبر الإنترنت مع البط ، نحتاج إلى إنشاء عنصر موروث من فئة المكتبة ، وتحديد التخطيط وتنفيذ الربط من خلال Syntentics Kotlin. مقارنةً بكمية الشفرة التي كان عليّ كتابتها باستخدام AdapterDelegates ، فهي مجرد جنة وأرض .


كل ما تبقى هو تعيين RecyclerView GroupieAdapter كمحول ، ووضع العناصر التي تم السخرية فيها.



يمكن ملاحظة أن مخطط العمل أكبر وأكثر تعقيدًا. هنا ، بالإضافة إلى العناصر البسيطة ، يمكنك استخدام أقسام كاملة - مجموعات من العناصر وفصول أخرى.


الإيجابيات:


  • واجهة بديهية ، على الرغم من أن api يجعلك تفكر ؛
  • وجود حلول محاصر ؛
  • الانقسام إلى مجموعات من العناصر ؛
  • الاختيار بين الخيار المعتاد ، ملحقات Kotlin و DataBinding ؛
  • تضمين ItemDecoration والرسوم المتحركة.

السلبيات:


  • ويكي غير مكتملة ؛
  • ضعف الدعم من المشرف والمجتمع.
  • البق الصغيرة التي كان يجب التحايل عليها في الإصدار الأول ؛
  • تختلف في الموضوع الرئيسي (في الوقت الحالي) ؛
  • لا يوجد دعم لـ AndroidX (في الوقت الحالي ، ولكنك تحتاج إلى تتبع المستودع).

من المهم أن Groupie ، بكل عيوبها ، يمكن بسهولة استبدال AdapterDelegates ، خاصة إذا كنت تخطط لإنشاء قوائم قابلة للطي من المستوى الأول ، ولا تريد كتابة الكثير من الألواح.


تنفيذ قائمة البط مع Groupie

الايبوكسي


آخر مكتبة بدأنا استخدامها مؤخرًا نسبيًا هي Epoxy ، التي طورها رجال Airbnb . المكتبة معقدة ، لكنها تتيح لك حل مجموعة كاملة من المهام. يستخدمه مبرمجو Airbnb أنفسهم لتقديم الشاشات مباشرة من الخادم. لقد كانت الإيبوكسي مفيدة لنا في أحد أحدث المشاريع - طلب للحصول على بنك في يكاترينبورغ.


لتطوير الشاشات ، كان علينا العمل مع أنواع مختلفة من البيانات ، وعدد كبير من القوائم. وكانت إحدى الشاشات لا حصر لها حقًا. وساعدنا الإيبوكسي جميعًا في التعامل مع هذا.


يشبه مبدأ المكتبة ككل المبدأين السابقين ، باستثناء أنه بدلاً من المحول ، يتم استخدام EpoxyController لبناء القائمة ، مما يسمح لك بتحديد هيكل المحول بشكل معلن.



لتحقيق ذلك ، بنيت المكتبة على توليد التعليمات البرمجية. كيف يعمل - مع جميع الفروق الدقيقة هو موصوف بشكل جيد في الويكي وينعكس في العينات .


الإيجابيات:


  • نماذج للقائمة ، تم إنشاؤها من العرض المعتاد ، مع إمكانية إعادة الاستخدام في الشاشات البسيطة ؛
  • الوصف التقريري للشاشات ؛
  • DataBinding بأقصى سرعة - يولد النماذج مباشرة من ملفات التخطيط ؛
  • ببساطة عرض قوائم ليس فقط من الكتل ، ولكن أيضًا شاشات معقدة ؛
  • المشتركة ViewPool على النشاط ؛
  • اختلاف غير متزامن خارج منطقة الجزاء (AsyncEpoxyController) ؛
  • لا حاجة للبخار مع القوائم الأفقية.

السلبيات:


  • مجموعة كبيرة من الفصول والمعالجات والشروح ؛
  • الغوص الصعب من الصفر ؛
  • يستخدم البرنامج المساعد ButterKnife لإنشاء ملفات R2 في الوحدات النمطية ؛
  • من الصعب جدًا فهم كيفية العمل بشكل صحيح مع رد الاتصال (نحن أنفسنا لم نفهم بعد) ؛
  • هناك مشكلات تحتاج إلى التحايل عليها: على سبيل المثال ، تعطل بنفس المعرف.

تنفيذ قائمة البط مع الايبوكسي

الملخص


الشيء الرئيسي الذي أردت إيصاله هو: لا تتحمّل التعقيد الذي يظهر عندما تحتاج إلى إنشاء قوائم معقدة ويجب عليك إعادة وضعها باستمرار. وهذا يحدث في كثير من الأحيان. ومن حيث المبدأ ، عندما يتم تنفيذها ، إذا كان المشروع قد بدأ للتو ، أو تشارك في إعادة هيكلة المشروع.


الحقيقة هي أنه ليس من الضروري تعقيد المنطق مرة أخرى ، معتقدين أن نوعًا من التجريد الخاص بنا سيكفي. إنها قصيرة بما فيه الكفاية والعمل معها ليس فقط متعة ، ولكن هناك أيضًا إغراء لنقل جزء من المنطق إلى جزء واجهة المستخدم ، والذي لا ينبغي أن يكون هناك. هناك أدوات يمكن أن تساعدك على تجنب معظم المشاكل ، وتحتاج إلى استخدامها.


أفهم أنه بالنسبة للعديد من المطورين ذوي الخبرة (وليس فقط) فإن هذا إما واضح أو قد يختلفون معي. لكني أعتبر أنه من المهم التأكيد على ذلك مرة أخرى.

لذا ، ماذا تختار؟


من الصعب جدًا تقديم المشورة للبقاء في مكتبة واحدة ، لأن الاختيار يعتمد على العديد من العوامل: من التفضيلات الشخصية إلى الإيديولوجيا في المشروع.


سأفعل ما يلي:


  1. إذا كنت قد بدأت للتو طريقك في التطوير ، فحاول البدء في مشروع صغير باستخدام AdapterDelegates - هذه هي أبسط مكتبة - لا يلزم معرفة خاصة. سوف تفهم كيفية العمل معها ولماذا هي أكثر ملاءمة من كتابة المحولات بنفسك.
  2. Groupie مناسب لأولئك الذين لعبوا بالفعل بما فيه الكفاية مع AdapterDelegates وتعبوا من كتابة مجموعة من لوحة مرجعية ، أو لأي شخص آخر يريد على الفور أن يبدأ بأرضية وسط. ولا تنسى وجود مجموعات قابلة للطي خارج الصندوق - هذه أيضًا حجة جيدة لصالحها.
  3. حسنًا ، وإيبوكسي - لأولئك الذين يواجهون مشروعًا معقدًا حقًا ، مع كمية هائلة من البيانات ، لذا فإن التعقيد في مكتبة الدهون سيكون أقل مشكلة. في البداية سيكون الأمر صعبًا ، ولكن بعد ذلك سيبدو تنفيذ القوائم بمثابة تافه. قد يكون وجود حجة مهمة لصالح Epoxy هو وجود DataBinding و MVVM في المشروع - تم إنشاؤه حرفيا لهذا ، بالنظر إلى إمكانية إنشاء نماذج من التخطيطات المقابلة.

إذا كانت لا تزال لديك أسئلة ، فيمكنك الاطلاع على الرابط لرؤية رمز طلبنا مرة أخرى مع التفاصيل.

Source: https://habr.com/ru/post/ar428525/


All Articles