Different layouts in the RecyclerView

D

The purpose of this tutorial is to learn the basics of the Android Material Design UI’s. This tutorial teaches you how to create Material Design based Android application, which uses RecyclerView and CardView components. This application uses hard coded data as golf course images and names.

Material design is a comprehensive guide for visual, motion, and interaction design across platforms and devices. To use material design in your Android apps, follow the guidelines defined in the material design specification and use the new components and styles available in the material design support library.

Create a new project

Launch Android Studio and create a new project with default settings.

  • Give unique application name Golf Course Wishlist
  • Use your own company domain name in package name example.com
  • Select Phone and Table target and some of the newest API level
  • Select Empty Activity and use default naming for Activity, Layout and Resources (leave checkboxes as selected)
  • Click Finish and your project will be created

Golf Courses data

Hard coded data is used in this example to simplify this tutorial. In real life all data should be loaded from the server side.

images

Save all the below images to your project drawable folder. You can find drawable folder in your project app/src/main/res folder.

Place.kt class

Add a Place Kotlin class to your project. This class holds the basic information of the golf course. Select your package in the source folder and add a new Place class to your project and modify generated Place class code as below.

class Place {
  var name: String? = null
  var image: String? = null

  fun getImageResourceId(context: Context): Int {
    return context.resources.getIdentifier(this.image,"drawable", context.packageName)
  }
}

Now a place has a name and image (or not, if null). Image will be loaded from the resources with getImageResourceId function.

Places.kt class

Add Places class to your project. This class will hold all the Place‘s objects in a list. Select your package in source folder and add a new Places class to your project and modify generated Places class code as below. Only a ‘mock’ data will be initialized in a below Places class. Above images will follow a naming rules generated in below code. Remember save images with their default names.

class Places {
  // like "static" in other OOP languages
  companion object {
    // hard code a few places
    var placeNameArray = arrayOf(
      "Black Mountain",
      "Chambers Bay",
      "Clear Water",
      "Harbour Town",
      "Muirfield",
      "Old Course",
      "Pebble Beach",
      "Spy Class"
    )

    // return places
    fun placeList(): ArrayList<Place> {
      val list = ArrayList<Place>()
      for (i in placeNameArray.indices) {
        val place = Place()
        place.name = placeNameArray[i]
        place.image = placeNameArray[i].replace("\\s+".toRegex(), "").toLowerCase()
        list.add(place)
      }
      return list
    }
  }    
}

You can tie function or a property to a class rather than to instances of it (similar to @staticmethod in Python or static keyword in many OOP), you can declare it inside a companion object.

Add Glide to load/show images

Select and click build.gradle (Module: app) file from the project tree in Gradle Scripts section. You will need to include Glide to dependences block of the code. Remember check latest version numbers from Glide.

dependencies {
  implementation 'com.github.bumptech.glide:glide:4.11.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
}

Remember click “Sync Now” text at the top right corner of the Android Studio. This will download/sync needed libraries to your project.

Modify styles.xml to support custom colors

colors.xml

Find and double click colors.xml file in your res/values folder. Using the colors.xml, file you can customize application colors. Now you will use some “green” ones.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#4CAF50</color>
    <color name="colorPrimaryDark">#388E3C</color>
    <color name="colorAccent">#8BC34A</color>
</resources>

styles.xml

Find and double click styles.xml file in your res/values folder. Using the styles.xml file you can customize application base theme and colors. It should be correct by default. You can modify it for you needs.

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
</resources>

AndroidManifest.xml

Open your application manifest file and check how the theme is linked to your app with application element.

<application 
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

Build and run

Run your app to see the new color scheme in the application is working. Don’t be afraid with “Hello World” text. It is generated by the project template.

Using the Recycler and Card Views

Android Material Design library introduces a new RecyclerView, which is a replacement for the ListView. Google describes RecyclerView as a flexible view for providing a limited window into large data set. In this tutorial you are going to switch view from list to a custom grid that uses the same data source (our Golf Courses Wish list).

Implementing a Recycler View in XML

Find and open activity_main.xml file, remove “Hello world!” TextView and drop RecyclerView to UI. It will be asked to add needed dependency for the project. Accept that one and it will be added to build.gradle file. In this tutorial you only need a RecyclerView to hold all the cards.

A below XML structure will be generated, when RecyclerView is dropped to your UI.

<androidx.recyclerview.widget.RecyclerView
  android:id="@+id/recyclerView"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintTop_toTopOf="parent" />

Remember check that you have an id for recyclerView and layout uses match_parent value in width and height.

Using a Card View as a row cells

Material Design CardView widget provides a consistent backdrop for the views. It includes rounded corners and drop shadows. CardView widgets are used as row/cell layout inside RecyclerView.

Now you will use CardView to display Golf course image and club name.

Create a new layout resource file to your res/layout folder and call it row_places.xml.

  • Drop CardView to your layout. Accept dependency for the CardView. Check generated XML code and remove other elements than CardView.
  • Add a ImageView and set height 200dp (look other settings below)
  • Add LinearLayout and TexView as described below.
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:id="@+id/placeCard"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_margin="4dp"
  app:cardCornerRadius="4dp"
  app:cardElevation="4dp">

  <ImageView
    android:id="@+id/placeImage"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:scaleType="centerCrop"
    android:contentDescription="@string/image_content_text"/>

  <LinearLayout
    android:id="@+id/placeNameHolder"
    android:layout_width="match_parent"
    android:layout_height="45dp"
    android:layout_gravity="bottom"
    android:orientation="horizontal">

    <TextView
      android:id="@+id/placeName"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="center_vertical"
      android:gravity="start"
      android:paddingStart="10dp"
      android:paddingEnd="10dp"
      android:textAppearance="?android:attr/textAppearanceLarge"
      android:textColor="@android:color/white" />

  </LinearLayout>

</androidx.cardview.widget.CardView>

You will need to use http://schemas.android.com/apk/res-auto namespace to get support of Material Design cardCornerRadius and cardElevation attributes.

Implementing an Adapter for a Recyclerview

Android application uses adapters to binding data to the view. Select your package in the source folder and add a new GolfCourseWishlistAdapter class to your project.

class GolfCourseWishlistAdapter(private val places: ArrayList<Place>) 
  : RecyclerView.Adapter<GolfCourseWishlistAdapter.ViewHolder>() {

}

This class will get places data from MainActivity as a parameter of function call. Class need to be extend from the RecyclerView.Adapter and it need to know which class is responsible to bind data to UI (now ViewHolder which will be created next).

ViewHolder

A ViewHolder describes an item view and metadata about it’s place within the RecyclerView component. Here you describe what “views/components” you have to display data in the UI. In this example, you will use only one ImageView and TextView.

Add a following inner class to inside GolfCourseWishlistAdapter class.

// View Holder class to hold UI views
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
  val nameTextView: TextView = view.placeName
  val imageView: ImageView = view.placeImage
}

You will need to implement all the below functions inside your adapter class.

  • onCreateViewHolder
  • getItemCount
  • onBindViewHolder

onCreateViewHolder

This function will be called, when a RecyclerView needs a new RecyclerView.ViewHolder of the given type to represent an item. This new ViewHolder should be constructed with a new View that can represent the items of the given type. You can create a new View manually, or inflate it from the XML layout file. The new ViewHolder will be used to display items of the adapter using onBindViewHolder(ViewHolder, int, List).

Add a following onCreateViewHolder function inside GolfCourseWishlistAdapter class. inside. Notice how XML layout will be inflated and a new ViewHolder will be returned.

// create UI View Holder from XML layout
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GolfCourseWishlistAdapter.ViewHolder {
  val layoutInflater = LayoutInflater.from(parent.context)
  val view = layoutInflater.inflate(R.layout.row_places, parent, false)
  return ViewHolder(view)
}

getItemCount

This function will return the total number of items in the data set held by the adapter. Add a following getItemCount function.

// return item count in employees
override fun getItemCount(): Int = places.size

onBindViewHolder

This function will be called by RecyclerView to display the data at the specified position. This method should update the contents of the itemView to reflect the item at the given position. Add a following onBindViewHolder function.

// bind data to UI View Holder
override fun onBindViewHolder(holder: GolfCourseWishlistAdapter.ViewHolder, position: Int) {
  // place to bind UI
  val place: Place = places.get(position)
  // name
  holder.nameTextView.text = place.name
  // image
  Glide.with(holder.imageView.context).load(place.getImageResourceId(holder.imageView.context)).into(holder.imageView)
}

Now a current position place name will be shown and image will be loaded with Glide.

Initializing a Recyclerview and Applying a Layout Manager

To use the RecyclerView widget, you have to specify an adapter and a layout manager. A layout manager positions item views inside a RecyclerView. It will reuse item views that are no longer visible to the user. To reuse (or recycle) a view, a layout manager may ask the adapter to replace the contents of the view with a different element from the dataset. Recycling views in this manner improves performance by avoiding the creation of unnecessary views or performing expensive findViewById() lookups.

