Olá a todos. Decidi levantar o tema das listas complexas no Android. Este tópico me intrigou no início. Pareceu-me muito difícil, mas na verdade tudo é mais simples. E acho que qualquer pessoa que agora enfrente esse problema achará o artigo útil. Todos os exemplos são escritos em Kotlin. Tentei escrever comentários em todos os lugares e deixar o mais claro possível para quem escreve em Java. Então aqui está a nossa estrutura de classes:
MainActivity
- Esta é a classe principal. ListAdapter
- aqui vinculamos nossa lista para visualizar elementos. ViewHolder
(existem vários deles) - esta é a nossa marcação para cada tipo de elemento. As classes de itens são dados para uma lista (pojo). Arquivos XML são marcações ( activity_main
para a tela principal, o restante para cada tipo de elemento da lista). A primeira coisa que precisamos fazer é adicionar uma dependência para que possamos usar listas (por algum motivo essa dependência está faltando na hora de criar um projeto). No arquivo build.gradle
do bloco dependencies
adicionamos a linha:
implementation "androidx.recyclerview:recyclerview:1.1.0"
Em seguida encontramos o arquivo activity_main.xml
: Aqui existe apenas o nosso elemento para listas - RecyclerView
. Adicionamos quaisquer recuos e formatação a gosto. Em seguida criamos a classe BaseViewHolder
. Será abstrato, pois todas as nossas classes herdarão dele ViewHolder
. É bem possível passar sem ele, mas ainda recomendo garantir que todas as classes herdadas tenham a mesma estrutura. (em Kotlin, extends
e implement
é usado em vez disso :
. Como em Java, aqui você pode herdar de uma classe e implementar muitas interfaces) Em seguida, criamos nossas classes de dados ItemTitle
: ItemGoogle
e ItemApple
. Em Java, essas são classes comuns com construtor e 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
Vamos nos concentrar na interface aqui ListMarker
.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
Todas as nossas classes de dados implementam esta interface para convertê-los no mesmo tipo. Precisaremos disso mais tarde para nossa lista. A seguir, criamos nossos viewholders para cada elemento da lista. Não esqueça que cada um deles herda de BaseViewHolder
. Nessas classes especificamos qual elemento view
corresponde ao campo das classes de dados.
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()
}
}
Não há nenhuma lógica particular aqui, tudo é extremamente simples. Além disso, cada visualizador tem seu próprio layout: list_item_title.xml
list_item_google.xml
list_item_apple.xml
agora vamos para a parte mais 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
//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 herdamos nossa classe de , RecyclerView.Adapter
precisaremos substituir três métodos: onCreateViewHolder
,,, getItemCount
. - aqui inicializamos nossas classes . — este método é responsável pelo tamanho da lista. - aqui passamos os elementos da lista para nossas classes . Para uma lista regular com um tipo de elemento, isso seria suficiente. Mas para tipos diferentes ainda precisamos redefinir o método (para isso usamos as constantes que estão no topo da nossa classe adaptadora. Em Java você pode usar variáveis para isso). Também em Java, em vez de uma expressão, você pode usar um arquivo . E finalmente, vamos para nossa aula principal - . onBindViewHolder
onCreateViewHolder
ViewHolder
getItemCount
onBindViewHolder
ViewHolder
getItemViewType
final
onBindViewHolder
when
if
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>
RecyclerView
É aqui que nosso e é inicializado ListAdapter
. Este é um código bastante padronizado que muitos encontraram. No método, stubData
preenchi a lista com dados (como vocês podem ver, dados com elementos diferentes) e passei essa lista para o adaptador. A seguir, lançamos nosso aplicativo e devemos ver algo assim em nossa tela: Como você pode ver, existem diferentes elementos em uma lista, que era nosso objetivo. PS Esqueci de mencionar o Extension
. Isto é o que parece:
//это расширение для класса 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)
Esta não é uma classe, mas sim um arquivo, por isso não há nome dentro dele. Mas agora podemos usá-lo no adaptador de forma simples, inflate
em vez de uma estrutura longa. Infelizmente, isso não é fornecido para Java, então escreva LayoutInflater.from(context).inflate
. Isso é tudo. Até a próxima :) Link para GitHub
GO TO FULL VERSION