Android Custom GridView - CardView With Images and Text


Android Custom GridView - CardView With Images and Text Tutrial

How to create a custom gridview containing CardViews with text and images in android studio and listen to each CardView's Click event. We do this for both Java and Kotlin.

1. Android Custom GridView - CardView With Images and Text

This first tutorial is written in java.

The Plan

  • Create a Custom GridView With CardViews as our ViewItems.
  • Each cardview will be our viewitem hence shall have images and text.
  • We then handle ItemClicks for the CardViews. When clicked we show a Toast message containing the clicked text.
  • We are using BaseAdapter as our adapter of choice.

Demo

Here's the demo for the Java Project:

Tools Used

This example was written with the following tools:

Lets jump directly to the source code. Source code is well commented. Furthermore we have explained everything in the video tutorial.

(a). Build.Gradle

We will be using CardView, which is a support library as opposed to a Framework APIs. Hence support libraries normally have to be added as a dependency. And that always take place in the app level build.gradle.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.3.0'
    compile 'com.android.support:cardview-v7:23.3.0'
}

We add a CardView using the an implementation statement these days: implementation com.android.support:cardview-v7:lateset_version.

(b). Spacecraft.java

This is the class that will be our POJO class, otherwise known as bean class or data object. In this case we are using Spacecraft as our data object. Each Spacecraft has a name, propellant and an image,

package com.tutorials.hp.customgridviewcardviews;

public class Spacecraft {
    int image;
    String name,propellant;

    public int getImage() {
        return image;
    }
    public void setImage(int image) {
        this.image = image;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPropellant() {
        return propellant;
    }
    public void setPropellant(String propellant) {
        this.propellant = propellant;
    }
}

You can see we've supplied it with getter and setter methods.

(c). CustomAdapter.java

Whenever you are working with adapterviews like GridView, you always need an adapter. By design, android does not allow manual inserting of data into these adapterviews like say .NET Windows Forms and JavaFX. In android data is added to an adapter, the adapter is set to the adapterview.

This design decision provides a big advantage in terms of flexibility and decouplement of views from data. Thus this allows us write more quality while creating highly flexible applications. The role of the view is just to show data and listen to events. The adapter on the hand handles data as well as custom layouts as we see here.

There are several adapters as can be seen here. The most commonly used with gridview is the baseadapter. It is the one we use here.

We start by making several imports.

  1. LayoutInflater - It is defined in the android.view package. It's the class we use for inflating our custom layout:
        if(view==null)
        {
            view= LayoutInflater.from(c).inflate(R.layout.model,viewGroup,false);
        }

This inflation is an expensive process. Inflating our XML layout for every data item in our data source can make our app noticeably slow and junky as the user scrolls. So normally we instead of inflating for every data item, we inflate for a single data item then reuse the inflated view for all the other data items. This leads to dramatic performance improvements since XML processing which is basically what the inflation involves has never been cheap. That's why we've checked if the view was null as above.

  1. BaseAdapter - defined in the android.wideget package. This is our adapter and it is an abstract class so we have to override several methods like the:

(a). getCount

To return us the number of spacecrafts to be used by the adapter.

    @Override
    public int getCount() {
        return spacecrafts.size();
    }

(b). getItem

To return the current item in the list. We pass the position to get that item.

    @Override
    public Object getItem(int i) {
        return spacecrafts.get(i);
    }

(c). getItemId

To return the id we are using for identification purposes.

   @Override
    public long getItemId(int i) {
        return i;
    }

(d). getView

The one we modify the most. Remember the plan is that the GridView should be showing cardviews with images and text as the item views. It is inside this method where the work occurs. The first part of that work involves inflation which we've seen above.

Then getting the current spacecraft:

        final Spacecraft s= (Spacecraft) this.getItem(i);

We've made it final as it will be called from an inner class.

Then referencing our the various widgets that will be rendered in our CardViews:

        ImageView img= (ImageView) view.findViewById(R.id.spacecraftImg);
        TextView nameTxt= (TextView) view.findViewById(R.id.nameTxt);
        TextView propTxt= (TextView) view.findViewById(R.id.propellantTxt);

Make sure you reference them from the actual layout that shall be inflated by our LayoutInflater.

Then once the inflation and referencing has been done, we come to data binding:

        nameTxt.setText(s.getName());
        propTxt.setText(s.getPropellant());
        img.setImageResource(s.getImage());

As you can see we've used the basic setText() methods for the TextViews and the setImageResource() for the ImageView.

What about handling itemClicks for our custom gridview? Well remember user will be clicking the CardViews. So we just listen to the click events for the inflated view.

        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(c, s.getName(), Toast.LENGTH_SHORT).show();
            }
        });

In our case we've show a Toast message. In some cases you may need to open a new activity. In that case you just use Intent to do that inside the onClick.

Here's this class as a whole:

package com.tutorials.hp.customgridviewcardviews;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;

public class CustomAdapter extends BaseAdapter{
    Context c;
    ArrayList<Spacecraft> spacecrafts;

    public CustomAdapter(Context c, ArrayList<Spacecraft> spacecrafts) {
        this.c = c;
        this.spacecrafts = spacecrafts;
    }

    @Override
    public int getCount() {
        return spacecrafts.size();
    }

    @Override
    public Object getItem(int i) {
        return spacecrafts.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        if(view==null)
        {
            view= LayoutInflater.from(c).inflate(R.layout.model,viewGroup,false);
        }

        final Spacecraft s= (Spacecraft) this.getItem(i);

        ImageView img= (ImageView) view.findViewById(R.id.spacecraftImg);
        TextView nameTxt= (TextView) view.findViewById(R.id.nameTxt);
        TextView propTxt= (TextView) view.findViewById(R.id.propellantTxt);

        //BIND
        nameTxt.setText(s.getName());
        propTxt.setText(s.getPropellant());
        img.setImageResource(s.getImage());

        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(c, s.getName(), Toast.LENGTH_SHORT).show();
            }
        });

        return view;
    }
}

MainActivity.java

This is the main activity, mostly used as the launcher activity. You can read more about activities here or more specifically appcompatactivity here.

But in this case among our imports you can see we have a GridView which is our adapterview. It will render our custom cardviews. Then when user clicks a single cardview a Toast message shall be shown.

We also have an ArrayList, which we use as our data source. Of course we add some data there first.

package com.tutorials.hp.customgridviewcardviews;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.GridView;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    CustomAdapter adapter;
    GridView gv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        gv= (GridView) findViewById(R.id.gv);

        adapter=new CustomAdapter(this,getData());
        gv.setAdapter(adapter);

    }
    private ArrayList getData()
    {
        ArrayList<Spacecraft> spacecrafts=new ArrayList<>();

        Spacecraft s=new Spacecraft();
        s.setName("Pioneer");
        s.setPropellant("Chemical Energy");
        s.setImage(R.drawable.pioneer);
        spacecrafts.add(s);

        s=new Spacecraft();
        s.setName("Spitzer");
        s.setPropellant("Warp Drive");
        s.setImage(R.drawable.spitzer);
        spacecrafts.add(s);

        s=new Spacecraft();
        s.setName("Enterprise");
        s.setPropellant("Anti Matter");
        s.setImage(R.drawable.enterprise);
        spacecrafts.add(s);

        s=new Spacecraft();
        s.setName("Hubble");
        s.setPropellant("Laser Beam");
        s.setImage(R.drawable.herbal);
        spacecrafts.add(s);

        s=new Spacecraft();
        s.setName("Voyager");
        s.setPropellant("Solar Energy");
        s.setImage(R.drawable.voyager);
        spacecrafts.add(s);

        s=new Spacecraft();
        s.setName("Kepler");
        s.setPropellant("Solar Energy");
        s.setImage(R.drawable.kepler);
        spacecrafts.add(s);

        s=new Spacecraft();
        s.setName("Rosetter");
        s.setPropellant("Nuclear Energy");
        s.setImage(R.drawable.rosetta);
        spacecrafts.add(s);

        s=new Spacecraft();
        s.setName("WMAP");
        s.setPropellant("Nuclear Energy");
        s.setImage(R.drawable.wmap);
        spacecrafts.add(s);

        s=new Spacecraft();
        s.setName("Columbia");
        s.setPropellant("Chemical Energy");
        s.setImage(R.drawable.columbia);
        spacecrafts.add(s);

        return spacecrafts;
    }

}

activity_main.xml

This is the layout that contains our GridView. We have wrapped it by a relativelayout. Am using two columns because my images are abit larger.

<?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.tutorials.hp.customgridviewcardviews.MainActivity">

    <GridView
        android:id="@+id/gv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:numColumns="2"
         />
</RelativeLayout>

Model.xml

This is the custom layout that will define a single view item in our gridview. This layout will get inflated by the LayoutInflater as we discussed earlier. That inflation will take place inside our custom adapter.

At the root of our layout we've used a CardView, a support library that allows us display cards that can have several properties. Such properties include elevation and custom corner radius. CardViews allow us make modern apps.

Inside the CardView we will have ImageView to render image and TextViews to render text.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_margin="10dp"
    card_view:cardCornerRadius="5dp"
    card_view:cardElevation="5dp"
    android:layout_height="200dp">

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:id="@+id/spacecraftImg"
                android:src="@drawable/spitzer"
                android:layout_width="150dp"
                android:layout_height="wrap_content" />

            <LinearLayout
                android:orientation="vertical"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceLarge"
                    android:text="Name"
                    android:id="@+id/nameTxt"
                    android:padding="10dp"
                    android:textColor="@color/colorAccent"
                    android:textStyle="bold"
                    android:layout_alignParentLeft="true"
                    />
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceLarge"
                    android:text="Propellant....................."
                    android:lines="1"
                    android:id="@+id/propellantTxt"
                    android:padding="10dp"
                    android:layout_alignParentLeft="true"
                    />
            </LinearLayout>
            </LinearLayout>
</android.support.v7.widget.CardView>

 

Download

You can download the full project here.

No. Resource Direct Links
1. GitHub Source Code Browse
2. Source Code Download
3. YouTube Video Tutorial
4. Facebook Page
5. YouTube Channel

2. Kotlin Android Custom GridView - CardViews with Images, Text and OnItemClick

Kotlin Android Custom GridView - CardViews with Images, Text and OnItemClick Tutorial

In this Kotlin Android tutorial, we see first how to populate a custom gridview with images and text. The images and text will bea rranged in custom view that has the CardView as the rootview.

We then listen to GridView item click events and show the selected item.

We are building a simple Quotes application to show a list of quotes, author names and author images.

Remember our programming language in this case is Kotlin.

Let's go.

Video Tutorial(ProgrammingWizards TV Channel)

There is also a video tutorial by the way.

Well if you prefer tutorials like this one then it would be good you subscribe to our YouTube channel. Basically we have a TV for programming where do daily tutorials especially android.

Demo

Let's now look at an example.

Kotlin Android Custom GridView Images and Text

Let's write some code.

Layouts

We have two layouts:

  1. activity_main.xml - To be inflated into MainActivity's UI.
  2. row_model.xml - To model a single grid for our custom gridview. Has a CardView as the root XML element.
(a). activity_main.xml

This layout will get inflated into the main activity's user interface. This will happen via the Activity's setContentView() method which will require us to pass it the layout.

We will do so inside the onCreate() method of Activity.

We will be using three elements:

  1. LinearLayout - a Viewgroup that organizes its children lineary.
  2. TextView - To show our header text.
  3. GridView - To show our data in scrollable grids.

Here's the code:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context="info.camposha.kotlingridviewcardviews.MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Zen Quotes App"
        android:textAlignment="center"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textColor="@color/colorAccent" />

    <GridView
        android:id="@+id/myGridView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:numColumns="auto_fit" />

</LinearLayout>
(b). row_model.xml

This layout will be inflated inside our CustomAdapter's getView() method into a view item for our GridView.

Some of the widgets we use include:

  1. TextView - To render text
  2. ImageView - To render images.

Hence courtesy of this layout and our CustomAdapter class, our Gridview will have both images and text.

Here's the full code:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_margin="5dp"
    card_view:cardCornerRadius="5dp"
    card_view:cardElevation="10dp"
    android:layout_height="300dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="150dp"
            android:padding="10dp"
            card_view:srcCompat="@android:color/holo_red_light" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:text="Name"
                android:id="@+id/quoteTxt"
                android:padding="5dp"
                android:textColor="@color/colorAccent"
                android:layout_below="@+id/imageView"
                android:layout_alignParentLeft="true"
                />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:text=" Author --------------------"
                android:id="@+id/authorTxt"
                android:padding="5dp"
                android:layout_below="@+id/quoteTxt"
                android:layout_alignParentLeft="true"
                />

    </RelativeLayout>

</android.support.v7.widget.CardView>

Kotlin Code

Android apps can be mainly written in Java or Kotlin. These days however there are many frameworks like Flutter also which use languages like Dart.

In this class we are using Kotlin programming language.

We will have these classes in our project.

(a). MainActivity.kt

This is our main activity as the name suggests. This means it will be the main entry point to our app in that when the user clicks the icon for our app, this activity will get rendered first.

We override a method called onCreate(). Here we will start by inflating our main layout via the setContentView() method.

Our main activity is actually an activity since it's deriving from the AppCompatActivity.

Remember we are writing our code in Kotlin.

Top Questions our MainActivity.kt will Answer For us?

1. How to Create an Activity in Kotlin

Well you do that by making an ordinary class derive from AppCompatActivity. Kotlin is a modern statically typed language that has full support for Object oriented capabilities including Inheritance which we utilize here:

class MainActivity : AppCompatActivity() {..}

2. How to Create a Data Object in Kotlin

Well a data object, also sometimes called a model class or a business object is a popular pattern for data representation. It's very easily understandable and is also an example of Kotlin's Object orientation through Encapsulation.

Like in this case our data object encapsulates us a single a Quote with these properties:

  1. Quote Author.
  2. Quote Text.
  3. Quote Author's Image
    class Quote(private var quote:String, private var author: String, private var image: Int) {

        fun getQuote(): String { return quote  }
        fun getAuthor(): String { return author }
        fun getImage(): Int { return image  }
    }

3. How to Create an Adapter For a GridView in Kotlin

Well you do that by deriving from any of the several Adapter sub-classes. In this case we will use the most commonly used one, the BaseAdapter.

Just have you class derive from the BaseAdapter:

    class CustomAdapter(private var c: Context, private var quotes: ArrayList<Quote>) : BaseAdapter() {...}

In our case we are also injecting several objects into our adapter class including:

  1. Context - To be used during inflation.
  2. ArrayList - To hold our quotes list. It is those quotes that we will be binding to our custom gridview.

4. How to set an adapter to a GridView

Well first you just reference the gridview from he layout using the findViewById() method.

   gv = findViewById(R.id.myGridView) as GridView     

Then instantiate the CustomAdapter and set it as the adapter property of our GridView.

        //instantiate and set adapter
        adapter = CustomAdapter(this, data)
        gv.adapter = adapter

Here's the full code:

package info.camposha.kotlingridviewcardviews

import android.content.Context
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import java.util.*

class MainActivity : AppCompatActivity() {
    /*
   Our data object class.
    */
    class Quote(private var quote:String, private var author: String, private var image: Int) {

        fun getQuote(): String { return quote  }
        fun getAuthor(): String { return author }
        fun getImage(): Int { return image  }
    }
    /*
    Our Custom Adapter class. Derives from BaseAdapter.
     */
    class CustomAdapter(private var c: Context, private var quotes: ArrayList<Quote>) : BaseAdapter() {

        override fun getCount(): Int   {  return quotes.size  }
        override fun getItem(i: Int): Any {  return quotes[i] }
        override fun getItemId(i: Int): Long { return i.toLong()}

