paint-brush
एंड्रॉइड में सुपर-पावर्ड विंटेज सूचियाँ: एक फाउंडेशन के रूप में प्रतिनिधिमंडलद्वारा@6hundreds
330 रीडिंग
330 रीडिंग

एंड्रॉइड में सुपर-पावर्ड विंटेज सूचियाँ: एक फाउंडेशन के रूप में प्रतिनिधिमंडल

द्वारा Sergey Opivalov9m2023/07/01
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

कई परियोजनाएँ अभी भी व्यू फ्रेमवर्क का उपयोग कर रही हैं और इसलिए सूचियाँ रखने के लिए RecyclerView का उपयोग कर रही हैं। यह आलेख RecylerView के साथ मेरे अनुभव के संकलन का परिणाम है। मैं RecylderView के आसपास एक ढांचा बनाने के अपने दृष्टिकोण के बारे में बताऊंगा।
featured image - एंड्रॉइड में सुपर-पावर्ड विंटेज सूचियाँ: एक फाउंडेशन के रूप में प्रतिनिधिमंडल
Sergey Opivalov HackerNoon profile picture
0-item
1-item

आजकल, जेटपैक कंपोज़ को स्वीकृति मिल रही है और यह निश्चित रूप से इसका हकदार है। यह एंड्रॉइड यूआई विकास में एक वास्तविक आदर्श बदलाव है। इतनी अधिक सूची बनाने से बॉयलरप्लेट को वहां से हटा दिया गया। और यह सुंदर है.


मेरा मानना है कि कई परियोजनाएं अभी भी व्यू फ्रेमवर्क का उपयोग कर रही हैं और इसलिए सूचियां रखने के लिए रीसाइक्लरव्यू का उपयोग कर रही हैं।


जैसा कि मैं देखता हूं, इसके कुछ कारण हैं:

  • RecyclerView के आस-पास के कुछ इन-हाउस स्वामित्व समाधान आसानी से कंपोज़ से मेल नहीं खाते हैं, इसलिए माइग्रेशन लागत अधिक है
  • डेवलपर्स अभी तक कंपोज़ से पर्याप्त रूप से परिचित नहीं हैं
  • कंपोज़ के प्रदर्शन के बारे में कोई भरोसा नहीं है (अभी तक)


लेकिन वैसे भी, कंपोज़ ही भविष्य है और यही कारण है कि मैं शीर्षक में "विंटेज" शब्द का उपयोग कर रहा हूँ।


विंटेज का मतलब है पुराना लेकिन सोना। यह लेख RecyclerView के साथ मेरे अनुभव के संकलन का परिणाम है, और जो लोग अभी भी इसके साथ ट्रैक पर हैं, मैं RecyclerView के आसपास एक रूपरेखा बनाने के अपने दृष्टिकोण के बारे में बताऊंगा।

आवश्यकताएं

ढांचे को स्केलेबिलिटी का उचित स्तर प्रदान करना चाहिए। इसलिए मुख्य परिदृश्यों को न्यूनतम मात्रा में बॉयलरप्लेट के साथ संभाला जाना चाहिए, लेकिन यदि उपयोगकर्ता कुछ कस्टम बनाना चाहता है तो इसे रास्ते में नहीं आना चाहिए।


मैं RecyclerView के साथ काम के दौरान परिदृश्यों के आधार सेट को इस प्रकार परिभाषित करूंगा:

  • डेटा का प्रभावी अद्यतन.
    • यह ज्ञात है कि RecyclerView के साथ काम करने के दौरान सूची में डेटा को अपडेट करने के लिए DiffUtil API का उपयोग करना सबसे अच्छा अभ्यास है। हम यहां किसी पहिये का दोबारा आविष्कार नहीं करने जा रहे हैं, इसलिए हम DiffUtil लाभ उठाने पर ध्यान केंद्रित करेंगे और इसके कार्यान्वयन के लिए एक समारोह को छोटा करने का भी प्रयास करेंगे।


  • विषमांगी सूचियों को परिभाषित करना.
    • किसी सूची में अनेक प्रकार के विचार होना स्वाभाविक है। मोबाइल उपकरणों की स्क्रीन ऊंचाई से सीमित होती हैं और विभिन्न उपकरणों के स्क्रीन आकार अलग-अलग होते हैं। तो भले ही आपकी सामग्री स्क्रॉल किए बिना एक डिवाइस पर स्क्रीन करने के लिए उपयुक्त हो, उसे किसी अन्य डिवाइस पर स्क्रॉल करने की आवश्यकता हो सकती है।


      इसीलिए RecyclerView के शीर्ष पर कुछ स्क्रीन विकसित करना एक अच्छा विचार हो सकता है।



  • सजावट सूची आइटम
    • सूचियों के लिए सजावट एक बहुत ही सामान्य मामला है। और यद्यपि आप सजावट को आइटम लेआउट में विलय कर सकते हैं, जो सीधा दिख सकता है, वह बहुत लचीला नहीं है: एक ही आइटम प्रकार के लिए अलग-अलग स्क्रीन पर अलग-अलग सजावट की आवश्यकता हो सकती है या किसी सूची में आइटम की स्थिति के आधार पर अलग-अलग सजावट की भी आवश्यकता हो सकती है।


