JavaRush /Java Blog /Random EN /Complex lists made easy
Paul Soia
Level 26
Kiyv

Complex lists made easy

Published in the Random EN group
Hi all. I decided to raise the topic of complex lists in Android. This topic puzzled me at first. It seemed very difficult to me, but everything is actually simpler. And I think that anyone who is now faced with this problem will find the article useful. All examples are written in Kotlin. I tried to write comments everywhere and make it as clear as possible for those who write in Java. So here is our class structure: Complex lists made easy - 1MainActivity- This is the main class. ListAdapter- here we bind our list to view elements. ViewHolder(there are several of them) - this is our markup for each type of element. Item classes are data for a list (pojo). Xml files are markups ( activity_mainfor the main screen, the rest for each type of list element). The first thing we need to do is add a dependency so that we can use lists (for some reason this dependency is missing when creating a project). In the file build.gradlein the block dependencieswe add the line:
implementation "androidx.recyclerview:recyclerview:1.1.0"
Next we find the file activity_main.xml: Complex lists made easy - 2Here there is only our element for lists - RecyclerView. We add any indents and formatting to taste. Next we create the class BaseViewHolder. It will be abstract, since all our classes will inherit from it ViewHolder. It’s quite possible to do without it, but I still recommend making sure that all inheriting classes are the same in structure. (in Kotlin, extendsand implementis used instead :. As in Java, here you can inherit from one class and implement many interfaces) Next, we create our data classes: ItemTitle, ItemGoogleand ItemApple. In Java, these are ordinary classes with a constructor and 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
Let's focus on the interface here ListMarker.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
All our data classes implement this interface to cast them to the same type. We will need this later for our list. Next, we create our viewholders for each element of the list. Don't forget that each of them inherits from BaseViewHolder. In these classes we specify which element viewcorresponds to the field from the data classes.
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()
    }
}
There is no particular logic here, everything is extremely simple. Also, each viewholder has its own layout: list_item_title.xmlComplex lists made easy - 3list_item_google.xmlComplex lists made easy - 4list_item_apple.xmlComplex lists made easy - 5Now let’s move on to the most difficult part - 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<*>
When we inherit our class from RecyclerView.Adapter, we will need to override three methods: onCreateViewHolder, getItemCount, onBindViewHolder. onCreateViewHolder- here we initialize our classes ViewHolder. getItemCount— this method is responsible for the list size. onBindViewHolder- here we pass the list elements to our classes ViewHolder. For a regular list with one element type, this would be enough. But for different types we still need to redefine the method getItemViewType(for this we use the constants that are at the top of our adapter class. In Java you can use finalvariables for this). Also in Java, onBindViewHolderinstead of an expression, whenyou can use a regular if. And finally, let's move on to our main class - 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>
RecyclerViewThis is where our and is initialized ListAdapter. This is a pretty boilerplate code that many have encountered. In the method, stubDataI filled the list with data (as you can see, data with different elements) and passed this list to the adapter. Next, we launch our application, and we should see something like this on our screen: Complex lists made easy - 6As you can see, there are different elements in one list, which was our goal. PS Forgot to mention the Extension. This is what it looks like:
//это расширение для класса 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)
This is not a class, but a file, which is why there is no name inside it. But now we can use it in the adapter simply inflateinstead of a long structure. Unfortunately, this is not provided for Java, so write LayoutInflater.from(context).inflate. That's all. Until next time :) Link to GitHub
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION