আজকাল, জেটপ্যাক কম্পোজ একটি গ্রহণযোগ্যতা লাভ করে এবং এটি অবশ্যই এটির যোগ্য। এটি অ্যান্ড্রয়েড UI বিকাশে একটি বাস্তব দৃষ্টান্ত পরিবর্তন। এত কিছু তৈরি করা তালিকা বয়লারপ্লেট সেখানে বাদ দেওয়া হয়েছিল। এবং এটা সুন্দর.
আমি বিশ্বাস করি যে অনেক প্রকল্প এখনও ভিউ ফ্রেমওয়ার্ক ব্যবহার করছে এবং তাই তালিকা থাকার জন্য RecyclerView ব্যবহার করছে।
কিন্তু যাইহোক, রচনা হল ভবিষ্যত এবং এই কারণেই আমি একটি শিরোনামে "ভিন্টেজ" শব্দটি ব্যবহার করছি।
ভিনটেজ মানে পুরানো কিন্তু সোনালি। এই নিবন্ধটি RecyclerView এর সাথে আমার অভিজ্ঞতার একটি সংকলনের ফলাফল, এবং যারা এখনও এটির সাথে ট্র্যাক করছেন তাদের জন্য, আমি RecyclerView এর চারপাশে একটি কাঠামো তৈরি করার আমার দৃষ্টিভঙ্গি সম্পর্কে বলব।
ফ্রেমওয়ার্কের একটি বুদ্ধিমান স্তরের মাপযোগ্যতা প্রদান করা উচিত। তাই প্রধান পরিস্থিতিগুলি ন্যূনতম পরিমাণে বয়লারপ্লেট দিয়ে পরিচালনা করা উচিত, তবে ব্যবহারকারী যদি কাস্টম কিছু তৈরি করতে চান তবে এটিকে বাধা দেওয়া উচিত নয়।
রিসাইক্লারভিউ-এর সাথে কাজ করার সময় এটি সর্বোত্তম অনুশীলনটি তালিকায় একটি ডেটা আপডেট করার জন্য DiffUtil
API ব্যবহার করে। আমরা এখানে একটি চাকা পুনরায় উদ্ভাবন করতে যাচ্ছি না, তাই আমরা DiffUtil
ব্যবহার করার উপর ফোকাস করব এবং এটি বাস্তবায়নের জন্য একটি অনুষ্ঠান কমানোর চেষ্টা করব।
একটি তালিকায় কয়েক ধরনের ভিউ থাকাটাই স্বাভাবিক। মোবাইল ডিভাইসের স্ক্রীন উচ্চতা দ্বারা সীমাবদ্ধ এবং বিভিন্ন ডিভাইসের বিভিন্ন স্ক্রীনের আকার রয়েছে। সুতরাং এমনকি আপনার সামগ্রী স্ক্রোল না করে একটি ডিভাইসে স্ক্রীন করার জন্য উপযুক্ত, এটি অন্য ডিভাইসে স্ক্রোল করার প্রয়োজন হতে পারে।
সেজন্য RecyclerView-এর উপরে কিছু স্ক্রিন তৈরি করা ভালো ধারণা হতে পারে।
সজ্জা তালিকা জন্য একটি খুব সাধারণ ক্ষেত্রে. এবং যদিও আপনি আইটেম লেআউটে সাজসজ্জা একত্রিত করতে পারেন, যা দেখতে সোজা হতে পারে, এটি খুব নমনীয় নয়: একই আইটেম প্রকারের জন্য বিভিন্ন স্ক্রিনে বিভিন্ন সজ্জা বা এমনকি একটি তালিকায় আইটেমের অবস্থানের উপর নির্ভর করে বিভিন্ন সজ্জা প্রয়োজন হতে পারে।
প্রথম বিকল্পটি স্ক্র্যাচ থেকে সবকিছু তৈরি করা হয়। এটি বাস্তবায়নের উপর সম্পূর্ণ নিয়ন্ত্রণ দিচ্ছে, কিন্তু আমরা সর্বদা এটি করতে পারি।
তৃতীয় পক্ষের ওপেন-সোর্স ফ্রেমওয়ার্কের একটি গুচ্ছ রয়েছে যা কম বয়লারপ্লেটের সাথে RecyclerView-এর সাথে কাজ করার অনুমতি দেয়। আমি সবগুলি গণনা করতে যাচ্ছি না, তবে তাদের মধ্যে দুটি সম্পূর্ণ বিপরীত পদ্ধতির সাথে দেখাতে চাই:
রিসাইক্লারভিউ দিয়ে জটিল স্ক্রিন তৈরির জন্য AirBnb থেকে সমাধান। এটি একটি বয়লারপ্লেটকে যতটা সম্ভব কমাতে একগুচ্ছ নতুন API এবং এমনকি একটি কোড জেনারেশন যোগ করছে।
কিন্তু আমি এই অনুভূতি থেকে পরিত্রাণ পেতে পারি না যে ফলাফলের কোডটি বিদেশী দেখাচ্ছে। এছাড়াও, একটি টীকা প্রক্রিয়াকরণ জটিলতার আরেকটি স্তর যোগ করে।
ক্ষুদ্র লাইব্রেরি, যেটি আসলে একটি সংক্ষিপ্ত গুচ্ছ ফাংশন এবং ইন্টারফেস সরবরাহ করে যা আপনাকে আপনার ভিন্নধর্মী তালিকাকে অর্পিত অ্যাডাপ্টারের সেটে বিভক্ত করার একটি উপায় প্রস্তাব করে। আসলে, আপনি তুলনামূলকভাবে স্বল্প সময়ের মধ্যে এই ধরনের নিজস্ব সমাধান তৈরি করতে পারেন।
আমি ছোট নেটিভ সমাধান পছন্দ করি যা সমস্ত আচরণকে নখদর্পণে রাখতে যথেষ্ট সহজ এবং এটি সমস্ত বাস্তবায়নের বিবরণ কার্পেটের নীচে লুকিয়ে রাখে না। তাই আমি বিশ্বাস করি যে অ্যাডাপ্টারডেলাগেটস এবং এর নীতিগুলি আমাদের কাঠামোর ভিত্তির জন্য একটি ভাল প্রার্থী।
শুরুর জন্য খুব মৌলিক জিনিস তালিকা আইটেম একটি সাধারণ ঘোষণা. বাস্তব বিশ্বের তালিকা আইটেমগুলি খুব আলাদা, কিন্তু একমাত্র জিনিস যা আমরা আত্মবিশ্বাসী - আমাদের এটি তুলনা করতে সক্ষম হওয়া উচিত।
আমি DiffUtil.ItemCallback
এর API উল্লেখ করার পরামর্শ দিই
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 } }
বেশিরভাগ ক্ষেত্রে isContentTheSame
বেশ সহজ হবে (একটি সমতা পরীক্ষা), যদি ListItem এর বাস্তবায়ন একটি data class
হবে।
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) }
এবং এখানে চূড়ান্ত পদক্ষেপ হল ডিফল্ট অ্যাডাপ্টারের একটি ঘোষণা যা AdapterDelegates থেকে AsyncListDifferDelegationAdapter
প্রসারিত করে:
class CompositeListAdapter : AsyncListDifferDelegationAdapter<ListItem>(DefaultDiffUtil)
AdapterDelegates লাইব্রেরি একটি নির্দিষ্ট ভিউ টাইপের জন্য প্রতিনিধি ঘোষণা করার একটি সহজ উপায় প্রদান করে। শুধুমাত্র যে জিনিসটি আমরা উন্নত করতে পারি তা হল একটি বয়লারপ্লেট কিছুটা কমানো:
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
এর API-এর কারণে, সেটিং অলঙ্করণগুলি ডেটা সেট করা থেকে তালিকায় আলাদাভাবে তৈরি করা হয়। আপনার যদি সাজসজ্জার জন্য কিছু যুক্তি থাকে যেমন: 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
এপিআই-এর অনুকরণের সাথে একটি পুরানো প্রমাণিত পদ্ধতি ব্যবহার করতে পারি এবং একটি বাস্তবায়ন অর্পণ করতে পারি।
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
এর চারপাশে একটি তালিকা কাঠামো তৈরি করার একটি পদ্ধতি আবিষ্কার করেছে। ডেলিগেশন তালিকা আইটেম এবং তালিকা সজ্জা মোড়ানো জন্য একটি ভিত্তি নীতি হিসাবে ব্যবহৃত.
দেশীয়তা। বাস্তবায়ন তৃতীয় পক্ষের লাইব্রেরির উপর নির্ভর করে না, যা ভবিষ্যতে নমনীয়তা কমাতে পারে। অ্যাডাপ্টার ডেলিগেটসকে ঠিক এই মানদণ্ডগুলি বিবেচনা করার জন্য বেছে নেওয়া হয়েছিল - এটি খুব সহজ এবং স্বাভাবিক এবং সমস্ত আচরণ আপনার নখদর্পণে।
পরিমাপযোগ্যতা। অর্পণ এবং উদ্বেগ নীতি বিচ্ছেদ ধন্যবাদ, আমরা আইটেম প্রতিনিধি বা সজ্জা ড্রয়ার বাস্তবায়ন decople করতে সক্ষম.
একটি বিদ্যমান প্রকল্পে ক্রমবর্ধমানভাবে সংহত করা সহজ
দ্বিতীয় অংশে, আমরা সমস্ত ধরণের তালিকা সজ্জা নিয়ে আলোচনা করব যা আমরা সম্ভবত একটি অ্যান্ড্রয়েড অ্যাপ্লিকেশনে দেখতে আশা করি।