Attack on RecyclerView

Last week my productivity level had increased and I was able to finish more tasks than the previous weeks. I became more familiar with the concepts and things that I am working on, my ability to find answers and solutions for my questions and problems became better, it’s all about how you ask your question.
Last week I finally solved one of the problems I faced for the past two weeks which is how to display multiple view types in multiple view holders within the same RecyclerView adapter which made working with other tasks much easier. Also, I used Anko library to create views in the code much faster than the conventional way, you can check their GitHub page for more information. Also, I start working with another library to deal with images, it helps you to take pictures and crop them as you want. The library does very much anything for you, all you have to do in your code is to tell the library you want to take a picture then it will open the camera or gallery or any other application of your choice then start a new activity for cropping your picture, then return your cropped picture to your activity. You don’t even need to create a new activity for cropping the picture the library will created for you, you just need to add the activity to your manifest file. For more information about this library, you can check their GitHub page. I may do a tutorial on how to use this library in the future.
Islam told me that one of the reasons that I am writing these posts because it is a way of knowledge sharing and giving back to the open source / free knowledge community. I am a big supporter of free knowledge, I personally benefit a lot from videos, tutorials, and articles that people share online for free just for the sake of sharing knowledge. So this post will be a tutorial on how to use multiple view holder within a single adapter. I am not saying this is the best way to do it, I will just show you how I solved it and what worked for me.

I talked about the RecyclerView in a previous post. You can check it out before continuing in this tutorial. This tutorial was inspired by this tutorial. Also, I am a fan of the anime “Attack on Titans”, so I will use some of the anime characters in the tutorial. The source of photos and information from this website.

All codes for this tutorial were written using Kotlin, I am using Android Studio 3.0.1. I will assume that you know how to start a new project and I will also assume that you checked the auto-import option in the setting.

First of all, you have to add the dependency for the RecyclerView in gradle, at the time of writing this tutorial the dependency

implementation 'com.android.support:recyclerview-v7:27.1.1'

Add this line to gradle then sync the project. Now you are ready to use the RecyclerView.

The project structure

In this tutorial, we will implement two view types(quote, character info) that will be inflated by two different layouts, each one of them has its own implementation (view holder) in the recycler view adapter.

Here is a screenshot of the project structure for this tutorial

 

Code

Lets start with layout (res/layout),  but first add these lines to the string.xml file located add res/values

<string name="name">Name</string>
<string name="quote">Quote</string>
<string name="regiment">Regiment</string>

In activity_main.xml I am using Linear Layout as the root view and then I added the recycler view as its child.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.narbase.recyclerviewwithmultipleviewtype.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="vertical" />
</LinearLayout>

Now we need to create the layout for the two view types., I used view  just to add a horizontal line as a separator.

quote.xml for the Quote view type which consits of just text views.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="12dp"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/name"
        android:textColor="@android:color/black"
        android:textSize="25sp" />

    <TextView
        android:id="@+id/character_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/regiment"
        android:textColor="@android:color/black"
        android:textSize="25sp" />

    <TextView
        android:id="@+id/character_regiment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@android:color/darker_gray" />

    <ImageView
        android:id="@+id/character_image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="fitXY"
        android:src="@drawable/armin" />

</LinearLayout>

character_info.xml for the Character view type which consits of text views and an image view.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="12dp"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/name"
        android:textColor="@android:color/black"
        android:textSize="25sp" />

    <TextView
        android:id="@+id/character_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/regiment"
        android:textColor="@android:color/black"
        android:textSize="25sp" />

    <TextView
        android:id="@+id/character_regiment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp" />

    <ImageView
        android:id="@+id/character_image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="fitXY"
        android:src="@drawable/armin" />

</LinearLayout>

The Model.kt class that populates the data in our adapter is given below

package package com.narbase.recyclerviewwithmultipleviewtype

data class Model (
        val type: Int,
        val name: String,
        val regiment: String,
        val quote: String,
        val image: Int
)

It has couple of texts and an image to displayed later each one in its related view holder.

For the MainActivity.kt two functions are added. The first function is for creating an array list and fill it with required data to be send later to the recycler view adapter. The second function is for just setup the layout manager for the recycler view and set its adapter. In the onCreate function in the MainActivity.kt  these two function are called.

