StaggeredGridLayoutManager

 

StaggeredGridLayoutManager is a part of RecyclerView.LayoutManager. For displaying list of items like Pinintrest we can use StaggeredGridLayoutManager with RecyclerView.

 

What is difference between GridLayoutManager and StaggeredGridLayoutManager?

  • GridLayoutManager:

GridLayoutManager items is of same size (Height and width). It shows symmetric items in view.

  • StaggeredGridLayoutManager:

StaggeredGridLayoutManager grid is of varying size(Height and width). Staggered Grid View shows asymmetric items view.

GridView-vs-Staggered-Grid-In-Android

 

Implementation


Step 1:

Add following dependency into build.gradle.

implementation 'com.android.support:recyclerview-v7:28.0.0-rc01'

 

Step 2:

Make layout file called list_item_grid_movie.xml for displaying items in RecyclerView.

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

    <ImageView
        android:id="@+id/imageMovie"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:contentDescription="@string/app_name"
        android:scaleType="fitXY"
        android:src="@drawable/ic_avengers"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/textMovieTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="@id/imageMovie"
        app:layout_constraintTop_toBottomOf="@id/imageMovie"
        tools:text="Avengers" />

    <TextView
        android:id="@+id/textMovieViews"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:textColor="@android:color/black"
        android:textSize="14sp"
        app:layout_constraintStart_toStartOf="@id/textMovieTitle"
        app:layout_constraintTop_toBottomOf="@id/textMovieTitle"
        tools:text="Views: 100" />

    <TextView
        android:id="@+id/textReleaseDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:textColor="@android:color/black"
        android:textSize="14sp"
        app:layout_constraintStart_toStartOf="@id/textMovieViews"
        app:layout_constraintTop_toBottomOf="@id/textMovieViews"
        tools:text="Release Date: 16 Feb 2018" />

</android.support.constraint.ConstraintLayout>

 

Step 3:

Now make one ViewHolder class and name it as a MovieListViewHolder . As a name suggest this holder class will hold view information.

package com.android4dev.recyclerview.adapter

import android.support.v7.widget.RecyclerView
import android.view.View
import com.android4dev.recyclerview.model.MovieModel
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.list_item_movie.view.*
import java.util.*

class MovieListStaggeredViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val mRandom = Random()
    fun bindView(movieModel: MovieModel) {
        itemView.textMovieTitle.text = movieModel.movieTitle
        itemView.textMovieViews.text = "Views: " + movieModel.movieViews
        itemView.textReleaseDate.text = "Release Date: " + movieModel.releaseDate
        itemView.imageMovie.layoutParams.height = getRandomIntInRange(250, 150)
        Glide.with(itemView.context).load(movieModel.moviePicture!!).into(itemView.imageMovie)
    }

    private fun getRandomIntInRange(max: Int, min: Int): Int {
        return mRandom.nextInt(max - min + min) + min
    }
}

 

Following function will use for displaying different height for grid items. We are randomly generating height value using this function but in real implementation it should measure upon grid items content.

private fun getRandomIntInRange(max: Int, min: Int): Int {
        return mRandom.nextInt(max - min + min) + min
    }

 

Step 4:

Now make one Adapter class called MovieListStaggeredAdapter for bind ViewHolder with Adapter items.

package com.android4dev.recyclerview.adapter

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import com.android4dev.recyclerview.R
import com.android4dev.recyclerview.model.MovieModel

class MovieListStaggeredAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private var listOfMovies = listOf<MovieModel>()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return MovieListStaggeredViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_grid_movie, parent, false))
    }

    override fun getItemCount(): Int = listOfMovies.size

    override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
        val movieViewHolder = viewHolder as MovieListStaggeredViewHolder
        movieViewHolder.bindView(listOfMovies[position])
    }

    fun setMovieList(listOfMovies: List<MovieModel>) {
        this.listOfMovies = listOfMovies
        notifyDataSetChanged()
    }
}

 

Step 5:

Now make one GridItemDecoration class for managing equal divider space between RecyclerView items.

package com.android4dev.recyclerview.recyclerview_helper

import android.graphics.Rect
import android.support.v7.widget.RecyclerView
import android.view.View

/***
 * Made by Lokesh Desai (Android4Dev)
 */
class GridItemDecoration(gridSpacingPx: Int, gridSize: Int) : RecyclerView.ItemDecoration() {
    private var mSizeGridSpacingPx: Int = gridSpacingPx
    private var mGridSize: Int = gridSize

