Hallo zusammen. Ich habe beschlossen, das Thema komplexer Listen in Android anzusprechen. Dieses Thema hat mich zunächst verwirrt. Es schien mir sehr schwierig, aber eigentlich ist alles einfacher. Und ich denke, dass jeder, der jetzt mit diesem Problem konfrontiert ist, den Artikel nützlich finden wird. Alle Beispiele sind in Kotlin geschrieben. Ich habe versucht, überall Kommentare zu schreiben und sie für diejenigen, die in Java schreiben, so klar wie möglich zu machen. Hier ist also unsere Klassenstruktur:
MainActivity
- Dies ist die Hauptklasse. ListAdapter
- Hier binden wir unsere Liste an Ansichtselemente. ViewHolder
(es gibt mehrere davon) – das ist unser Markup für jeden Elementtyp. Elementklassen sind Daten für eine Liste (Pojo). XML-Dateien sind Markups ( activity_main
für den Hauptbildschirm, der Rest für jeden Listenelementtyp). Als Erstes müssen wir eine Abhängigkeit hinzufügen, damit wir Listen verwenden können (aus irgendeinem Grund fehlt diese Abhängigkeit beim Erstellen eines Projekts). In der Datei build.gradle
im Block dependencies
fügen wir die Zeile hinzu:
implementation "androidx.recyclerview:recyclerview:1.1.0"
Als nächstes finden wir die Datei activity_main.xml
: Hier gibt es nur unser Element für Listen - RecyclerView
. Je nach Geschmack fügen wir Einzüge und Formatierungen hinzu. Als nächstes erstellen wir die Klasse BaseViewHolder
. Es wird abstrakt sein, da alle unsere Klassen davon erben werden ViewHolder
. Es ist durchaus möglich, darauf zu verzichten, ich empfehle aber dennoch, darauf zu achten, dass alle erbenden Klassen vom Aufbau her gleich sind. (in Kotlin extends
und implement
wird stattdessen verwendet :
. Wie in Java können Sie hier von einer Klasse erben und viele Schnittstellen implementieren) Als nächstes erstellen wir unsere Datenklassen: ItemTitle
, ItemGoogle
und ItemApple
. In Java sind dies gewöhnliche Klassen mit einem Konstruktor und Gettern.
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
Konzentrieren wir uns hier auf die Schnittstelle ListMarker
.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
Alle unsere Datenklassen implementieren diese Schnittstelle, um sie in denselben Typ umzuwandeln. Wir werden dies später für unsere Liste benötigen. Als nächstes erstellen wir unsere Viewholder für jedes Element der Liste. Vergessen Sie nicht, dass jeder von ihnen von erbt BaseViewHolder
. In diesen Klassen legen wir fest, welches Element view
dem Feld aus den Datenklassen entspricht.
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()
}
}
Hier gibt es keine besondere Logik, alles ist extrem einfach. Außerdem hat jeder Viewholder sein eigenes Layout: list_item_title.xml
list_item_google.xml
list_item_apple.xml
Kommen wir nun zum schwierigsten Teil – 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
//Wie вариант можно было сделать 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<*>
Wenn wir unsere Klasse von erben RecyclerView.Adapter
, müssen wir drei Methoden überschreiben: onCreateViewHolder
, getItemCount
, onBindViewHolder
. onCreateViewHolder
- Hier initialisieren wir unsere Klassen ViewHolder
. getItemCount
— Diese Methode ist für die Listengröße verantwortlich. onBindViewHolder
- Hier übergeben wir die Listenelemente an unsere Klassen ViewHolder
. Für eine reguläre Liste mit einem Elementtyp würde dies ausreichen. Aber für verschiedene Typen müssen wir die Methode noch neu definieren getItemViewType
(dafür verwenden wir die Konstanten, die oben in unserer Adapterklasse stehen. In Java können Sie final
dafür Variablen verwenden). Auch in Java können Sie onBindViewHolder
anstelle eines Ausdrucks einen regulären Ausdruck verwenden . Und schließlich kommen wir zu unserer Hauptklasse – . 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
Hier wird unser und initialisiert ListAdapter
. Dies ist ein hübscher Standardcode, auf den viele gestoßen sind. In der Methode stubData
habe ich die Liste mit Daten gefüllt (wie Sie sehen können, Daten mit unterschiedlichen Elementen) und diese Liste an den Adapter übergeben. Als nächstes starten wir unsere Anwendung und auf unserem Bildschirm sollte etwa Folgendes angezeigt werden: Wie Sie sehen, gibt es in einer Liste verschiedene Elemente, was unser Ziel war. PS: Ich habe vergessen, das zu erwähnen Extension
. So sieht es aus:
//это расширение для класса ViewGroup. Теперь мы можем по всему проекту использовать
//короткое inflate anstatt длинного LayoutInflater.from(context).inflate
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View =
LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
Dies ist keine Klasse, sondern eine Datei, weshalb sie keinen Namen enthält. Aber jetzt können wir es einfach im Adapter inflate
anstelle einer langen Struktur verwenden. Leider ist dies für Java nicht vorgesehen, also schreiben Sie LayoutInflater.from(context).inflate
. Das ist alles. Bis zum nächsten Mal :) Link zu GitHub
GO TO FULL VERSION