फाउंडेशन विकल्प

पहला विकल्प सब कुछ नए सिरे से बनाना है। यह कार्यान्वयन पर पूर्ण नियंत्रण दे रहा है, लेकिन हम इसे हमेशा कर सकते हैं।


तृतीय पक्ष ओपन-सोर्स फ्रेमवर्क का एक समूह है जो कम बॉयलरप्लेट के साथ रीसाइक्लरव्यू के साथ काम करने की अनुमति देता है। मैं सभी को गिनने नहीं जा रहा हूं, लेकिन उनमें से दो को बिल्कुल विपरीत दृष्टिकोण के साथ दिखाना चाहता हूं:


epoxy

RecyclerView के साथ जटिल स्क्रीन बनाने के लिए AirBnb का समाधान। यह बॉयलरप्लेट को यथासंभव कम करने के लिए नए एपीआई का एक समूह और यहां तक कि एक कोड पीढ़ी भी जोड़ रहा है।


लेकिन मैं इस भावना से छुटकारा नहीं पा सकता कि परिणामी कोड विदेशी दिख रहा है। इसके अलावा, एक एनोटेशन प्रसंस्करण जटिलता की एक और परत जोड़ता है।


एडाप्टरप्रतिनिधि

छोटी लाइब्रेरी, जो वास्तव में फ़ंक्शंस और इंटरफ़ेस का एक संक्षिप्त समूह प्रदान करती है जो आपको केवल अपनी विषम सूची को प्रत्यायोजित एडेप्टर के सेट में विभाजित करने का एक तरीका सुझाती है। वास्तव में, आप अपेक्षाकृत कम समय में ऐसा स्वयं समाधान बना सकते हैं।


मुझे छोटे देशी समाधान पसंद हैं जो सभी व्यवहार को उंगलियों पर रखने के लिए काफी सरल हैं और जो सभी कार्यान्वयन विवरणों को कालीन के नीचे नहीं छिपाते हैं। इसीलिए मेरा मानना है कि एडॉप्टरडेलगेट्स और इसके सिद्धांत हमारे ढांचे की नींव के लिए एक अच्छे उम्मीदवार हैं।

सूची आइटम

शुरुआत के लिए सबसे बुनियादी चीज़ सूची आइटमों की एक सामान्य घोषणा है। वास्तविक दुनिया में सूची आइटम बहुत अलग हैं, लेकिन एकमात्र चीज जिस पर हम आश्वस्त हैं - हमें इसकी तुलना करने में सक्षम होना चाहिए।


मैं DiffUtil.ItemCallback के एपीआई का संदर्भ लेने का सुझाव देता हूं

 interface ListItem { fun isItemTheSame(other: ListItem): Boolean fun isContentTheSame(other: ListItem): Boolean { return this == other } }


यह ढांचे में एक सूची आइटम की एक न्यूनतम (और इसलिए ठोस, आईएमओ) घोषणा है। विधियों का अर्थ DiffUtil के समान होने का इरादा है:


isItemTheSame other और this पहचान की जाँच करता है

isContentTheSame other और this डेटा की जाँच करता है


स्थिर और विशिष्ट पहचान प्रदान करने का सबसे आम तरीका सर्वर से आए पहचानकर्ताओं का उपयोग करना है।


तो आइए संभावित बॉयलरप्लेट को थोड़ा कम करने के लिए एक सार कार्यान्वयन करें:

 abstract class DefaultListItem : ListItem { abstract val id: String override fun isItemTheSame(other: ListItem): Boolean = when { other !is DefaultListItem -> false this::class != other::class -> false else -> this.id == other.id } }

यदि ListItem का कार्यान्वयन एक data class होगा, तो अधिकांश मामलों में isContentTheSame काफी सरल (एक समानता जांच) होगा।


ListItem अलग रखना उन मामलों में अभी भी उचित है जब आपकी सूची में ऐसे आइटम हैं जो सर्वर डेटा का प्रक्षेपण नहीं हैं और उनकी कोई उचित पहचान नहीं है। उदाहरण के लिए, यदि आपके पास किसी सूची में आइटम के रूप में पादलेख और शीर्षलेख है, या आपके पास किसी प्रकार का एक ही आइटम है:


 data class HeaderItem(val title: String) : ListItem { override fun isItemTheSame(other: ListItem): Boolean = other is HeaderItem }


