JavaRush /Blog Java /Random-FR /Des listes complexes simplifiées
Paul Soia
Niveau 26
Kiyv

Des listes complexes simplifiées

Publié dans le groupe Random-FR
Salut tout le monde. J'ai décidé d'aborder le sujet des listes complexes sous Android. Ce sujet m'a d'abord intrigué. Cela m'a semblé très difficile, mais en réalité tout est plus simple. Et je pense que quiconque est aujourd’hui confronté à ce problème trouvera cet article utile. Tous les exemples sont écrits en Kotlin. J'ai essayé d'écrire des commentaires partout et de le rendre aussi clair que possible pour ceux qui écrivent en Java. Voici donc notre structure de classe : Des listes complexes simplifiées - 1MainActivity- C'est la classe principale. ListAdapter- ici, nous lions notre liste pour afficher les éléments. ViewHolder(il y en a plusieurs) - c'est notre balisage pour chaque type d'élément. Les classes d'éléments sont des données pour une liste (pojo). Les fichiers XML sont des balises ( activity_mainpour l'écran principal, le reste pour chaque type d'élément de liste). La première chose que nous devons faire est d'ajouter une dépendance afin de pouvoir utiliser des listes (pour une raison quelconque, cette dépendance manque lors de la création d'un projet). Dans le fichier build.gradledu bloc dependencieson ajoute la ligne :

implementation "androidx.recyclerview:recyclerview:1.1.0"
Ensuite nous trouvons le fichier activity_main.xml: Des listes complexes simplifiées - 2Ici il n'y a que notre élément pour les listes - RecyclerView. Nous ajoutons des retraits et des mises en forme à votre goût. Ensuite, nous créons la classe BaseViewHolder. Ce sera abstrait, puisque toutes nos classes en hériteront ViewHolder. Il est tout à fait possible de s'en passer, mais je recommande quand même de s'assurer que toutes les classes héritières ont la même structure. (dans Kotlin, extendset implementest utilisé à la place :. Comme en Java, vous pouvez ici hériter d'une classe et implémenter de nombreuses interfaces) Ensuite, nous créons nos classes de données : ItemTitle, ItemGoogleet ItemApple. En Java, ce sont des classes ordinaires avec un constructeur et des getters.

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
Concentrons-nous ici sur l'interface ListMarker.

//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
Toutes nos classes de données implémentent cette interface pour les convertir au même type. Nous en aurons besoin plus tard pour notre liste. Ensuite, nous créons nos viewholders pour chaque élément de la liste. N'oubliez pas que chacun d'eux hérite de BaseViewHolder. Dans ces classes nous spécifions quel élément viewcorrespond au champ des classes de données.

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()
    }
}
Il n'y a pas de logique particulière ici, tout est extrêmement simple. De plus, chaque viewholder a sa propre disposition : list_item_title.xmlDes listes complexes simplifiées - 3list_item_google.xmlDes listes complexes simplifiées - 4list_item_apple.xmlDes listes complexes simplifiées - 5passons maintenant à la partie la plus 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<*>
Lorsque nous héritons de notre classe de RecyclerView.Adapter, nous devrons remplacer trois méthodes : onCreateViewHolder, getItemCount, onBindViewHolder. onCreateViewHolder- ici nous initialisons nos classes ViewHolder. getItemCount— cette méthode est responsable de la taille de la liste. onBindViewHolder- ici, nous transmettons les éléments de la liste à nos classes ViewHolder. Pour une liste régulière avec un seul type d’élément, cela suffirait. Mais pour différents types, nous devons encore redéfinir la méthode getItemViewType(pour cela, nous utilisons les constantes qui se trouvent en haut de notre classe d'adaptateur. En Java, vous pouvez utiliser finaldes variables pour cela). Également en Java, onBindViewHolderau lieu d'une expression, whenvous pouvez utiliser un fichier if. Et enfin, passons à notre 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>
RecyclerViewC'est ici que notre and est initialisé ListAdapter. Il s’agit d’un joli code passe-partout que beaucoup ont rencontré. Dans la méthode, stubDataj'ai rempli la liste avec des données (comme vous pouvez le voir, des données avec différents éléments) et j'ai transmis cette liste à l'adaptateur. Ensuite, nous lançons notre application, et nous devrions voir quelque chose comme ceci sur notre écran : Des listes complexes simplifiées - 6Comme vous pouvez le voir, il y a différents éléments dans une seule liste, ce qui était notre objectif. PS J'ai oublié de mentionner le Extension. Voici à quoi cela ressemble :

//это расширение для класса 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)
Ce n'est pas une classe, mais un fichier, c'est pourquoi il n'y a pas de nom à l'intérieur. Mais maintenant, nous pouvons l'utiliser simplement dans l'adaptateur inflateau lieu d'une longue structure. Malheureusement, cela n'est pas fourni pour Java, alors écrivez LayoutInflater.from(context).inflate. C'est tout. Jusqu'à la prochaine fois :) Lien vers GitHub
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION