आजकल, जेटपैक कंपोज़ को स्वीकृति मिल रही है और यह निश्चित रूप से इसका हकदार है। यह एंड्रॉइड यूआई विकास में एक वास्तविक आदर्श बदलाव है। इतनी अधिक सूची बनाने से बॉयलरप्लेट को वहां से हटा दिया गया। और यह सुंदर है.
मेरा मानना है कि कई परियोजनाएं अभी भी व्यू फ्रेमवर्क का उपयोग कर रही हैं और इसलिए सूचियां रखने के लिए रीसाइक्लरव्यू का उपयोग कर रही हैं।
लेकिन वैसे भी, कंपोज़ ही भविष्य है और यही कारण है कि मैं शीर्षक में "विंटेज" शब्द का उपयोग कर रहा हूँ।
विंटेज का मतलब है पुराना लेकिन सोना। यह लेख RecyclerView के साथ मेरे अनुभव के संकलन का परिणाम है, और जो लोग अभी भी इसके साथ ट्रैक पर हैं, मैं RecyclerView के आसपास एक रूपरेखा बनाने के अपने दृष्टिकोण के बारे में बताऊंगा।
ढांचे को स्केलेबिलिटी का उचित स्तर प्रदान करना चाहिए। इसलिए मुख्य परिदृश्यों को न्यूनतम मात्रा में बॉयलरप्लेट के साथ संभाला जाना चाहिए, लेकिन यदि उपयोगकर्ता कुछ कस्टम बनाना चाहता है तो इसे रास्ते में नहीं आना चाहिए।
यह ज्ञात है कि RecyclerView के साथ काम करने के दौरान सूची में डेटा को अपडेट करने के लिए DiffUtil
API का उपयोग करना सबसे अच्छा अभ्यास है। हम यहां किसी पहिये का दोबारा आविष्कार नहीं करने जा रहे हैं, इसलिए हम DiffUtil
लाभ उठाने पर ध्यान केंद्रित करेंगे और इसके कार्यान्वयन के लिए एक समारोह को छोटा करने का भी प्रयास करेंगे।
किसी सूची में अनेक प्रकार के विचार होना स्वाभाविक है। मोबाइल उपकरणों की स्क्रीन ऊंचाई से सीमित होती हैं और विभिन्न उपकरणों के स्क्रीन आकार अलग-अलग होते हैं। तो भले ही आपकी सामग्री स्क्रॉल किए बिना एक डिवाइस पर स्क्रीन करने के लिए उपयुक्त हो, उसे किसी अन्य डिवाइस पर स्क्रॉल करने की आवश्यकता हो सकती है।
इसीलिए RecyclerView के शीर्ष पर कुछ स्क्रीन विकसित करना एक अच्छा विचार हो सकता है।
सूचियों के लिए सजावट एक बहुत ही सामान्य मामला है। और यद्यपि आप सजावट को आइटम लेआउट में विलय कर सकते हैं, जो सीधा दिख सकता है, वह बहुत लचीला नहीं है: एक ही आइटम प्रकार के लिए अलग-अलग स्क्रीन पर अलग-अलग सजावट की आवश्यकता हो सकती है या किसी सूची में आइटम की स्थिति के आधार पर अलग-अलग सजावट की भी आवश्यकता हो सकती है।
पहला विकल्प सब कुछ नए सिरे से बनाना है। यह कार्यान्वयन पर पूर्ण नियंत्रण दे रहा है, लेकिन हम इसे हमेशा कर सकते हैं।
तृतीय पक्ष ओपन-सोर्स फ्रेमवर्क का एक समूह है जो कम बॉयलरप्लेट के साथ रीसाइक्लरव्यू के साथ काम करने की अनुमति देता है। मैं सभी को गिनने नहीं जा रहा हूं, लेकिन उनमें से दो को बिल्कुल विपरीत दृष्टिकोण के साथ दिखाना चाहता हूं:
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 } }
getAdapterPositionForView()
सजावट लागू करने के लिए कुछ विशिष्ट नहीं है, यह केवल दिए गए दृश्य के लिए एडाप्टर स्थिति को सही ढंग से हल करने का एक तरीका हैHasDecorations
उदाहरण की सजावट के माध्यम से पुनरावृत्ति इस आलेख ने RecyclerView
के आसपास एक सूची ढांचा बनाने के लिए एक दृष्टिकोण की खोज की। सूची वस्तुओं को लपेटने और सूची सजावट के लिए आधार सिद्धांत के रूप में प्रतिनिधिमंडल का उपयोग किया जाता है।
स्वाभाविकता. कार्यान्वयन तीसरे पक्ष के पुस्तकालयों पर निर्भर नहीं है, जो भविष्य में लचीलेपन को कम कर सकता है। एडॉप्टरडेलिगेट्स को इन मानदंडों पर विचार करने के लिए चुना गया था - यह बहुत सरल और प्राकृतिक है और सभी व्यवहार आपकी उंगलियों पर हैं।
स्केलेबिलिटी। प्रतिनिधिमंडल और चिंताओं के पृथक्करण सिद्धांत के लिए धन्यवाद, हम आइटम प्रतिनिधियों या सजावट दराजों के कार्यान्वयन को अलग करने में सक्षम हैं।
किसी मौजूदा प्रोजेक्ट में क्रमिक रूप से एकीकृत करना आसान है
दूसरे भाग में, हम सभी प्रकार की सूची सजावटों पर चर्चा करेंगे जिन्हें हम संभवतः एंड्रॉइड एप्लिकेशन में देखने की उम्मीद करते हैं।