JavaRush /Java Blog /Random-IT /Elenchi complessi resi semplici
Paul Soia
Livello 26
Kiyv

Elenchi complessi resi semplici

Pubblicato nel gruppo Random-IT
Ciao a tutti. Ho deciso di sollevare l'argomento degli elenchi complessi in Android. Questo argomento all'inizio mi ha lasciato perplesso. Mi è sembrato molto difficile, ma in realtà è tutto più semplice. E penso che chiunque si trovi ora ad affrontare questo problema troverà utile l'articolo. Tutti gli esempi sono scritti in Kotlin. Ho provato a scrivere commenti ovunque e a renderlo il più chiaro possibile per chi scrive in Java. Quindi ecco la struttura della nostra classe: Elenchi complessi resi semplici - 1MainActivity- Questa è la classe principale. ListAdapter- qui leghiamo la nostra lista per visualizzare gli elementi. ViewHolder(ce ne sono diversi): questo è il nostro markup per ogni tipo di elemento. Le classi di elementi sono dati per un elenco (pojo). I file XML sono markup ( activity_mainper la schermata principale, il resto per ogni tipo di elemento dell'elenco). La prima cosa che dobbiamo fare è aggiungere una dipendenza in modo da poter utilizzare gli elenchi (per qualche motivo questa dipendenza manca durante la creazione di un progetto). Nel file build.gradlenel blocco dependenciesaggiungiamo la riga:
implementation "androidx.recyclerview:recyclerview:1.1.0"
Successivamente troviamo il file activity_main.xml: Elenchi complessi resi semplici - 2Qui c'è solo il nostro elemento per le liste - RecyclerView. Aggiungiamo eventuali rientri e formattazioni a piacere. Successivamente creiamo la classe BaseViewHolder. Sarà astratto, poiché tutte le nostre classi erediteranno da esso ViewHolder. È possibile farne a meno, ma consiglio comunque di assicurarsi che tutte le classi ereditarie abbiano la stessa struttura. (in Kotlin, extendse implementviene utilizzato invece :. Come in Java, qui puoi ereditare da una classe e implementare molte interfacce) Successivamente, creiamo le nostre classi di dati: ItemTitle, ItemGooglee ItemApple. In Java, queste sono classi ordinarie con un costruttore e getter.
data class ItemGoogle(
    val name: String,
    val product: String,
    val version: String,
    val isUse: Boolean
) : ListMarker
data class ItemApple(
    val name: String,
    val country: String,
    val year: Int
) : ListMarker
data class ItemTitle(
    val title: String,
    val amount: Int
) : ListMarker
Concentriamoci qui sull'interfaccia ListMarker.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
Tutte le nostre classi di dati implementano questa interfaccia per trasmetterle allo stesso tipo. Ne avremo bisogno in seguito per la nostra lista. Successivamente, creiamo i nostri viewholder per ciascun elemento dell'elenco. Non dimenticare che ognuno di essi eredita da BaseViewHolder. In queste classi specifichiamo quale elemento viewcorrisponde al campo delle classi di dati.
abstract class BaseViewHolder<t>(itemView: View) : RecyclerView.ViewHolder(itemView) {
    abstract fun bind(item: T)
}
class GoogleViewHolder(view: View) : BaseViewHolder<itemgoogle>(view) {

    override fun bind(item: ItemGoogle) {
        itemView.tvName.text = item.name
        itemView.tvProduct.text = item.product
        itemView.tvVersion.text = item.version
        if (item.isUse) {
            itemView.tvProduct.visibility = View.GONE
        } else {
            itemView.tvProduct.visibility = View.VISIBLE
        }
    }
}
class AppleViewHolder(view: View) : BaseViewHolder<itemapple>(view) {

    override fun bind(item: ItemApple) {
        //можем делать так
        itemView.tvName.text = item.name
        itemView.tvCountry.text = item.country
        itemView.tvYear.text = item.year.toString()

        /*----сверху и снизу два идентичных блока----*/

        //а можем сделать такой блок и не использовать в каждой строке itemView
        with(itemView) {
            tvName.text = item.name
            tvCountry.text = item.country
            tvYear.text = item.year.toString()
        }
    }
}
class TitleViewHolder(view: View) : BaseViewHolder<itemtitle>(view) {

    override fun bind(item: ItemTitle) {
        itemView.tvTitle.text = item.title
        itemView.tvAmount.text = item.amount.toString()
    }
}
Non esiste una logica particolare qui, tutto è estremamente semplice. Inoltre, ogni viewholder ha il proprio layout: list_item_title.xmlElenchi complessi resi semplici - 3list_item_google.xmlElenchi complessi resi semplici - 4list_item_apple.xmlElenchi complessi resi semplici - 5ora passiamo alla parte più difficile: ListAdapter.
class ListAdapter : RecyclerView.Adapter<baseviewholder<*>>() {

    companion object {
        //задаем константы для каждого типа айтема
        private const val TYPE_TITLE = 0
        private const val TYPE_GOOGLE = 1
        private const val TYPE_APPLE = 2
    }

    //здесь можно использовать обычный ArrayList
    //сюда добавляются все айтемы, которые реализовали интерфейс ListMarker
    //How вариант можно было сделать mutableListOf<any>() и обойтись без интерфейса
    private val items = mutableListOf<listmarker>()

    internal fun swapData(list: List<listmarker>) {
        items.clear()
        items.addAll(list)
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
        return when(viewType) {
            //задаем разметку для каждого типа айтема
            TYPE_TITLE -> TitleViewHolder(parent.inflate(R.layout.list_item_title))
            TYPE_GOOGLE -> GoogleViewHolder(parent.inflate(R.layout.list_item_google))
            TYPE_APPLE -> AppleViewHolder(parent.inflate(R.layout.list_item_apple))
            else -> throw IllegalArgumentException("Invalid view type")
        }
    }

    override fun getItemViewType(position: Int): Int {
        return when (items[position]) {
            is ItemTitle -> TYPE_TITLE
            is ItemGoogle -> TYPE_GOOGLE
            is ItemApple -> TYPE_APPLE
            else -> throw IllegalArgumentException("Invalid type of item $position")
        }
    }

    override fun getItemCount(): Int {
        //этот метод определяет размер списка
        return items.size
    }

    override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
        val element = items[position]
        when (holder) {
            //отправляем каждый айтем к своему ViewHolder
            is TitleViewHolder -> holder.bind(element as ItemTitle)
            is GoogleViewHolder -> holder.bind(element as ItemGoogle)
            is AppleViewHolder -> holder.bind(element as ItemApple)
            else -> throw IllegalArgumentException()
        }
    }
}
</listmarker></listmarker></any></baseviewholder<*>
Quando ereditiamo la nostra classe da RecyclerView.Adapter, dovremo sovrascrivere tre metodi: onCreateViewHolder, getItemCount, onBindViewHolder. onCreateViewHolder- qui inizializziamo le nostre classi ViewHolder. getItemCount— questo metodo è responsabile della dimensione dell'elenco. onBindViewHolder- qui passiamo gli elementi della lista alle nostre classi ViewHolder. Per un elenco normale con un tipo di elemento, questo sarebbe sufficiente. Ma per i diversi tipi dobbiamo ancora ridefinire il metodo getItemViewType(per questo usiamo le costanti che si trovano in cima alla nostra classe adattatore. In Java puoi usare finalle variabili per questo). Anche in Java, onBindViewHolderinvece di un'espressione, whenpuoi usare un normale file if. E infine, passiamo alla nostra classe principale: MainActivity.
class MainActivity : AppCompatActivity() {

    private val adapter = ListAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initRecyclerView()
    }

    private fun initRecyclerView() {
        rvList.layoutManager = LinearLayoutManager(this)

        //здесь мы задаем разделитель между айтемами, чтоб они не сливались друг с другом
        val divider = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
        rvList.addItemDecoration(divider)

        rvList.adapter = adapter
        stubData()
    }

    private fun stubData() {
        val list = mutableListOf<listmarker>()
        list.add(ItemTitle("title1", 4))
        list.add(ItemGoogle("android", "product1", "17.0v", true))
        list.add(ItemGoogle("no name", "product2", "3.1v", false))
        list.add(ItemApple("macOs", "USA", 2005))
        list.add(ItemApple("iOs", "China", 2007))
        list.add(ItemTitle("title2", 2))
        list.add(ItemGoogle("map", "product3", "23.0v", true))
        list.add(ItemApple("car", "England", 2018))
        list.add(ItemTitle("title3", 0))
        //отправляем все данные в адаптер
        adapter.swapData(list)
    }
}
</listmarker>
Qui è dove viene inizializzato il nostro RecyclerViewe ListAdapter. Questo è un codice piuttosto standard che molti hanno riscontrato. Nel metodo, stubDataho riempito l'elenco con i dati (come puoi vedere, dati con elementi diversi) e ho passato questo elenco all'adattatore. Successivamente, lanciamo la nostra applicazione e dovremmo vedere qualcosa di simile sul nostro schermo: Elenchi complessi resi semplici - 6Come puoi vedere, ci sono diversi elementi in un elenco, che era il nostro obiettivo. PS Ho dimenticato di menzionare il file Extension. Ecco come appare:
//это расширение для класса ViewGroup. Теперь мы можем по всему проекту использовать
//короткое inflate instead of длинного LayoutInflater.from(context).inflate
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View =
    LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
Questa non è una classe, ma un file, motivo per cui al suo interno non è presente alcun nome. Ma ora possiamo usarlo semplicemente nell'adattatore inflateinvece che in una struttura lunga. Sfortunatamente, questo non è previsto per Java, quindi scrivi LayoutInflater.from(context).inflate. È tutto. Alla prossima volta :) Collegamento a GitHub
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION