displaying-data-in-cardviews-and-gridviews-16x9-720p

Displaying large amounts of data in an Android app can be a tricky balancing act. Even on devices with larger screens (such as a laptop or computer) being confronted with a solid wall of data isn’t particularly appealing! This is even more true on Android devices, as the smaller screen of a tablet or smartphone tends to make blocks of content appear even larger.

If your app needs to display a considerable amount of data, then presentation is everything. Android provides several UI components that can help you bring instant structure and design to even the largest data sets, so you can deliver data-packed screens that users won’t mind scrolling through.

In this article, I’m going to show you how to use two data-focused UI components in your projects: CardViews and GridViews.

Working with CardViews

As the name suggests, CardView gives you an easy way of displaying data in Google Now-style cards. Each CardView can contain multiple child views and multiple content types, for example you can place TextViews and ImageViews inside the same CardView.

To ensure CardView fits with Android’s Material Design makeover, each CardView component has its own elevation and shadow. The elevation is the card’s position on the ‘Z’ axis, and the Android system dynamically generates a shadow based on this elevation.

When generating shadows, Android also takes into account factors such as the view’s position in relation to the “virtual lights” that illuminate the Material Design environment, but even if your app winds up on a pre-Material Design device (i.e anything running KitKat or earlier) then the system will fall back on an emulated shadow implementation, so your CardViews will still have that shadow effect.

You can use the card_view:cardElevation attribute to define your card’s elevation and shadow in one fell swoop.

Creating a CardView

The Android SDK doesn’t include the CardView class, so you’ll need to add the CardView support library before you can use cards in your project.

Open your project’s module-level build.gradle file, and add the following:

dependencies {
 ...
compile 'com.android.support:cardview-v7:24.2.1'
} 

A CardView is a ViewGroup, so the next step is opening your project’s XML layout resource file and declaring a CardView, in exactly the same way you’d declare any other UI component:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical" >

<android.support.v7.widget.CardView
 xmlns:card_view="http://schemas.android.com/apk/res-auto"
 android:id="@+id/card_view"
 android:layout_gravity="center"
 android:layout_width="300dp"
 android:layout_height="200dp"
 android:layout_margin="5dp"
 card_view:cardCornerRadius="6dp">
 
//Create a LinearLayout inside the CardView//
<LinearLayout
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:orientation="vertical">
 
//Add all the views you want to display//
<TextView
 style="@style/Base.TextAppearance.AppCompat.Headline"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:text="Hello, World!" />
 
<TextView
 style="@style/Base.TextAppearance.AppCompat.Body1"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:text="Text, text, text" />
 
<ImageView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:src="@drawable/pancakes"/>
 
   </LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>

Run this project on your Android device, and you’ll see the following output:

Displaying data in CardView

Creating CardViews programmatically

Creating a CardView declaratively is far easier than delving into your Java code, however there are instances where you might need to define at least some of your UI elements programmatically. The major advantage of this approach, is that it allows you to create dynamic user interfaces that change based on user interaction, such as cards that can appear, disappear, or display different information in response to user activity.

In this section, I’m going to show you how you might use dynamic CardViews, by creating a simple app that displays a card when the user taps a button.

The first step is adding the CardView support library to your project’s module-level build.gradle file:

dependencies {
...
compile 'com.android.support:cardview-v7:24.2.1'

} 

It’s always a good idea to define as much of your UI via XML as possible, as this separation helps to keep your application code more human-readable and easier to maintain. For this reason, I’m going to create the ‘default’ version of my UI in my project’s layout resource file:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical"
  android:layout_marginLeft="20dp"
  android:layout_marginRight="20dp"
  android:layout_marginTop="20dp"
  android:layout_marginBottom="20dp"
  android:id="@+id/linearlayout">

//Create the button that’ll eventually trigger our CardView// 
<Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Show me the CardView"
      android:id="@+id/button"
      android:layout_centerVertical="true"
      android:layout_centerHorizontal="true"
      android:layout_marginBottom="20dp"/>
</LinearLayout>

creating a cardview programmatically

The next task is modifying our MainActivity.java file to generate a CardView (complete with content) in response to the user tapping the button.