package com.narbase.recyclerviewwithmultipleviewtype

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val characterList = fillList()
        setupRecyclerView(characterList)

    }

    private fun fillList(): ArrayList<Model> {

        val QUOTE_TYPE = 0
        val CHARACTER_INFO_TYPE = 1

        val characterList: ArrayList<Model> = ArrayList()

        characterList.add(Model(CHARACTER_INFO_TYPE, "Erwin Smith", "Scout Regiment",
                "", R.drawable.erwin))
        characterList.add(Model(QUOTE_TYPE, "", "", "One day, we'll break it down. This wall hiding the truth...will fall", 0))

        characterList.add(Model(CHARACTER_INFO_TYPE, "Levi Ackermann", "Scout Regiment",
                "", R.drawable.levi))
        characterList.add(Model(QUOTE_TYPE, "", "", "I don't know the answer to that. I never have. " +
                "Whether I trusted myself or the choices of my dependable comrades, there was no telling how things would turn out. " +
                "So, just do the best you can and choose whichever you'll regret the least", 0))

        characterList.add(Model(CHARACTER_INFO_TYPE, "Eren Jaeger", "Scout Regiment",
                "", R.drawable.eren))
        characterList.add(Model(QUOTE_TYPE, "", "", "I'll control the Titans. I'll plug up Wall Maria." +
                " I'll capture Reiner and make him pay. That's what I've gotta do to atone for all those lives lost.", 0))

        characterList.add(Model(CHARACTER_INFO_TYPE, "Mikasa Ackermann", "Scout Regiment",
                "", R.drawable.mikasa))
        characterList.add(Model(QUOTE_TYPE, "", "", "There's only so many lives that I actually care about." +
                " My enemies made deciding that easy six years ago. So...you're mistaken to seek any compassion from me. Because right now, " +
                "I'm all out of time and room in my heart to care", 0))

        characterList.add(Model(CHARACTER_INFO_TYPE, "Armin Arlelt", "Scout Regiment",
                "", R.drawable.armin))
        characterList.add(Model(QUOTE_TYPE, "", "", "Fiery water! Lands of ice! Sandy snowfields! " +
                "The outside world must be many times larger than the inside of these Walls!", 0))

        return characterList
    }

    private fun setupRecyclerView(characterList: ArrayList<Model>) {
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = MyAdapter(characterList)
    }
}

In MyAdapter.kt class which is our adapter, we will create two sepertae ViewHolder classes for each of the two layouts I menthioned earler as shown bellow.

package com.narbase.recyclerviewwithmultipleviewtype

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.character_info_item.view.*
import kotlinx.android.synthetic.main.quote_item.view.*

class MyAdapter(val myList: List<Model>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    val QUOTE_TYPE = 0
    val CHARACTER_INFO_TYPE = 1

    class Quote(view: View) : RecyclerView.ViewHolder(view) {
        val quoteView = view
    }

    class Character(view: View) : RecyclerView.ViewHolder(view) {
        val characterView = view
    }


    override fun getItemViewType(position: Int) = when (myList[position].type) {
        QUOTE_TYPE -> QUOTE_TYPE
        else -> CHARACTER_INFO_TYPE
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is Quote) {
            holder.quoteView.quote.text = myList[position].quote
        } else if (holder is Character) {
            holder.characterView.character_name.text = myList[position].name
            holder.characterView.character_regiment.text = myList[position].regiment
            holder.characterView.character_image.setImageResource(myList[position].image)
        }
    }

    override fun getItemCount() = myList.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
            when (viewType) {
                QUOTE_TYPE -> Quote(LayoutInflater.from(parent.context)
                        .inflate(R.layout.quote_item, parent, false))
                else -> Character(LayoutInflater.from(parent.context)
                        .inflate(R.layout.character_info_item, parent, false))
            }

}

Here we are, we finished coding. Now, are you excited to see what the output of your work?!

Here is the output for the application we built.

You can find the project on my GitHub page.

I hope you enjoyed and benefit from this tutorial. If you have any feedback or questions please leave a comment.

So that’s it for this week.

Till next week

 

Leave a Reply

Your email address will not be published. Required fields are marked *