Monday, April 7, 2014

Java: Creating a ListView, populating it with a custom layout, and registering clicks in Android.

    ListView, is, without a doubt, one of the most important Views in Android SDK. A listView is usually populated using an array adapter, a class that takes an array of some kind and populates the ListView with it.  This makes populating the ListView with, lets say, a Strings array very easy. But most of the time, you want to populate your listView with a custom layout, that usually contains multiples of strings, buttons and the likes. In order to do this, you'll need to create a class that extends an ArrayAdapter and handles populating a ListView with the provided layout. Alright, lets get to it!
 
    First, fire up the Android Studio (or Eclipse with the android plugin installed, whichever your prefer) and create a new project. I named my project "ListViewExample". Minimum required SDK - API 16: Android 4.1 (but anything lower than that should work too). Leave everything else as is.



Now you should have an activity called "MainActivity" and two layouts - "activity_main.xml" and "fragment_main.xml" (the latter only if you're using the android studio). This step is optional and only applies if you're using the Android Studio  -  delete fragment_main.xml from your layout folder. Then go to your activity and delete the following chunks of code:

1
2
3
4
5
if (savedInstanceState == null) {
       getFragmentManager().beginTransaction()
           .add(R.id.container, new PlaceholderFragment())
            .commit();
     }
and

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
      // Inflate the menu; this adds items to the action bar if it is present.
     getMenuInflater().inflate(R.menu.main, menu);
      return true;
    }
   @Override
    public boolean onOptionsItemSelected(MenuItem item) {
     // Handle action bar item clicks here. The action bar will
     // automatically handle clicks on the Home/Up button, so long
     // as you specify a parent activity in AndroidManifest.xml.
     int id = item.getItemId();
     if (id == R.id.action_settings) {
        return true;
      }
     return super.onOptionsItemSelected(item);
    }
    /**
     * A placeholder fragment containing a simple view.
     */
    public static class PlaceholderFragment extends Fragment {
      public PlaceholderFragment() {
      }
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
          Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_main, container, false);
:        return rootView;
      }
    }
W're not going to use fragments or the menu button in this project.

Next go to activity_main.xml and replace everything in there with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.tysovsky.listviewexample.MainActivity"
android:orientation="vertical">

<ListView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/listView"
    android:layout_gravity="center_horizontal" />
</LinearLayout>
Now you should see a ListView that fills the entire screen of the app.



The next step is creating the layout of every individual entry in the list. In the layout folder create a new layout called content_layout.xml. Here's the simple xml I used:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>

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

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/content_image"
        android:src="@drawable/ic_launcher"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="Title"
        android:id="@+id/content_title"
        android:layout_alignParentTop="true"
        android:layout_toRightOf="@+id/content_image" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="Subtitle"
        android:id="@+id/content_subttitle"
        android:layout_below="@+id/content_title"
        android:layout_toRightOf="@+id/content_image" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="Description"
        android:id="@+id/content_description"
        android:layout_below="@+id/content_subttitle"
        android:layout_toRightOf="@+id/content_image" />
</RelativeLayout>
Of course you can modify it to fit any of your needs.



If you were to compile & run the app right now, you will only see a blank page. The listView we've created earlier hasn't been populated yet. To populate the list, we're gonna use an Object Oriented approach and create a class that houses all of our values. Create a new java class called content. It's gonna take 4 parameters as its constructor: Bitmap "image" and three strings - "title", "subtitle" and "description". Create all the setters and getters. Here's my final code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.tysovsky.listviewexample;

import android.graphics.Bitmap;

/**
 * Created by tysovsky on 4/7/14.
 */
public class content {
    Bitmap image;
    String title;
    String subtitle;
    String description;

    public content(Bitmap bmp, String ttl, String subttl, String desc){
        this.image = bmp;
        this.title = ttl;
        this.subtitle = subttl;
        this.description = desc;
    }

    public Bitmap getImage(){
        return this.image;
    }

    public String getTitle(){
        return this.title;
    }

    public String getSubtitle(){
        return this.subtitle;
    }

    public String getDescription(){
        return this.description;
    }
}

Go back to our MainActivity. Let's create our array of content now. Above the onCreate() method add the following line:

1
List ContentList = new ArrayList();pre>
Lets create some more variables. Below the previous line add these two lines:

1
2
  Bitmap image;  
  ListView list; 

The first line is a bitmap that we'll pas as a constructor of our content class later. Of course you can pass any image, whether it's stored locally or was downloaded from the internet.  The second line is a listView that we will later reference to the listView from the activity_main.xml. Inside the onCreate() function insert the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
        list = (ListView) findViewById(R.id.listView);

        //Create a bitmap from ic_laucher that is located at /drawable folders by default, this will be displayed as an image of every item on the list
        image = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

        ContentList.add(new content(image, "Title1", "Subtitle1", "Some description here1"));
        ContentList.add(new content(image, "Title2", "Subtitle2", "Some description here2"));
        ContentList.add(new content(image, "Title3", "Subtitle3", "Some description here3"));
        ContentList.add(new content(image, "Title4", "Subtitle4", "Some description here4"));
        ContentList.add(new content(image, "Title5", "Subtitle5", "Some description here5"));

Now let's create an ArrayAdapter and populate the list view with it.

1
2
  ArrayAdapter<content> adapter = new CustomArrayAdapter();  
  list.setAdapter(adapter);  