    private var mNeedLeftSpacing = false

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        val frameWidth = ((parent.width - mSizeGridSpacingPx.toFloat() * (mGridSize - 1)) / mGridSize).toInt()
        val padding = parent.width / mGridSize - frameWidth
        val itemPosition = (view.getLayoutParams() as RecyclerView.LayoutParams).viewAdapterPosition
        if (itemPosition < mGridSize) {
            outRect.top = 0
        } else {
            outRect.top = mSizeGridSpacingPx
        }
        if (itemPosition % mGridSize == 0) {
            outRect.left = 0
            outRect.right = padding
            mNeedLeftSpacing = true
        } else if ((itemPosition + 1) % mGridSize == 0) {
            mNeedLeftSpacing = false
            outRect.right = 0
            outRect.left = padding
        } else if (mNeedLeftSpacing) {
            mNeedLeftSpacing = false
            outRect.left = mSizeGridSpacingPx - padding
            if ((itemPosition + 2) % mGridSize == 0) {
                outRect.right = mSizeGridSpacingPx - padding
            } else {
                outRect.right = mSizeGridSpacingPx / 2
            }
        } else if ((itemPosition + 2) % mGridSize == 0) {
            mNeedLeftSpacing = false
            outRect.left = mSizeGridSpacingPx / 2
            outRect.right = mSizeGridSpacingPx - padding
        } else {
            mNeedLeftSpacing = false
            outRect.left = mSizeGridSpacingPx / 2
            outRect.right = mSizeGridSpacingPx / 2
        }
        outRect.bottom = 0
    }
}

 

Step 6:

Make Activity class called RecyclerViewStaggeredGridActivity to bind Adapter class with RecyclerView.

package com.android4dev.recyclerview

import android.os.Bundle
import android.os.PersistableBundle
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.StaggeredGridLayoutManager
import com.android4dev.recyclerview.adapter.MovieListGridRecyclerAdapter
import com.android4dev.recyclerview.adapter.MovieListStaggeredAdapter
import com.android4dev.recyclerview.base.BaseActivity
import com.android4dev.recyclerview.model.MovieModel
import com.android4dev.recyclerview.recyclerview_helper.GridItemDecoration
import com.android4dev.recyclerview.recyclerview_helper.VerticalSpaceItemDecoration
import kotlinx.android.synthetic.main.activity_main.*

class RecyclerViewStaggeredGridActivity : BaseActivity() {

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


    private fun initView() {
        recyclerViewMovies.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
        //This will for default android divider
        recyclerViewMovies.addItemDecoration(GridItemDecoration(10, 2))

        val movieListAdapter = MovieListStaggeredAdapter()
        recyclerViewMovies.adapter = movieListAdapter
        movieListAdapter.setMovieList(generateDummyData())
    }

    private fun generateDummyData(): List<MovieModel> {
        val listOfMovie = mutableListOf<MovieModel>()

        var movieModel = MovieModel(1, "Avengers", 500, "16 Feb 2018", R.drawable.ic_avengers)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(2, "Avengers: Age of Ultron", 400, "17 March 2018", R.drawable.ic_avengers_2)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(3, "Iron Man 3", 550, "17 April 2018", R.drawable.ic_ironman_3)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(4, "Avengers: Infinity War", 1500, "15 Jan 2018", R.drawable.ic_avenger_five)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(5, "Thor: Ragnarok", 200, "17 March 2018", R.drawable.ic_thor)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(6, "Black Panther", 250, "17 May 2018", R.drawable.ic_panther)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(7, "Logan", 320, "17 Dec 2018", R.drawable.ic_logan)
        listOfMovie.add(movieModel)

        return listOfMovie
    }
}

 

For using RecyclerView with StaggredGridLayoutManager we will use below lines
recyclerViewMovies.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL). 
Here, 2 is a number of columns we want to display in grid. StaggeredGridLayoutManager.VERTICAL is used to display GridItems vertically. We can use StaggeredGridLayoutManager.HORIZONTAL for displaying list horizontally.

Step 7:

Create layout file for RecyclerViewStaggeredGridActivity class.

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

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerViewMovies"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:overScrollMode="never"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

 

Now Run the project and wait for the result.

 

Source Code

Check out Github Project Which Contains All RecyclerView Examples

You Can Also Refer

https://android4dev.com

Hi, I am Android Developer and Founder of Android4Dev. Right now my interest is in RxKotlin, MVVM and Dagger2. I am Kotlin Lover. Kotlin made me more productive in terms of coding. Do you want me to code for you ? Please don’t hesitate to contact me.