android listview custom adapter

This post describes how to use Android ListView custom adapter. In the last post, we talked about how we can interact with the user handling the item click events and the long click events. We saw also how to add and remove items dynamically from the ListView using the adapter. Now we want to go a bit further and start analyzing how the ListView can be customized. In this post we will talk about two different customization aspects:

  • Custom Layout
  • Custom Adapter

If you want to download the Android source code click here:

Android ListView with custom layout

The first thing we want to discover is how we can implement a custom layout. In the last post we used a prebuilt layout shipped with Android SDK and we saw how simple is adding and removing items. To keep things simple, let’s suppose we want a custom layout built by a Planet name and under it a Number, that is the distance. Let’s reuse the Planet tutorial and assume we want to have something like that:Android ListView custom row layout
where each item row is like the one shown above.
The first step while creating Android ListView custom adapter is to define the row layout. So we go under res directory in our Android project and we create a new layout called row_layout.xml. Considering we want to have two line for each row we will use a LinearLayout with the vertical orientation. We have then:

<xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent" 
      android:layout_height="match_parent" 
      android:orientation="vertical">

  <TextView android:id="@+id/name" 
      android:layout_width="match_parent" 
      android:layout_height="match_parent" 
      android:layout_weight="1" 
      android:textStyle="bold"/>

  <TextView android:id="@+id/dist" 
      android:layout_width="match_parent" 
      android:layout_height="match_parent" 
      android:textSize="8dp" 
      android:textStyle="italic"/>
</LinearLayout>

The next step is how can we use this layout for our ListView rows? We could reuse the example shown in the last post and match the new view id in the row layout with the new values. We want to go further and introduce a custom adapter. This adapter will manage our layout. To not re-invent the wheel, we can create a new class called PlanetAdapter and extend the ArrayAdapter, in this way:

public class PlanetAdapter extends ArrayAdapter<Planet> {
 private List<Planet> planetList;
 private Context context;

 public PlanetAdapter(List<Planet> planetList, Context ctx) {
  super(ctx, R.layout.row_layout, planetList);
  this.planetList = planetList;
  this.context = ctx;
 }

 public View getView(int position, View convertView, ViewGroup parent) {

  // First let's verify the convertView is not null
  if (convertView == null) {
   // This a new view we inflate the new layout
   LayoutInflater inflater = (LayoutInflater) 
         context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   convertView = inflater.inflate(R.layout.row_layout, parent, false);
  }
  // Now we can fill the layout with the right values
  TextView tv = (TextView) convertView.findViewById(R.id.name);
  TextView distView = (TextView) convertView.findViewById(R.id.dist);
  Planet p = planetList.get(position);

  tv.setText(p.getName());
  distView.setText("" + p.getDistance());

  return convertView;
}

Let’s analyze this class. In the constructor we reference the new layout calling super constructor.

What’s now? Well, we need to override some methods in our class in order to customize the standard ArrayLayout. We have to consider that each time a row is created the ListView calls getView method in the custom adapter. So if we want to modify the layout of each row, we have just to override the getView. An important aspect we have to consider is that the Android OS is very smart and tries to reuse the view we created to show each row. In other word, Android OS doesn’t create for each row in the ListView a corresponding view but only for the rows that are visible, reusing the same views if the user scrolls up and down. So the first thing we have to do in this method is verifying if the view we get as a parameter is null (so it is the first time) or is not null so we are reusing an existing view. If the view is null, we have simply inflate our layout, otherwise we can directly use findViewById to find out components. You can see this in the code:

if (convertView == null) {
  // This a new view we inflate the new layout
  LayoutInflater inflater = (LayoutInflater) 
    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  convertView = inflater.inflate(R.layout.row_layout, parent, false);
}

The next step is to update the view with the right information. Let’s suppose we have only the Planet name and the sun distance (in our case is just a fake value). So we use findViewById in the layout, we inflated before, to get the reference to the view we want to update and then simply set the new value.

// Now we can fill the layout with the right values
TextView tv = (TextView) convertView.findViewById(R.id.name);
TextView distView = (TextView) convertView.findViewById(R.id.dist);
Planet p = planetList.get(position);
tv.setText(p.getName());
distView.setText("" + p.getDistance());

Running the example we have:

How to use custom layout in Android ListView

Android ListView with custom layout using image (ImageView)

Another interesting example is using an image for each row. To keep things simple and clear we supposed that the image is always the same but we could change it as the planet changes. To do so we have simply to modify our row layout to include an ImageView component that will be updated in the getView. So the layout will be:

<xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content">

  <ImageView android:id="@+id/img" 
      android:layout_width="wrap_content" 
      android:layout_height="match_parent" 
      android:layout_gravity="left" 
      android:paddingLeft="0dip" 
      android:src="@drawable/planet"/>

   <TextView android:id="@+id/name" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:layout_alignTop="@+id/img" 
      android:layout_toRightOf="@+id/img" 
      android:textStyle="bold" />

   <TextView android:id="@+id/dist" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:layout_below="@id/name" 
      android:layout_gravity="center" 
      android:layout_marginTop="2dip" 
      android:layout_toRightOf="@id/img" 
      android:gravity="right" 
      android:textSize="8dp" 
      android:textStyle="italic" />

</RelativeLayout>

In this case, we have to modify the constructor and the layout inflater in this way:

public PlanetAdapter(List<Planet> planetList, Context ctx) {
  super(ctx, R.layout.img_row_layout, planetList);
  this.planetList = planetList;
  this.context = ctx;
}
...

public View getView(int position, View convertView, ViewGroup parent) {
  View v = convertView;
  // First let's verify the convertView is not null
  if (convertView == null) {
   // This a new view we inflate the new layout
   LayoutInflater inflater = (LayoutInflater) 
      context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   v = inflater.inflate(R.layout.img_row_layout, null);
  }
  ...
  return v;
}

Running the example above we have:

Android ListView with custom layout with Image

How to use different images with ListView

We want that each row in the Android ListView looks like:

android_listview_adapter_image

 

The first thing we have to do is to modify the layout and use a RelativeLayout instead of a simple LinearLayout. Our layout looks like:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content">

  <ImageView android:id="@+id/img" 
      android:layout_width="wrap_content" 
      android:layout_height="match_parent" 
      android:layout_alignParentLeft="true"/>

 <TextView android:id="@+id/name" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignTop="@+id/img" 
     android:layout_toRightOf="@+id/img" 
     android:textStyle="bold" />

  <TextView android:id="@+id/dist" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_below="@id/name" 
     android:layout_gravity="center" 
     android:layout_marginTop="2dip" 
     android:layout_toRightOf="@id/img" 
     android:gravity="right" 
     android:textSize="8dp" 
     android:textStyle="italic" />

</RelativeLayout>

The layout shown above is quite trivial and we don’t need to explain it. Then we have to modify the custom adapter in order to handle the images and the holder class. The holder class becomes:

private static class PlanetHolder {
  public TextView planetNameView;
  public TextView distView;
  public ImageView img;
}

and the adapter becomes:

public View getView(int position, View convertView, ViewGroup parent) {
  View v = convertView;
  PlanetHolder holder = new PlanetHolder();

  // First let's verify the convertView is not null
  if (convertView == null) {
   // This a new view we inflate the new layout
   LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   v = inflater.inflate(R.layout.img_row_layout, null);
   // Now we can fill the layout with the right values
   TextView tv = (TextView) v.findViewById(R.id.name);
   TextView distView = (TextView) v.findViewById(R.id.dist);
   ImageView img = (ImageView) v.findViewById(R.id.img);

   holder.planetNameView = tv;
   holder.distView = distView;
   holder.img = img;

   v.setTag(holder);
 }
 else
   holder = (PlanetHolder) v.getTag();

 System.out.println("Position ["+position+"]");
 Planet p = planetList.get(position);
 holder.planetNameView.setText(p.getName());
 holder.distView.setText("" + p.getDistance());

 holder.img.setImageResource(p.getIdImg());

 return v;
}

In the Android list view custom adapter using this way for each row in the ListView we set not only the planet name in the TextView but the ImageView also, with the image relative to the planet. If we run the app we have something like it:

Android Listview custom adapter with imageview

 

Well, I didn’t use the right images for the planets it is just a way to show you how to use images!!!. You have to resize and make the images better in your app!

Now if you click on an item you get a ClassCastException. Why? Well, you try to cast, inside the onItemClick method, a RelativeLayout to a TextView and it is impossible.

Handle user item click in Android ListView

If you want to handle user click on each item you have to modify the code inside the method lv.setOnItemClickListener. First of all we have to find the item position chosen by the user and we could do it or using the position parameter in  public void onItemClick(AdapterView<?> parentAdapter, View view, int position,  long id) or we could ask to the SO to find the right position in this way:
int pos = lv.getPositionForView(view);

In this way we know the position of the item clicked by the user. Once we know it, it is really simple to extract the information, because having the reference to the array of items we simply do:

Planet planet =  aAdpt.getItem(pos);

So when user clicks on an item inside the ListView we have:

Android Listview custom adapter imageview user click

Android ListView with Checkbox custom adapter

This tutorial describes how to use Android ListView with checkbox. This is an important component and it is widely used and has many applications in different fields. In this post, i want to show how to use ListView with Checkbox so that user can select items and give you some tricks to handle some problems.
We want to implement an Android ListView with checkbox where each item has a checkbox like the figures shown below:

Android ListView with Checkbox custom layout

Android Listview checkbox item checked custom layout

At the same time, we want to handle user click so that we keep track of items checked and give access to the item menu.

Implement a layout of Android Listview with checkbox custom layout

The first thing we have to do is implementing the layout like the one shown below:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <CheckBox android:id="@+id/chk" 
     android:layout_alignParentLeft="true" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content"/>

  <TextView android:id="@+id/name" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_weight="1" 
     android:textStyle="bold" 
     android:layout_toRightOf="@id/chk"/>

 <TextView android:id="@+id/dist" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:textSize="8dp" 
    android:textStyle="italic" 
    android:layout_below="@id/name" 
    android:layout_toRightOf="@id/chk"/>

</RelativeLayout>

How to create the ListView adapter and user interaction

Once we have our layout, we use it in our Adapter. As discussed in other post we have to modify our getView method so that we can handle the checkbox. The code is very similar to others used in other post, we simply need to get the reference to the Checkbox component and handle it.

CheckBox chk = (CheckBox) v.findViewById(R.id.chk);
....
holder.chk = chk;
chk.setOnCheckedChangeListener( (MainActivity) context);

In the last line, we simply define the listener invoked when a user clicks on the Checkbox. Java purists can discuss if it is better to implement the listener directly inside the adapter or somewhere else (MainActivity in our case). Well in my vision the adapter should be used just for filling the ListView, so that MainActivityclass is my opinion should the one that handles user touch interactions.

The MainActivity becomes:

public class MainActivity extends Activity implements android.widget.CompoundButton.OnCheckedChangeListener {
  .....
  @Override
  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
   int pos = lv.getPositionForView(buttonView);
   System.out.println("Pos ["+pos+"]");
   if (pos != ListView.INVALID_POSITION) {
     Planet p = planetsList.get(pos);
     p.setChecked(isChecked);
   }
  }
}

The onCheckedChanged method is invoked when a user clicks on the checkbox. In this method, we handle the event, finding first the position or in other words the item position inside the ListView. Then we verify that this position is valid, comparing it against ListView.INVALID_POSITION and if everything is ok we set the boolean attribute of the Planet class the the isChe parameter value.

Running the app you have this result:

Android Listview with checkbox item

Analysis of some “WIRED” behaviors

If you “play” with the app you could notice some strange behaviors. It crashes!!!…Yes right.

It happens that in some circumstances it suddenly crashes as you check the checkbox. Why?! Well this is a mystery in my opinion. If you read the log you get a NullPointerException in getPositionView. It could be because reuse the View in the ListView to optimize the resources. Anyway, investigating the problem it seems to me that the check listener is attached to an invalid checkbox that i suppose it is caused by the View re-use. The solution (but i don’t know if it is correct) is the “refresh” the listener reference everytime we get a item view reference. So in our adapter we have to modify a bit the code:

//chk.setOnCheckedChangeListener( (MainActivity) context); 
// We have to remove it it causes NPE
...
holder.chk.setOnCheckedChangeListener( (MainActivity) context);

Another “strange” thing, maybe you already noticed, it is the fact we can’t select the item anymore so if you long press on the item the menu doesn’t appear anymore. There’s a solution explained in this link, this behavior is caused by components that “locks” the focus. If you read the solution you can find that it is enough blocking focus on descendants, like that:

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

</RelativeLayout>

Using this trick if we run the app we have:

android listview with checkbox issue

 

Android ListView custom adapter Optimization

By now for each new row, the system inflates the new layout and then find the view to update. This process is very expensive and consumes a lot of resources, so we could find something smarter to avoid to look again and again at the same components. To optimize this view look up we can use a common pattern called View Holder pattern. This pattern is very simple and implies using a static inner class that holds the reference to the view inside the layout. So if we have a new row and the view is null we will inflate the new layout and use the View Holder pattern to keep track of the components we inflated, otherwise we don’t need anymore to look for them because we store the references in the static class. The static holder class is:

private static class PlanetHolder {
  public TextView planetNameView;
  public TextView distView;
}

Now the problem is: How we can bind the static class to the ListView row. Very Simple. For each view  (remember getView method) we simply use a setTag method and we store our static class. In this way we can handle the ListView more efficiently. We need to modify the getView method in this way:

public View getView(int position, View convertView, ViewGroup parent) {
  View v = convertView;
  PlanetHolder holder = new PlanetHolder();
  // First let's verify the convertView is not null
  if (convertView == null) {
   // This a new view we inflate the new layout
   LayoutInflater inflater = (LayoutInflater) 
      context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   v = inflater.inflate(R.layout.img_row_layout, null);
   // Now we can fill the layout with the right values
   TextView tv = (TextView) v.findViewById(R.id.name);
   TextView distView = (TextView) v.findViewById(R.id.dist);

   holder.planetNameView = tv;
   holder.distView = distView;

   v.setTag(holder);
  }
  else
    holder = (PlanetHolder) v.getTag();

  Planet p = planetList.get(position);
  holder.planetNameView.setText(p.getName());
  holder.distView.setText("" + p.getDistance());

  return v;
}

 

24 COMMENTS

  1. I have multiple layout in my main xml so what is the solution regarding this?? Here is only one layout so the list is visible with " LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    v = inflater.inflate(R.layout.img_row_layout, null);"

    But what is the solution for the multiple layouts in the same xml

    Please help me

  2. Thank you very very much!! It's been hours that I search for this kind of easy understanding sample. Now I understand what I do instead of just try copy paste code!

  3. In the optimised getView method, is the PlanetHolder holder object being unnnecessarily created

    when convertView is not null.

    When convertView != null, the holder object is being loaded from the getTag method. I think the holder variable should be created in the "if (convertView == null) {" block

    (I wouldnt normally mention a small thing like this but it is an optimisation code)

  4. I don't understand how to you use PlanetAdapter class instead of arrayAdapter. This way everything is just the same as earlier. I suppose it is needed to change the row

    aAdpt = new ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1, planetsList);

    To something that call for Planet adapter constructor but I didn't manage to do that the right way. Can someone explain?

  5. i search so many times, but not getting relevant information about my problem.
    my question is list view have more than 1 row. each row contain 1 image view 1 text view. when user click on row. image should be changed..!

    i need your valuable guide.
    Thanks.

  6. i search so many times, but not getting relevant information about my problem.
    my question is list view have more than 1 row. each row contain 1 image view 1 text view. when user click on row. image should be changed..!

    i need your valuable guide.
    Thanks.

LEAVE A REPLY

Please enter your comment!
Please enter your name here