package com.jessicathornsby.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; 
import android.widget.Button;
import android.support.v7.widget.CardView;
import android.graphics.Color;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
  Context context;
  LinearLayout LinearLayout;
  Button button;
  TextView textview;
  ImageView imageview1;
  CardView cardview;
  LayoutParams layoutparams;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      button = (Button)findViewById(R.id.button);
      context = getApplicationContext();
      LinearLayout = (LinearLayout)findViewById(R.id.linearlayout);

     // Create an View.OnClickListener and assign it to the button
     button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              CreateMyCardView();
          }
     });
  }

  public void CreateMyCardView(){
      // Initialise the CardView
      cardview = new CardView(context);

      // Create the “wrap_content” layout params that you’ll apply to the various
      // UI elements we’re going to create
      layoutparams = new LayoutParams(
              LayoutParams.WRAP_CONTENT,
              LayoutParams.WRAP_CONTENT
      );

     // Set the layoutParams on the CardView
     cardview.setLayoutParams(layoutparams);

     // Set the card’s corner radius
     cardview.setRadius(6);

     // Set its background color
     cardview.setCardBackgroundColor(Color.GRAY);

     // Set its maximum elevation
     cardview.setMaxCardElevation(6);

     // Create a TextView
     textview = new TextView(context);

     // Apply the layoutParams (wrap_content) to this TextView
     textview.setLayoutParams(layoutparams);

     // Define the text you want to display
     textview.setText("Hello, World!");

     // Define the text’s appearance, including its color
     textview.setTextAppearance(android.R.style.TextAppearance_Material_Headline);
     textview.setTextColor(Color.BLACK);

     // Create an ImageView
     imageview1 = new ImageView(context);

     // Specify the drawable this ImageView should display
     imageview1.setImageResource(R.drawable.scenery);

     // Apply the layoutParams
     imageview1.setLayoutParams(layoutparams);

     // Add the content to your CardView. Here, we’re adding the TextView//
     cardview.addView(textview);

     // Add the ImageView
     cardview.addView(imageview1);

     // Add the CardView to your layout
     LinearLayout.addView(cardview);
  }
}

Install the finished project on your Android device or AVD. Give the button a click and the CardView should appear, complete with the content you specified.

creating dynamic cardviews

Working with GridViews

A GridView is a view that displays items in a two-dimensional, scrollable grid of rows and columns. GridViews are particularly useful for displaying images in a structured fashion, for example if you’re designing a gallery app.

To populate a GridView with data (whether that’s images, text, or a combination of the two) you’ll need to bind your data to the GridView using a ListAdapter. In the following example, I’m going to use a ListAdapter to retrieve data and create a view for each data entry.

The first step is adding the GridView UI component to your project’s XML layout file:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
 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"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin"
 tools:context="com.jessicathornsby.gridviewexample.MainActivity">
 
<GridView 
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/gridview"
 android:layout_width="match_parent"
 android:layout_height="match_parent"

 // Define how many columns you want to display in the GridView//
 android:numColumns="3"

 // Set the vertical spacing between each row. You may also want to use the android:horizontalSpacing
 // to set the horizontal space between each column
 android:verticalSpacing="5dp"/>
</RelativeLayout>

Depending on the style of GridView you have in mind, you may also want to use the android:stretchMode attribute to define how your GridView’s columns should stretch to fill any available space. Choose from the following values:

  • None.
  • spacingWidth. The spacing between each column is stretched equally.
  • columnWidth. Each column is stretched equally.
  • spacingWidthUniform. The spacing between each column is uniformly stretched.

You can also define the padding that should be added to your GridView, using the ‘setPadding’ attribute.

Once you’ve created and styled your GridView UI component, the next step is to modify your project’s MainActivity.java file:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.GridView;
public class MainActivity extends AppCompatActivity { @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Initialise the GridView
    GridView gridview = (GridView) findViewById(R.id.gridview); 

    // Use the setAdapter method to tell your app to use a custom adapter (ImageAdapter) as its data source.
    // We’ll create this adapter in a moment
    gridview.setAdapter(new ImageAdapter(this));
  }
}

The last thing we need to do, is create the custom adapter and attach it to the GridView:

import android.widget.BaseAdapter;
import android.content.Context;
import android.view.ViewGroup;
import android.view.View;
import android.widget.ImageView;
import android.widget.GridView;

// Extend the BaseAdapter class//

public class ImageAdapter extends BaseAdapter {
private Context mContext;

// Define an array of drawables that’ll be displayed in the GridView//
public Integer[] gridviewImages = {
           R.drawable.image1, R.drawable.image2, R.drawable.image3, 
           R.drawable.image4, R.drawable.image5, R.drawable.image6, 
           R.drawable.image7, R.drawable.image8, R.drawable.image9,
  };