Find and open MainActivity.kt file and add following lines to onCreate() method:

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

  // Use StaggeredGridLayoutManager as a layout manager for recyclerView
  recyclerView.layoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL)
  // Use GolfCourseWishlistAdapter as a adapter for recyclerView
  recyclerView.adapter = GolfCourseWishlistAdapter(Places.placeList())
}

You need to import RecyclesView and StaggeredGridLayoutManager classes or you can configure Android Studio so that it automatically inserts import statements to your class. If you want to do that go to “Preferences\Editor\General\Auto Import” and select the “Add unambiguous imports on the fly” checkbox.

Above code initializes RecyclerView and applies StaggeredGridLayout to it. Passing number 1 for the span count makes this component behave as a vertical list.

Build and run

You can now run your application to see how Card View Widgets are visible in RecyclerView.

From List to Grid and back

StaggeredLayoutManager lets you add versatility to your layouts. To change your existing list to a more compact two column grid, you simply have to change the span count of the mLayoutManager in MainActivity.

Add icons to the project

Use Android Studio Vector Assets Studio to include icons to your project. Right click res folder and select New > Vector Asset.

Click Icon button to change the icon. Add “view column” and “view stream” icons to your project. All the icons are black by default, change colors to white.

It can be done from the code too:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:width="24dp"
  android:height="24dp"
  android:viewportWidth="24.0"
  android:viewportHeight="24.0">
  <path
    android:fillColor="#FFFFFFFF"
    android:pathData="M10,18h5L15,5h-5v13zM4,18h5L9,5L4,5v13zM16,5v13h5L21,5h-5z"/>
</vector>

Add menu structure to the project

Right click your project res folder and create a menu folder to your project.

Right click your project menu folder and create a menu.xml file to your project.

Double click menu file and edit it content as below. Now you will have a one options menu item, which shows drawable icon.

<?xml version="1.0" encoding="utf-8"?>
<menu
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto">

  <item
    android:id="@+id/action_toggle"
    android:icon="@drawable/ic_view_column_white_24dp"
    app:showAsAction="always"
    android:orderInCategory="100"
    android:title="Show as grid" />

</menu>

Programming menu in MainActivity

Menu selection will change RecyclerView’s to show a list or grid. isListView variable is true when a list mode is used and false if grid mode is used. RecyclerView will work together with StaggeredGridLayoutManager to make this change happened.

private var isListView = true
private var mStaggeredLayoutManager: StaggeredGridLayoutManager? = null

StaggeredGridLayoutManager will be used inside onCreate() and onOptionsItemSelected() functions, so it need to be also moved to class variable.

Modify your onCreate function:

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

    // Use StaggeredGridLayoutManager as a layout manager for recyclerView
    mStaggeredLayoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL)
    recyclerView.layoutManager = mStaggeredLayoutManager

    // Use GolfCourseWishlistAdapter as a adapter for recyclerView
    recyclerView.adapter = GolfCourseWishlistAdapter(Places.placeList())
}

Add onCreateOptionsMenu method to MainActivity to handle menu creation.

override fun onCreateOptionsMenu(menu: Menu): Boolean {
  val inflater: MenuInflater = menuInflater
  inflater.inflate(R.menu.menu, menu)
  return true
}

Finally, add onOptionsItemSelected method which will be called when menu item is selected. Options menu selection will change RecyclerView’s to show a to list or grid.

override fun onOptionsItemSelected(item: MenuItem): Boolean {
  return when (item.itemId) {
    R.id.action_toggle -> {
      if (isListView) {
        item.setIcon(R.drawable.ic_view_stream_white_24dp)
        item.setTitle("Show as list")
        isListView = false
        mStaggeredLayoutManager?.setSpanCount(2)
      } else {
        item.setIcon(R.drawable.ic_view_column_white_24dp)
        item.setTitle("Show as grid")
        isListView = true
        mStaggeredLayoutManager?.setSpanCount(1)
      }
      true
    }
    else -> super.onOptionsItemSelected(item)
  }
}

Build and run

Run your application and test grid and list functionality from the menu.

Add comment

Pasi Manninen

Pasi Manninen

Pasi is a mobile and web application developer. Currently working as a senior lecturer in JAMK University of Applied Sciences. Pasi has programming experience over many decades and has taught dozens of courses since the 90's.

Recent Posts

Follow Me