Versi sederhana dari tampilan pendaur ulang beraneka ragam pada template Pengunjung

Enam bulan telah berlalu sejak saya berguling dari Pascaldi kotlin dan jatuh cinta dengan pengembangan android, dan sekarang saya sudah mengizinkan diri saya untuk secara terbuka memanjat ide-ide saya ke biara orang lain. Tapi ada alasannya. Setelah melihat di obrolan profil pertanyaan apa yang paling sering muncul untuk pengembang android, dan tidak hanya untuk pemula, saya menyadari bahwa dalam banyak kasus, ketika seseorang menemukan kesalahan yang tidak dapat dia pahami, karena dia tidak dapat memahami penjelasan rekan-rekan dari obrolan atau pertanyaan utama mereka, alasannya adalah penggunaan tanpa pertimbangan dari potongan kode atau pustaka yang sudah jadi. Namun, mengandalkan contoh kode siap pakai yang tidak berfungsi untuk mereka (dan di area ini, kode yang ditulis lebih dari setahun yang lalu, secara default, memerlukan pembaruan atau pengerjaan ulang secara umum, dan ini berlaku untuk kode dengan stack overflow, panduan pustaka , dan bahkan panduan dari Google sendiri), mereka tidak memahami alasan terjadinya kesalahan atau perilaku yang berbeda,karena mengandalkan perpustakaan sepertiRuang Cina , tanpa mencoba memahami arsitektur dan prinsip kerjanya.





Karena masalah tampilan pendaur ulang sangat sering muncul, saya ingin memahami sedikit tentang cara membuat kode yang dapat diperluas dan bersih sendiri untuk menampilkan daftar multi-item dalam suatu aplikasi.






Mempelajari pola arsitektur pengembangan android, saya melatih diri untuk mencari jawaban terlebih dahulu di server panduan pengembang Google . Namun terkadang di sana, terutama di codelab pelatihan, ada contoh kode yang lebih disederhanakan daripada yang dirancang untuk keserbagunaan, kemurnian, dan ekstensibilitas.





Dalam hal ini, saya perlu menggunakan tampilan pendaur ulang yang mewah untuk menampilkan daftar item dengan markup dan logika internal yang berbeda. Semua aplikasi modern didasarkan pada ide ini - dari pengirim pesan instan dan umpan media sosial hingga aplikasi perbankan. Selain itu, menggabungkan dengan cepat menggunakan pendekatan reaktif dari elemen visual yang berbeda dari daftar tampilan daur ulang daripada markup tata letak manual adalah jembatan ke dunia antarmuka fungsional-deklaratif, yang ditawarkan kepada kami di Jetpack Compose, dan yang lebih cepat atau nanti Google akan dengan lembut menawarkan untuk beralih.





Codelab, recycler view , sealed . . , ,- , , . , /, ( , SOLID, ).





, Google id data- : id Long.MIN_VALUE, id data-. : data-, , . recycler view .





. adapter delegates, groupie epoxy. , . , , . , , , .





:





  • , , 10%, ;





  • : , - data- .





, , , , , , .





, , , , recycler view , . , .





, .

recycler view, ListAdapter, , :





  • getItemType - , ( , Google );





  • onCreateViewHolder - , ViewHolder , ( );





  • onBindViewHolder - , ( ) ViewHolder, .





recycler view , recycler view , , , , DiffUtil-.





DiffCallback,
class BaseDiffCallback : DiffUtil.ItemCallback<HasStringId>() {
    override fun areItemsTheSame(oldItem: HasStringId, newItem: HasStringId): Boolean = oldItem.id == newItem.id
    override fun areContentsTheSame(oldItem: HasStringId, newItem: HasStringId): Boolean = oldItem == newItem
}
      
      



, areContentsTheSame , areItemsTheSame true. HasStringId, id String equals, data- , view. Data- id, DiffUtil , ui- id .





, , . , :





interface ViewHoldersManager {
    fun registerViewHolder(itemType: Int, viewHolder: ViewHolderVisitor)
    fun getItemType(item: Any): Int
    fun getViewHolder(itemType: Int): ViewHolderVisitor
}
      
      



recycler view:





object ItemTypes {
    const val UNKNOWN = -1
    const val HEADER = 0
    const val TWO_STRINGS = 1
    const val ONE_LINE_STRINGS = 2
    const val CARD = 3
}
      
      



"" adapter delegates, . .





hilt data binding, : ui. , , :





@Module
@InstallIn(FragmentComponent::class)
object DiModule {

    @Provides
    @FragmentScoped
    fun provideAdaptersManager(): ViewHoldersManager = ViewHoldersManagerImpl().apply {
        registerViewHolder(ItemTypes.HEADER, HeaderViewHolder())
        registerViewHolder(ItemTypes.ONE_LINE_STRINGS, OneLine2ViewHolder())
        registerViewHolder(ItemTypes.TWO_STRINGS, TwoStringsViewHolder())
        registerViewHolder(ItemTypes.CARD, CardViewHolder())
    }
}
      
      



:





ard item
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable name="card" type="ru.alexmaryin.recycleronvisitor.data.ui_models.CardItem" />
    </data>

    <androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_margin="8dp"
        card_view:cardBackgroundColor="@color/cardview_shadow_end_color"
        card_view:cardCornerRadius="15dp">

        <ImageView
            android:id="@+id/card_background_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:scaleType="centerCrop"
            tools:ignore="ContentDescription"
            tools:src="@android:mipmap/sym_def_app_icon" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:background="@android:drawable/screen_background_dark_transparent"
            android:orientation="vertical"
            android:padding="16dp">

            <TextView
                android:id="@+id/card_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:maxLines="1"
                android:paddingTop="8dp"
                android:paddingBottom="8dp"
                android:textAllCaps="true"
                android:textColor="#FFFFFF"
                android:textStyle="bold"
                tools:text="Cart title"
                android:text="@{card.title}"/>

            <TextView
                android:id="@+id/txt_discription"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:maxLines="2"
                android:textColor="#FFFFFF"
                tools:text="this is a simple discription with losts of text lorem ipsum dolor sit amet,
            consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
                android:text="@{card.description}"/>

        </LinearLayout>
    </androidx.cardview.widget.CardView>
</layout>
      
      



One line item
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable name="model" type="ru.alexmaryin.recycleronvisitor.data.ui_models.OneLineItem2" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/text1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:paddingStart="8dp"
            android:text="@{model.left}"
            android:textAlignment="textEnd"
            android:textAppearance="?attr/textAppearanceListItem"
            android:textColor="@color/cardview_dark_background"
            app:layout_constraintEnd_toStartOf="@+id/divider"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="RtlSymmetry,TextContrastCheck"
            tools:text="Left text" />

        <ImageView
            android:id="@+id/divider"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:alpha="0.6"
            android:padding="5dp"
            android:scaleType="center"
            android:scaleX="0.5"
            android:scaleY="0.9"
            android:src="@drawable/ic_outline_waves_24"
            android:visibility="visible"
            app:layout_constraintBottom_toBottomOf="@+id/text1"
            app:layout_constraintEnd_toStartOf="@+id/text2"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/text1"
            app:layout_constraintTop_toTopOf="@+id/text1"
            app:srcCompat="@drawable/ic_outline_waves_24"
            tools:ignore="ContentDescription"
            tools:visibility="visible" />

        <TextView
            android:id="@id/text2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:paddingEnd="8dp"
            android:text="@{model.right}"
            android:textAppearance="?attr/textAppearanceListItem"
            app:layout_constraintBottom_toBottomOf="@+id/divider"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/divider"
            app:layout_constraintTop_toTopOf="@+id/divider"
            tools:ignore="RtlSymmetry"
            tools:text="Right text" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
      
      



Two line item
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable name="model" type="ru.alexmaryin.recycleronvisitor.data.ui_models.TwoStringsItem" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/listPreferredItemHeight"
        android:mode="twoLine"
        android:paddingStart="?attr/listPreferredItemPaddingStart"
        android:paddingEnd="?attr/listPreferredItemPaddingEnd">

        <TextView
            android:id="@+id/text1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="@{model.caption}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:textAppearance="?attr/textAppearanceListItem" />

        <TextView
            android:id="@id/text2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{model.details}"
            app:layout_constraintTop_toBottomOf="@id/text1"
            app:layout_constraintStart_toStartOf="parent"
            android:textAppearance="?attr/textAppearanceListItemSecondary" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

      
      



Header item
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="headerItem"
            type="ru.alexmaryin.recycleronvisitor.data.ui_models.RecyclerHeader" />
    </data>

    <TextView
        style="@style/regularText"
        android:id="@+id/header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#591976D2"
        android:textAlignment="center"
        android:textStyle="italic"
        android:text="@{headerItem.text}"/>
</layout>
      
      