  public ImageAdapter(Context c) {
  mContext = c;
}

// Get the number of images in the gridviewImages array//
public int getCount() {
    return gridviewImages.length;
}

public Object getItem(int position) {
  return null;
}

public long getItemId(int position) {
  return 0;
}

// The getView method is responsible for creating an ImageView for each image in our array.
// To provide a better user experience, I’m using the convertView method to specify that the
// Adapter should recycle views whenever possible, instead of creating a new view for every single
// item in our data set. Reusing views that are no longer visible to the user, improves the app’s
// performance, as the system doesn’t have to keep inflating views and doesn’t waste memory keeping
// a bunch of unnecessary views alive in the background
public View getView(int position, View convertView, ViewGroup parent) {
    ImageView imageView;

    // Check whether convertView is null
    if (convertView == null) { 
        // If convert is null, then this means we cannot recycle an old view,
        // and need to create a new one
        imageView = new ImageView(mContext); 

        // To make sure each image is displayed as you intended, you may need to assign some properties to
        // your ImageViews. I’m going to use setLayoutParams to specify how each image should be resized
        imageView.setLayoutParams(new GridView.LayoutParams(300, 300)); 

        // setScaleType defines how the image should be scaled and positioned. I’m using the CENTER_CROP
        // value as this maintains the image’s aspect ratio by scaling it in both directions, and then
        // centers the newly-scaled image.
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    } else { 
        // If the view passed to getView is not null, then recycle the view
        imageView = (ImageView) convertView;
    }

    // Use the position integer to select an image from the gridviewImages array, and set it to the
    // ImageView we just created
    imageView.setImageResource(gridviewImages[position]);
    return imageView;
  }
}

At this point you can run the application and check out the GridView in action.

Displaying data in GridView

Data Binding

Gridviews and CardViews are just two examples of many Android UI components that are designed to display data. Despite data being such an important part of many Android apps, until recently data binding required a considerable amount of code.

Typically, developers had to inflate their layout, use findViewbyID to locate the element that was going to use the data, assign it a local variable, retrieve a value from the data, and then assign this value to the UI element. This is already a significant amount of code, but if the layout featured multiple elements that needed to use data (such as multiple CardViews) then this could cause the data binding code to really spiral out of control.

An improvement to Android’s data binding was long overdue, and that’s exactly what we got at Google I/O 2015, when Google announced the Data Binding support library.

This library helps you bind data to a UI element without having to write so much “glue” code. Eliminating findViewById lookups in particular means faster code, as the view hierarchy is only traversed once, rather than every time findViewById is called.

To configure your app to use data binding, open your project’s module-level build.gradle file and add the dataBinding element:

android {
    ....
    dataBinding {
      enabled = true
    }
}

If you want to use data binding in a layout resource file, you need to configure this file slightly differently. Instead of declaring the target root View, you need to use ‘layout’ as your root tag, followed by a ‘data’ element, for example:

<?xml version="1.0" encoding="utf-8"?>
//The layout tag indicates that this layout file should be configured for data binding//
<layout xmlns:android="http://schemas.android.com/apk/res/android">

  // Add a data tag before the UI view root, and declare the variables and the classes you want
  // to use inside your layout
  <data>
      <variable name="contact" type="com.jessicathornsby.contact"/>
  </data>

Next, I’m going to use this data binding to set the value of a TextView inside a CardView:

<android.support.v7.widget.CardView
  xmlns:card_view="http://schemas.android.com/apk/res-auto"
...
...
<TextView android:layout_width="wrap_content"
         android:layout_height="wrap_content"

//Set the value of our TextView, using the "@{}" syntax//

         android:text="@{contact.contactName}"/>

The data objects that can be used for data binding don’t need to be a special type, so in this example the “Contact” target object could simply be a POJO.

The Gradle plugin auto generates a binding class from your layout file and assigns it the name of your layout resource file, with the addition of the “Binding” suffix. So, if our layout file was main_activity.xml, Gradle would generate a MainActivityBinding class. To associate this auto generated binding class with your code, you can either use:

MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);

Or:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

Data binding is a huge topic that’s well worth exploring in more detail, particularly if you’re planning on displaying large amounts of data, or your layouts include multiple elements that need to use data in some way. For a more detailed look at the Data Binding library, take a look at our Data Binding in Android article.

Wrapping up

In this article we looked at two ways of displaying large amounts of data in a structured, accessible way, by adding cards and grids to your layouts. We also looked at how to use a simple custom Adapter and the Data Binding library to supply your UI with data.

Do you have anymore tips for the best ways of displaying large amounts of data in your Android apps?  Share them in the comments below!