        override fun getView(i: Int, view: View?, viewGroup: ViewGroup): View {
            var view = view
            if (view == null) {
                //inflate layout resource if its null
                view = LayoutInflater.from(c).inflate(R.layout.row_model, viewGroup, false)
            }

            //get current quote
            val quote = this.getItem(i) as Quote

            //reference textviews and imageviews from our layout
            val img = view!!.findViewById<ImageView>(R.id.imageView) as ImageView
            val nameTxt = view.findViewById<TextView>(R.id.quoteTxt) as TextView
            val propTxt = view.findViewById<TextView>(R.id.authorTxt) as TextView

            //BIND data to TextView and ImageVoew
            nameTxt.text = quote.getQuote()
            propTxt.text = quote.getAuthor()
            img.setImageResource(quote.getImage())

            //handle itemclicks for the GridView
            view.setOnClickListener { Toast.makeText(c, quote.getQuote(), Toast.LENGTH_SHORT).show() }

            return view
        }
    }
    //Main Activity Instance Fields.
    private lateinit var adapter: CustomAdapter
    private lateinit var gv: GridView
    // our data source
    private val data: ArrayList<Quote>
        get() {
            val quotes = ArrayList<Quote>()

            var quote = Quote("Out beyond ideas of wrongdoing and rightdoing there is a field.I'll meet you there." +
                    "When the soul lies down in that grass the world is too full to talk about.","Rumi",R.drawable.rumi)
            quotes.add(quote)
            quote= Quote("Walk as if you are kissing the Earth with your feet.","Thich Nhat Hanh",R.drawable.thich)
            quotes.add(quote)
            quote= Quote("Man suffers only because he takes seriously what the gods made for fun.","Allan Watts",R.drawable.allan_watts)
            quotes.add(quote)
            quote= Quote("I have lived with several Zen masters -- all of them cats.","Eckhart Tolle",R.drawable.eckhart)
            quotes.add(quote)
            quote= Quote("I'm simply saying that there is a way to be sane. I'm saying that you can get rid of all this insanity created" +
                    " by the past in you. Just by being a simple witness of your thought processes.","Osho",R.drawable.osho)
            quotes.add(quote)
            quote= Quote("The way out is through the door. Why is it that no one will use this method?","Confucius",R.drawable.confucius)
            quotes.add(quote)
            quote= Quote("It is the power of the mind to be unconquerable.","Senecca",R.drawable.jiddu)
            quotes.add(quote)
            quote= Quote("It's like you took a bottle of ink and you threw it at a wall. Smash! And all that ink spread. And in " +
                    "the middle, it's dense, isn't it? ","Allan Watts",R.drawable.allan_watts)
            quotes.add(quote)
            quote= Quote("Only the hand that erases can write the true thing.","Meister Eckhart",R.drawable.allan_watts)
            quotes.add(quote)
            quote= Quote("Many have died; you also will die. The drum of death is being beaten. The world has fallen in love with a " +
                    "dream. Only sayings of the wise will remain."," Kabir",R.drawable.osho)
            quotes.add(quote)
            quote= Quote("Where there are humans, You'll find flies,And Buddhas.","Kobayashi Issa",R.drawable.eckhart)
            quotes.add(quote)
            quote= Quote("Silence is the language of Om. We need silence to be able to reach our Self. Both internal and external " +
                    "silence is very important to feel the presence of that supreme Love.","Amit Ray",R.drawable.jiddu)
            quotes.add(quote)
            quote= Quote("One day in my shoes and a day for me in your shoes, the beauty of travel lies in the ease and willingness " +
                    "to be more open.","Forrest Curran",R.drawable.confucius)
            quotes.add(quote)
            quote= Quote("Like vanishing dew,a passing apparition or the sudden flashnof lightning -- already gone -- thus should" +
                    " one regard one's self.","Ikyyu",R.drawable.thich)
            quotes.add(quote)
            quote= Quote("A student, filled with emotion and crying, implored, 'Why is there so much suffering? Suzuki Roshi " +
                    "replied, No reason.' ","Suzuki Roshi",R.drawable.rumi)
            quotes.add(quote)

            return quotes
        }
    /*
    When activity is created, reference gridview and set its adapter
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        gv = findViewById(R.id.myGridView) as GridView

        //instantiate and set adapter
        adapter = CustomAdapter(this, data)
        gv.adapter = adapter
    }
}

How do You Feel after reading this?

According to scientists, we humans have 8 primary innate emotions: joy, acceptance, fear, surprise, sadness, disgust, anger, and anticipation. Feel free to tell us how you feel about this article using these emotes or via the comment section. This feedback helps us gauge our progress.

Help me Grow.

I set myself some growth ambitions I desire to achieve by this year's end regarding this website and my youtube channel. Am halfway. Help me reach them by:




Recommendations


What do You Think


Previous Post Next Post