, :





interface ViewHolderVisitor {
    val layout: Int
    fun acceptBinding(item: Any): Boolean
    fun bind(binding: ViewDataBinding, item: Any, clickListener: AdapterClickListenerById)
}
      
      



( acceptVisitor execute, , ) - acceptBinding bind, layout, .





accept : ( ) , , , accept, , true. , , , . - (accept = true), - , .





, , . :





class ViewHoldersManagerImpl : ViewHoldersManager {

    private val holdersMap = emptyMap<Int, ViewHolderVisitor>().toMutableMap()

    override fun registerViewHolder(itemType: Int, viewHolder: ViewHolderVisitor) {
        holdersMap += itemType to viewHolder
    }

    override fun getItemType(item: Any): Int {
        holdersMap.forEach { (itemType, holder) -> 
            if(holder.acceptBinding(item)) return itemType
        }
        return ItemTypes.UNKNOWN
    }

    override fun getViewHolder(itemType: Int) = holdersMap[itemType] ?: throw TypeCastException("Unknown recycler item type!")
}
      
      



( ):





class CardViewHolder : ViewHolderVisitor {
  
    override val layout: Int = R.layout.card_item

    override fun acceptBinding(item: Any): Boolean = item is CardItem

    override fun bind(binding: ViewDataBinding, item: Any, clickListener: AdapterClickListenerById) {
        with(binding as CardItemBinding) {
            card = item as CardItem
            Picasso.get().load(item.image).into(cardBackgroundImage)
        }
    }
}
      
      



as . -, , : accept , CardItem, bind . : layout, binding data binding . -, , idea android studio ?





, recycler view,- , , , :





class BaseListAdapter(
    private val clickListener: AdapterClickListenerById,
    private val viewHoldersManager: ViewHoldersManager
) : ListAdapter<HasStringId, BaseListAdapter.DataViewHolder>(BaseDiffCallback()) {

    inner class DataViewHolder(
        private val binding: ViewDataBinding,
        private val holder: ViewHolderVisitor
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: HasStringId, clickListener: AdapterClickListenerById) =
            holder.bind(binding, item, clickListener)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataViewHolder =
        LayoutInflater.from(parent.context).run {
            val holder = viewHoldersManager.getViewHolder(viewType)
            DataViewHolder(DataBindingUtil.inflate(this, holder.layout, parent, false), holder)
        }

    override fun onBindViewHolder(holder: DataViewHolder, position: Int) = holder.bind(getItem(position), clickListener)

    override fun getItemViewType(position: Int): Int = viewHoldersManager.getItemType(getItem(position))
}
      
      



view, :





// -   :
// private val viewModel: MainViewModel by viewModels()
// private lateinit var recycler: RecyclerView
// @Inject lateinit var viewHoldersManager: ViewHoldersManager
// private val items = mutableListOf<HasStringId>()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        recycler = requireActivity().findViewById(R.id.recycller)
        val itemsAdapter = BaseListAdapter(AdapterClickListenerById {}, viewHoldersManager)
        itemsAdapter.submitList(items)
        recycler.apply {
            layoutManager = LinearLayoutManager(requireContext())
            addItemDecoration(DividerItemDecoration(requireContext(), (layoutManager as LinearLayoutManager).orientation))
            adapter = itemsAdapter
        }
        populateRecycler()
    }

private fun populateRecycler() {
     lifecycleScope.launch {
        viewModel.getItems().flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
           .collect { items.add(it) }
     }
   }
      
      



"" , recycler view . :





  • -;





  • sealed ;





  • data- / , view data ;





  • - ;





  • , SOLID ;





  • , (YAGNI).





Tentu saja, penerapan saya masih memiliki cara untuk diperbaiki dan dikembangkan. Anda dapat, seperti dalam groupie, menambahkan pengelompokan elemen dan visual collapse. Anda dapat meninggalkan data binding atau melengkapi adaptor dengan opsi untuk view binding atau peningkatan markup biasa dengan semua findViewById favorit Anda di view holder. Dan kemudian kodenya akan berubah menjadi pustaka yang sama, yang sudah ada begitu banyak dan sebagainya. Untuk tujuan khusus saya, pada saat dibutuhkan, opsi dengan Pengunjung sederhana lebih dari cukup:





Tolong jangan menilai dengan tegas, karena ini adalah kelahiran pertama saya di dunia android. Kode contoh lengkap dari teks artikel akan tersedia di repositori github .








All Articles