And here comes the exiting part, we're finally gonna write our CustomArrayAdapter class! Create a new class inside your MainActivity:

1
2
3
4
5
6
7
private class CustomArrayAdapter extends ArrayAdapter<content>{
        //Class constructor
        public CustomArrayAdapter(){
            //Takes context(MainActivity.this), layout to populate with(R.layout.content_layout) and an array to populate with(ContentList)
            super(MainActivity.this, R.layout.content_layout, ContentList);
        }
}
As you can see, an ArrayAdapter requires 3 constructors. First one is a context (MainActivity.this), the second one is a layout of our content (R.layout.content_layout), and finally a list that we're gonna use to populate the list view(ContentList).
Now lets create a function inside the CustomArrayAdapter class that will return the view that we need:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    @Override
    public View getView(int position, View convertView, ViewGroup parent){
        View contentView = convertView;

        if(contentView == null){
            //If view was not returned, create it
            contentView = getLayoutInflater().inflate(R.layout.content_layout, parent, false);
        }

        //Get the current content we're working with
        content currentContent = ContentList.get(position);

        //Reference the ImageView from the xml
        ImageView image = (ImageView) contentView.findViewById(R.id.content_image);
        //Set it to our bmp
        image.setImageBitmap(currentContent.getImage());


        //Reference the rest of the variables and set properties of the current content as their value
        TextView titleTextView = (TextView) contentView.findViewById(R.id.content_title);
        titleTextView.setText(currentContent.getTitle());

        TextView subTitleText = (TextView) contentView.findViewById(R.id.content_subttitle);
        subTitleText.setText(currentContent.getSubtitle());

        TextView descriptionTextView = (TextView) contentView.findViewById(R.id.content_description);
        descriptionTextView.setText(currentContent.getDescription());

        return contentView;
    }

ListView has its own OnClickListener called OnItemClickListener. We will use it to listen for clicks on out list. Write the following code inside of your OnCreate() function:


1
2
3
4
5
6
7
//Listen for clicks on the list items
        list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Toast.makeText(MainActivity.this, "You've pressed item #" + i, Toast.LENGTH_SHORT).show();
            }
        });

And last but not least, we might want to listen for clicks of a listView child views. I am gonna listen for clicks on every imageView inside of every item inside the ListView. To do this put the following code inside your getView() function of the CustomArrayAdapter class:


1
2
3
4
5
6
7
8
9
//Add a tag to the ImageView so we could differentiate between them later on
            image.setTag(new Integer(position));
            //Listen for clicks
            image.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(MainActivity.this, "You've clicked an image in item#" + view.getTag().toString(),Toast.LENGTH_LONG ).show();
                }
            });

And here's the entire code of my MainActivity for those whoo need it
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
com.tysovsky.listviewexample;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    List<content> ContentList = new ArrayList<content>();
    Bitmap image;
    ListView list;

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

        list = (ListView) findViewById(R.id.listView);

        ArrayAdapter<content> adapter = new CustomArrayAdapter();
        list.setAdapter(adapter);


        //Listen for clicks on the list items
        list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Toast.makeText(MainActivity.this, "You've pressed item #" + i, Toast.LENGTH_SHORT).show();
            }
        });

        //Create a bitmap from ic_laucher that is located at /drawable folders by default, this will be displayed as an image of every item on the list
        image = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

        ContentList.add(new content(image, "Title1", "Subtitle1", "Some description here1"));
        ContentList.add(new content(image, "Title2", "Subtitle2", "Some description here2"));
        ContentList.add(new content(image, "Title3", "Subtitle3", "Some description here3"));
        ContentList.add(new content(image, "Title4", "Subtitle4", "Some description here4"));
        ContentList.add(new content(image, "Title5", "Subtitle5", "Some description here5"));

    }

    private class CustomArrayAdapter extends ArrayAdapter<content>{
        //Class constructor
        public CustomArrayAdapter(){
            //Takes context(MainActivity.this), layout to populate with(R.layout.content_layout) and an array to populate with(ContentList)
            super(MainActivity.this, R.layout.content_layout, ContentList);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent){
            View contentView = convertView;

            if(contentView == null){
                //If view was not returned, create it
                contentView = getLayoutInflater().inflate(R.layout.content_layout, parent, false);
            }

            //Get the current content we're working with
            content currentContent = ContentList.get(position);

            //Reference the ImageView from the xml
            ImageView image = (ImageView) contentView.findViewById(R.id.content_image);
            //Set it to our bmp
            image.setImageBitmap(currentContent.getImage());

            //Add a tag to the ImageView so we could differentiate between them later on
            image.setTag(new Integer(position));
            //Listen for clicks
            image.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(MainActivity.this, "You've clicked an image in item#" + view.getTag().toString(),Toast.LENGTH_LONG ).show();
                }
            });

            //Reference the rest of the variables and set properties of the current content as their value
            TextView titleTextView = (TextView) contentView.findViewById(R.id.content_title);
            titleTextView.setText(currentContent.getTitle());

            TextView subTitleText = (TextView) contentView.findViewById(R.id.content_subttitle);
            subTitleText.setText(currentContent.getSubtitle());

            TextView descriptionTextView = (TextView) contentView.findViewById(R.id.content_description);
            descriptionTextView.setText(currentContent.getDescription());

            return contentView;
        }
    }
}