Jetpack Compose günümüzde benimseniyor ve bunu kesinlikle hak ediyor. Bu, kullanıcı arayüzü geliştirmede gerçek bir paradigma değişimidir. Orada o kadar çok liste oluşturma kalıpları ortadan kaldırıldı ki. Ve çok güzel. Android Pek çok projenin hala View çerçevesini kullandığına ve dolayısıyla liste oluşturmak için RecyclerView'ı kullandığına inanıyorum. Gördüğüm kadarıyla bunun birkaç nedeni var: RecyclerView çevresindeki bazı şirket içi özel çözümler Compose ile kolayca eşleşmez, bu nedenle geçiş maliyetleri yüksektir Geliştiriciler henüz Compose'a yeterince aşina değil Compose'un performansına dair güven yok (henüz) Ama yine de, Compose gelecek ve bu yüzden başlıkta “vintage” kelimesini kullanıyorum. Vintage eski ama altın anlamına gelir. Bu makale, RecyclerView ile olan deneyimlerimin bir derlemesinin sonucudur ve hala bu yolda ilerleyenler için, RecyclerView etrafında bir çerçeve oluşturma vizyonumu anlatacağım. Gereksinimler Çerçeve makul düzeyde ölçeklenebilirlik sağlamalıdır. Bu nedenle, ana senaryolar minimum miktarda ortak metinle ele alınmalı, ancak kullanıcının özel bir şey yaratmak istemesinin önüne geçmemelidir. RecyclerView ile çalışırken temel senaryo setini bir sonraki şekilde tanımlayacağım: Verilerin etkili güncellenmesi. RecyclerView ile çalışırken bilinen en iyi uygulama, listedeki bir veriyi güncellemek için API'yi kullanmaktır. Burada tekerleği yeniden icat etmeyeceğiz, bu nedenle yararlanmaya odaklanacağız ve aynı zamanda bunun uygulanması için bir töreni azaltmaya çalışacağız. DiffUtil DiffUtil Heterojen listelerin tanımlanması. Bir listede birden fazla görünüm türünün bulunması doğaldır. Mobil cihazların ekranları yükseklik nedeniyle sınırlıdır ve farklı cihazlar farklı ekran boyutlarına sahiptir. Dolayısıyla içeriğiniz kaydırma yapmadan bir cihazda ekrana sığsa bile, başka bir cihazda kaydırılması gerekebilir. Bu nedenle RecyclerView'ın üst kısmında bazı ekranlar geliştirmek iyi bir fikir olabilir. Liste öğelerini dekore etme Dekorasyonlar listeler için çok yaygın bir durumdur. Öğe düzenleriyle birleştirilmiş dekorasyonlara sahip olabilseniz de, basit görünse de çok esnek değildir: aynı öğe türleri farklı ekranlarda farklı süslemeler gerektirebilir, hatta listedeki öğe konumuna bağlı olarak farklı süslemeler gerektirebilir. Temel seçenekleri İlk seçenek her şeyi sıfırdan yapmaktır. Uygulama üzerinde tam kontrol sağlıyor ama bunu her zaman yapabiliriz. RecyclerView ile daha az standartla çalışmaya izin veren bir dizi üçüncü taraf açık kaynaklı çerçeve vardır. Hepsini saymayacağım ama tamamen zıt yaklaşımlara sahip ikisini göstermek istiyorum: Epoksi RecyclerView ile karmaşık ekranlar oluşturmaya yönelik AirBnb çözümü. Standartları mümkün olduğunca azaltmak için bir dizi yeni API ve hatta bir kod üretimi ekliyor. Ancak ortaya çıkan kodun yabancı göründüğü hissinden kurtulamıyorum. Ayrıca, başka bir karmaşıklık katmanı ekleyen bir açıklama işleme. BağdaştırıcıDelegeler Aslında size yalnızca heterojen listenizi yetkilendirilmiş bağdaştırıcılar kümesine bölmenin bir yolunu öneren kısa bir dizi işlev ve arayüz sağlayan küçük kütüphane. Aslında nispeten kısa sürede böyle bir çözüm yaratabilirsiniz. Tüm davranışları parmaklarınızın ucunda tutacak kadar basit olan ve tüm uygulama ayrıntılarını halının altına saklamayan küçük yerel çözümleri seviyorum. Bu nedenle AdapterDelagates'in ve ilkelerinin çerçevemizin temeli için iyi bir aday olduğuna inanıyorum. Öğeleri listele Başlangıç için en temel şey, liste öğelerinin ortak bir beyanıdır. Gerçek dünyadaki liste öğeleri çok farklıdır ancak güvendiğimiz tek şey, onu karşılaştırabilmemizdir. API'sine başvurmanızı öneririm DiffUtil.ItemCallback interface ListItem { fun isItemTheSame(other: ListItem): Boolean fun isContentTheSame(other: ListItem): Boolean { return this == other } } Çerçevedeki bir liste öğesinin minimalist (ve dolayısıyla sağlam IMO) beyanıdır. Yöntemlerin anlambiliminin aşağıdakileri kullanarak DiffUtil ile aynı olması amaçlanmıştır: kimliğini kontrol eder ve isItemTheSame other this verileri kontrol eder ve isContentTheSame other this Sabit ve benzersiz bir kimlik sağlamanın en yaygın yolu, sunucudan gelen tanımlayıcıları kullanmaktır. O halde olası kalıpları biraz azaltmak için soyut bir uygulamaya bakalım: 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 uygulamaları bir olacaksa, çoğu durumda oldukça basit olacaktır (eşitlik kontrolü). data class isContentTheSame Listenizde sunucu verilerinin yansıması olmayan ve aklı başında bir kimliğe sahip olmayan öğelerin bulunduğu durumlarda ayrı tutmak yine de mantıklıdır. Örneğin, bir listedeki öğeler olarak alt bilginiz ve üst bilginiz varsa veya belirli türde tek bir öğeniz varsa: ListItem data class HeaderItem(val title: String) : ListItem { override fun isItemTheSame(other: ListItem): Boolean = other is HeaderItem } Önerilen yaklaşım, çok doğal ve tek bir geri çağırma uygulamasına sahip olmamızı sağlar: 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) } Ve buradaki son adım, AdapterDelegates'ten genişleten varsayılan bağdaştırıcının bildirimidir: AsyncListDifferDelegationAdapter class CompositeListAdapter : AsyncListDifferDelegationAdapter<ListItem>(DefaultDiffUtil) Heterojen listeler AdapterDelegates kitaplığı, belirli bir görünüm türü için delegeleri bildirmenin kullanışlı bir yolunu sağlar. İyileştirebileceğimiz tek şey standart metni biraz azaltmak: 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) Gösterim amacıyla, bir metin alanı içeren bazı bildiriminin nasıl görünebileceğine dair bir örnek düşünelim: TitleListItem data class TitleListItem( override val id: String, val title: String, ) : DefaultListItem() ve bunun için delege 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 } } } Dekore edilmiş listeler Başlangıçta API'si nedeniyle, ayar dekorasyonları listeye veri ayarından ayrı olarak yapılır. Dekorasyon için bazı mantığınız varsa: veya aksi takdirde dekorasyon oluşturmak bir acı haline gelir. 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 Genellikle takımlar yapılandırılabilen “tanrı” dekoratörüyle gelir: 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() Uygulama ayrıntılarını ve ne kadar kırılgan ve ölçeklenebilir olmadığını yalnızca hayal edebilirsiniz. Bazı API'lerini taklit ederek ve bir uygulamayı devrederek kanıtlanmış eski bir yaklaşımı kullanabiliriz. RecyclerView O halde bir veriyle başlıyorum: interface Decoration Gelecekte sunulacak olan dekorasyonlar için arayüz işaretçisi. interface HasDecorations { var decorations: List<Decoration> } Bu arayüz, veri öğelerimiz tarafından, bu özel öğenin belirli bir dekorasyon listesiyle süslenmek istediğini bildirmek için uygulanmalıdır. Gerektiğinde çalışma zamanında değiştirilebilmesi için dekorasyonlar göstermektedir. var Çoğu zaman bir öğenin tek bir dekorasyonu vardır, dolayısıyla tek bir öğeyi a sararak ortak metni azaltmak için şu manevrayı yapabiliriz: 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") } } } Sonraki aşama API'sini taklit edecek: 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 ) } Gördüğünüz gibi tek bir farkla tamamen tekrarlıyor – artık dekorasyon tipinin ve örneğinin geldiğinin farkında. ItemDecoration olası uygulamaları ikinci makalenin konusu, şimdi sadece arayüze ve dekorasyonun sunumunda nasıl ele alınması gerektiğine odaklanalım. 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 } } Oluşturulan çekmeceler için önbellek dekorasyon uygulamasına özel bir şey değildir, yalnızca verilen görünüm için bağdaştırıcı konumunu doğru şekilde çözümlemeye yönelik bir yöntemdir getAdapterPositionForView() örneğinin dekorasyonlarını yineleme HasDecorations Bu çalışma “Alakart Dekorasyonlar” başlıklı ikinci yazımızın konusudur. Çözüm Bu makale, etrafında bir liste çerçevesi oluşturmaya yönelik bir yaklaşım keşfetti. Yetkilendirme, liste öğelerinin ve liste süslemelerinin paketlenmesinde temel prensip olarak kullanılır. RecyclerView Önerilen çözümün ana avantajlarının şunlar olduğuna inanıyorum: Yerlilik. Uygulama, gelecekte esnekliği azaltabilecek üçüncü taraf kütüphanelere bağlı değildir. AdapterDelegates tam olarak bu kriterleri dikkate almak üzere seçilmiştir; çok basit ve doğaldır ve tüm davranışlar parmaklarınızın ucundadır. Ölçeklenebilirlik. Delegasyon ve endişelerin ayrılması ilkesi sayesinde, öğe delegelerinin veya dekorasyon çekmecelerinin uygulamalarını ayrıştırabiliyoruz. Mevcut bir projeye aşamalı olarak entegre etmek kolaydır İkinci bölümde muhtemelen bir Android uygulamasında görmeyi beklediğimiz tüm liste dekorasyon türlerini tartışacağız.