सुझाया गया दृष्टिकोण हमें एक बहुत ही स्वाभाविक और एकल DiffUtil कॉलबैक कार्यान्वयन की अनुमति देता है:

 object DefaultDiffUtil : DiffUtil.ItemCallback<ListItem>() { override fun areItemsTheSame(oldItem: ListItem, newItem: ListItem): Boolean = oldItem.isItemTheSame(newItem) override fun areContentsTheSame(oldItem: ListItem, newItem: ListItem): Boolean = oldItem.isContentTheSame(newItem) }


और यहां अंतिम चरण डिफ़ॉल्ट एडाप्टर की घोषणा है जो एडाप्टरडेलिगेट्स से AsyncListDifferDelegationAdapter का विस्तार करता है:

 class CompositeListAdapter : AsyncListDifferDelegationAdapter<ListItem>(DefaultDiffUtil)

विषम सूचियाँ

एडाप्टरडिलीगेट्स लाइब्रेरी किसी विशेष दृश्य प्रकार के लिए प्रतिनिधियों को घोषित करने का एक आसान तरीका प्रदान करती है। केवल एक चीज जिसे हम सुधार सकते हैं वह है बॉयलरप्लेट को थोड़ा कम करना:

 inline fun <reified I : ListItem, V : ViewBinding> defaultAdapterDelegate( noinline viewBinding: (layoutInflater: LayoutInflater, parent: ViewGroup) -> V, noinline block: AdapterDelegateViewBindingViewHolder<I, V>.() -> Unit ) = adapterDelegateViewBinding<I, ListItem, V>(viewBinding = viewBinding, block = block)


शोकेस उद्देश्यों के लिए, आइए एक उदाहरण पर विचार करें कि एक टेक्स्ट फ़ील्ड वाले कुछ TitleListItem की घोषणा कैसी दिख सकती है:

 data class TitleListItem( override val id: String, val title: String, ) : DefaultListItem()


और इसके लिए प्रतिनिधि नियुक्त करें

 fun titleItemDelegate(onClick: ((String) -> Unit)) = defaultAdapterDelegate< TitleListItem, TitleListItemBinding >(viewBinding = { inflater, root -> TitleListItemBinding.inflate(inflater,root,false) }) { itemView.setOnClickListener { it(item.id) } bind { binding.root.text = item.title } } }

सजी हुई सूचियाँ

मूल रूप से, RecyclerView के एपीआई के कारण, सेटिंग सजावट को डेटा को सूची में सेट करने से अलग बनाया जाता है। यदि आपके पास सजावट के लिए कुछ तर्क हैं जैसे: all items should have offset at the bottom except the last one , या all items should have a divider at the bottom, but headers should have an offset at bottom जिससे सजावट बनाना कठिन हो जाता है।


अक्सर टीमें "भगवान" डेकोरेटर के साथ आती हैं जिन्हें कॉन्फ़िगर किया जा सकता है:

 class SmartDividerItemDecorator( val context: Context, val skipDividerFor: Set<Int> = emptySet(), val showDividerAfterLastItem: Boolean = false, val showDividerBeforeFirstItem: Boolean = false, val dividerClipToPadding: Boolean = true, val dividerPaddingLeft: Int = 0, val dividerPaddingRight: Int = 0 ) : ItemDecoration()

आप केवल कार्यान्वयन विवरण की कल्पना कर सकते हैं और यह कितना नाजुक और स्केलेबल नहीं है।


हम कुछ RecyclerView API की नकल करने और एक कार्यान्वयन सौंपने के साथ एक पुराने सिद्ध दृष्टिकोण का उपयोग कर सकते हैं।


इसलिए मैं एक डेटा से शुरुआत कर रहा हूं:

 interface Decoration


सजावट के लिए इंटरफ़ेस मार्कर, जिसे भविष्य में प्रस्तुत किया जाएगा।

 interface HasDecorations { var decorations: List<Decoration> }

इस इंटरफ़ेस को हमारे डेटा आइटम द्वारा यह घोषित करने के लिए कार्यान्वित किया जाना चाहिए कि यह विशेष आइटम सजावट की दी गई सूची से सजाया जाना चाहता है। यदि आवश्यक हो तो रनटाइम में इसे बदलने के लिए सजावट को सरल var है।


बहुत बार किसी आइटम में एक ही सजावट होती है, इसलिए किसी एक आइटम को listOf() में लपेटकर बॉयलरप्लेट को कम करने के लिए हम इस तरह का पैंतरेबाज़ी कर सकते हैं:

 interface HasDecoration : HasDecorations { var decoration: Decoration override var decorations: List<Decoration> get() = listOf(decoration) set(value) { decoration = when { value.isEmpty() -> None value.size == 1 -> value.first() else -> throw IllegalArgumentException("Applying few decorations to HasDecoration instance is prohibited. Use HasDecorations") } } }


अगला चरण ItemDecoration एपीआई की नकल है:

 interface DecorationDrawer<T : Decoration> { fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State, decoration: T ) fun onDraw( c: Canvas, view: View, parent: RecyclerView, state: RecyclerView.State, decoration: T ) }

जैसा कि आप देख सकते हैं, यह एक ही अंतर के साथ ItemDecoration पूरी तरह से दोहराता है - अब इसे सजावट के प्रकार और उदाहरण के बारे में पता चल रहा है।


DecorationDrawer के लिए संभावित कार्यान्वयन दूसरे लेख का विषय है, अब आइए केवल इंटरफ़ेस पर ध्यान केंद्रित करें और सजावट प्रस्तुत करने के लिए इसे कैसे संभाला जाना चाहिए।

 class CompositeItemDecoration( private val context: Context ) : RecyclerView.ItemDecoration() { private val drawers = mutableMapOf<Class<Decoration>, DecorationDrawer<Decoration>>() // [1] private fun applyDecorationToView(parent: RecyclerView, view: View, applyDecoration: (Decoration) -> Unit) { val position = getAdapterPositionForView(parent, view) // [2] when { // [3] position == RecyclerView.NO_POSITION -> return adapter.items[position] is HasDecorations -> { val decoration = (adapter.items[position] as HasDecorations).decorations decoration.forEach(applyDecoration) } else -> return } } override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State ) { applyDecorationToView(parent, view) { decoration -> val drawer = getDrawerFor(decoration) drawer.getItemOffsets(outRect, view, parent, state, decoration) } } override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { parent.forEach { child -> applyDecorationToView(parent, child) { decoration -> val drawer = getDrawerFor(decoration) drawer.onDraw(c, child, parent, state, decoration) } } } private fun getDrawerFor(decoration: Decoration): DecorationDrawer<Decoration> { // [4] } private fun getAdapterPositionForView(parent: RecyclerView, view: View): Int { var position = parent.getChildAdapterPosition(view) if (position == RecyclerView.NO_POSITION) { val oldPosition = parent.getChildViewHolder(view).oldPosition if (oldPosition in 0 until (parent.adapter?.itemCount ?: 0)) { position = oldPosition } } return position } }
  1. निर्मित दराजों के लिए कैश
  2. getAdapterPositionForView() सजावट लागू करने के लिए कुछ विशिष्ट नहीं है, यह केवल दिए गए दृश्य के लिए एडाप्टर स्थिति को सही ढंग से हल करने का एक तरीका है
  3. HasDecorations उदाहरण की सजावट के माध्यम से पुनरावृत्ति
  4. यह टुकड़ा दूसरे लेख "सजावट अ ला कार्टे" का विषय है

निष्कर्ष

इस आलेख ने RecyclerView के आसपास एक सूची ढांचा बनाने के लिए एक दृष्टिकोण की खोज की। सूची वस्तुओं को लपेटने और सूची सजावट के लिए आधार सिद्धांत के रूप में प्रतिनिधिमंडल का उपयोग किया जाता है।


मेरा मानना है कि सुझाए गए समाधान के मुख्य लाभ हैं:

  1. स्वाभाविकता. कार्यान्वयन तीसरे पक्ष के पुस्तकालयों पर निर्भर नहीं है, जो भविष्य में लचीलेपन को कम कर सकता है। एडॉप्टरडेलिगेट्स को इन मानदंडों पर विचार करने के लिए चुना गया था - यह बहुत सरल और प्राकृतिक है और सभी व्यवहार आपकी उंगलियों पर हैं।

  2. स्केलेबिलिटी। प्रतिनिधिमंडल और चिंताओं के पृथक्करण सिद्धांत के लिए धन्यवाद, हम आइटम प्रतिनिधियों या सजावट दराजों के कार्यान्वयन को अलग करने में सक्षम हैं।

  3. किसी मौजूदा प्रोजेक्ट में क्रमिक रूप से एकीकृत करना आसान है


दूसरे भाग में, हम सभी प्रकार की सूची सजावटों पर चर्चा करेंगे जिन्हें हम संभवतः एंड्रॉइड एप्लिकेशन में देखने की उम्मीद करते हैं।