RecyclerView with different number of columns using SpanSizeLookup

We already know how to design RecyclerView. But how to create RecyclerView with different columns depending on its row? Like below image?


Yeah, we can do that, by using two different layouts with different viewType on Adapter. Another way is do some logic to do so. But, it's cumbersome. Luckily, we have another simple way to do that using SpanSizeLookUp. Let's see how to implement that in this post.

Implementation:

build.gradle

Include the following in app-level gradle file.

   implementation 'com.android.support:recyclerview-v7:27.0.2'
   implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta5'

recycler.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

  <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:paddingBottom="10dp"
        android:clipToPadding="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

grid_item.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@drawable/minion"
        />

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/transparentBlack"
        android:text="Title"
        android:padding="15dp"
        android:textColor="@color/white"
        android:layout_alignParentBottom="true"/>

</RelativeLayout>

colors.xml

<resources>
    <color name="colorPrimary">#e30b53</color>
    <color name="colorPrimaryDark">#cc094a</color>
    <color name="colorAccent">#342365</color>
    <color name="white">#ffffff</color>
    <color name="transparentBlack">#80000000</color>
</resources>

RecyclerActivity.kt

We have to set GridLayoutManager for RecyclerView with spanCount as 2 (2 columns, max). Also we have to set spanSizeLookUp for it. Normally, it will look like below:

recyclerView.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                   
                }
            }

This function is to assign span size for each item to occupy in a row. Suppose, there are 2 columns, and an item's span is 2, then it will occupy 2 columns of a row, i.e., the whole row. And if an item's span is 1, it will occupy half of the device's screen. (Gotcha..??!!) Just Remember it as calculation,

(Giving SpanSize) / (Total Columns Count)

Here, Total Columns Count is 2(i.e., Max Column Count). Using Mod(%) of 3, we can return spanSize for each item. But why we are using 3 here for Mod calculation? Good Question, It's because, mainly we want to design first 3 items(position 0, 1 and 2), then the remaining will only be the repetition of those 3.

This is where the magic happens.

recyclerView.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    return if (position % 3 == 0)
                        2
                    else
                        1
                }
            }

class RecyclerActivity : AppCompatActivity() {

    /** Declare variables **/
    private var list: ArrayList<HashMap<String, String>> = ArrayList<HashMap<String, String>>()
    lateinit var adapter: GridRecyclerAdapter

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

        setValues()

    }

    private fun setValues() {
        /** Adding values **/

        var map = HashMap<String, String>()
        map.put("title", "Beach House")
        map.put("image", "https://www.planwallpaper.com/static/images/2015-wallpaper_111525594_269.jpg")
        list.add(map)

        map = HashMap<String, String>()
        map.put("title", "Car")
        map.put("image", "https://www.planwallpaper.com/static/images/5018576-wallpaper-hd-cars.jpg")
        list.add(map)

        map = HashMap<String, String>()
        map.put("title", "SuperMan")
        map.put("image", "https://www.planwallpaper.com/static/images/542a0313e9630e447a47564717f1714e.jpg")
        list.add(map)

        map = HashMap<String, String>()
        map.put("title", "King of Jungle")
        map.put("image", "https://www.planwallpaper.com/static/images/wallpaper-in-hd-4.jpg")
        list.add(map)

        map = HashMap<String, String>()
        map.put("title", "Joker")
        map.put("image", "https://www.planwallpaper.com/static/images/41912.jpg")
        list.add(map)

        map = HashMap<String, String>()
        map.put("title", "White Horse")
        map.put("image", "https://www.planwallpaper.com/static/images/Nithf7m.jpg")
        list.add(map)

        
        recyclerView.layoutManager = GridLayoutManager(this, 2).also {
            it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    return if (position % 3 == 0)
                        2
                    else
                        1
                }
            }
        }
        recyclerView.addItemDecoration(SpaceItemDecoration(10));
        adapter = GridRecyclerAdapter(this@ShimmerActivity, list)
        recyclerView.adapter = adapter
    }


}

GridRecyclerAdapter.kt

As usual, we have to create an adapter for RecyclerView, not much changes here.

class GridRecyclerAdapter(val context: Context, var data: ArrayList<HashMap<String, String>>) :
        RecyclerView.Adapter<GridRecyclerAdapter.ViewHolder>() {


    override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
        if (holder != null)
            holder.bindItems(context, data.get(position))
    }

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
        val v = LayoutInflater.from(context).inflate(R.layout.grid_item, parent, false)
        return ViewHolder(v)
    }

    override fun getItemCount(): Int {
        return data.size
    }

    class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
        fun bindItems(context: Context, map: HashMap<String, String>) {
            itemView.title.text = map.get("title")
            Glide.with(context).load(map.get("image")).into(itemView.image)

        }
    }
}

SpaceItemDecoration.kt

With the help of MOD operator (%) , we can get the position in terms of 0,1 and 2. I have declared bottom padding to RecyclerView, so its not needed. So I am assigning the rest, depending on its position. 

class SpaceItemDecoration(mspace: Int) : RecyclerView.ItemDecoration() {

    val space = mspace

    override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
        super.getItemOffsets(outRect, view, parent, state)

        outRect?.apply {

            when ((parent!!.getChildLayoutPosition(view)) % 3) {
                0,  1  -> {
                    left = space
                    right = space
                    top = space
                }

                2 -> {
                    top = space
                    right = space
                }

            }

        }

    }
}

Coolll, It's done. Now, we can run the application to check it out.

Comments

  1. How to working like this kind of layout for spanCount 3 with multiple viewtype of Recyclerview?

    ReplyDelete
  2. I don't know who you are or if you still blog here. But I want to thank you very much for this article! I spent a lot, really a lot of time to find a working solution for this arrangement of elements.

    ReplyDelete

Post a Comment

Popular posts from this blog

SOAP Client using ksoap2 in Android - Kotlin

Databinding in RecyclerView - Android - Kotlin

Using Camera in Android - Kotlin

Using RxJava, Retrofit in Android - Kotlin

Map, Location update and AutoComplete Places - Kotlin

Braintree Integration in Android - Kotlin

Exploring Android Slices - JetPack

Room with LiveData, ViewModel - Android Architecture Components - Kotlin

Stripe Integration in Android - Kotlin