JavaRush /Blog Java /Random-ES /Listas complejas simplificadas
Paul Soia
Nivel 26
Kiyv

Listas complejas simplificadas

Publicado en el grupo Random-ES
Hola a todos. Decidí plantear el tema de las listas complejas en Android. Este tema me desconcertó al principio. Me pareció muy difícil, pero en realidad todo es más sencillo. Y creo que cualquiera que se enfrente ahora a este problema encontrará útil el artículo. Todos los ejemplos están escritos en Kotlin. Intenté escribir comentarios en todas partes y hacerlo lo más claro posible para quienes escriben en Java. Aquí está nuestra estructura de clases: Listas complejas simplificadas - 1MainActivity- Esta es la clase principal. ListAdapter- Aquí vinculamos nuestra lista para ver elementos. ViewHolder(hay varios): este es nuestro marcado para cada tipo de elemento. Las clases de elementos son datos para una lista (pojo). Los archivos xml son marcas ( activity_mainpara la pantalla principal, el resto para cada tipo de elemento de la lista). Lo primero que debemos hacer es agregar una dependencia para que podamos usar listas (por alguna razón, esta dependencia falta al crear un proyecto). En el archivo build.gradledel bloque dependenciesagregamos la línea:
implementation "androidx.recyclerview:recyclerview:1.1.0"
A continuación encontramos el archivo activity_main.xml: Listas complejas simplificadas - 2Aquí sólo está nuestro elemento para listas - RecyclerView. Agregamos sangrías y formato al gusto. A continuación creamos la clase BaseViewHolder. Será abstracto, ya que todas nuestras clases heredarán de él ViewHolder. Es muy posible prescindir de él, pero aún así recomiendo asegurarse de que todas las clases heredadas tengan la misma estructura. (en Kotlin, extendsy implementse usa en su lugar :. Como en Java, aquí puedes heredar de una clase e implementar muchas interfaces) A ​​continuación, creamos nuestras clases de datos ItemTitle: ItemGoogley ItemApple. En Java, estas son clases ordinarias con un constructor y captadores.
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
Centrémonos aquí en la interfaz ListMarker.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
Todas nuestras clases de datos implementan esta interfaz para convertirlas al mismo tipo. Necesitaremos esto más adelante para nuestra lista. A continuación, creamos nuestros visualizadores para cada elemento de la lista. No olvides que cada uno de ellos hereda de BaseViewHolder. En estas clases especificamos qué elemento viewcorresponde al campo de las clases de datos.
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()
    }
}
No hay ninguna lógica particular aquí, todo es extremadamente simple. Además, cada visor tiene su propio diseño: list_item_title.xmlListas complejas simplificadas - 3list_item_google.xmlListas complejas simplificadas - 4list_item_apple.xmlListas complejas simplificadas - 5ahora pasemos a la parte más difícil: 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
    //Cómo вариант можно было сделать 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<*>
Cuando heredamos nuestra clase de RecyclerView.Adapter, necesitaremos anular tres métodos: onCreateViewHolder, getItemCount, onBindViewHolder. onCreateViewHolder-Aquí inicializamos nuestras clases ViewHolder. getItemCount— este método es responsable del tamaño de la lista. onBindViewHolder-Aquí pasamos los elementos de la lista a nuestras clases ViewHolder. Para una lista normal con un tipo de elemento, esto sería suficiente. Pero para diferentes tipos todavía necesitamos redefinir el método getItemViewType(para esto usamos las constantes que están en la parte superior de nuestra clase de adaptador. En Java puedes usar finalvariables para esto). También en Java, onBindViewHolderen lugar de una expresión, whenpuedes usar un archivo if. Y finalmente, pasemos a nuestra clase principal: 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>
Aquí es donde se inicializa nuestro RecyclerViewand ListAdapter. Este es un código bastante repetitivo que muchos han encontrado. En el método, stubDatallené la lista con datos (como puede ver, datos con diferentes elementos) y pasé esta lista al adaptador. A continuación, iniciamos nuestra aplicación y deberíamos ver algo como esto en nuestra pantalla: Listas complejas simplificadas - 6Como puede ver, hay diferentes elementos en una lista, que era nuestro objetivo. PD: Olvidé mencionar el Extension. Esto es lo que parece:
//это расширение для класса ViewGroup. Теперь мы можем по всему проекту использовать
//короткое inflate en lugar de длинного LayoutInflater.from(context).inflate
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View =
    LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
Esto no es una clase, sino un archivo, por lo que no contiene ningún nombre. Pero ahora podemos usarlo en el adaptador simplemente inflateen lugar de una estructura larga. Desafortunadamente, esto no está disponible para Java, así que escriba LayoutInflater.from(context).inflate. Eso es todo. Hasta la próxima :) Enlace a GitHub
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION