November 26, 2014

Fancy ListViews, Part One

The classic Android ListView is a plain list of text — solid but uninspiring. This is the first in a series of posts where we will see how to create a ListView with a bit more pizazz. Today, in particular, we will see two techniques for creating a ListView whose rows contain icons, in addition to text.

If you want rows to have a different look than the stock rows, one way to accomplish this is to supply your own layout XML to be used for each row, telling Android to use your layout rather than one of the built-in ones. This gives you complete control over what goes in the row and how it is laid out.

For example, suppose you want a ListView whose entries are made up of an icon, followed by some text. You could construct a layout for the row that looks like this:

[sourcecode language=’xml’]

android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
android:id="@+id/icon"
android:layout_width="22px"
android:paddingLeft="2px"
android:paddingRight="2px"
android:paddingTop="2px"
android:layout_height="wrap_content"
android:src="@drawable/ok"
/>
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="44sp"
/>
[/sourcecode]

This layout uses a LinearLayout to set up a row, with the icon on the left and the text (in a nice big font) on the right.

By default, though, Android has no idea that you want to use this layout with your ListView. To make the connection, you need to supply your Adapter with the resource ID of your custom layout:

[sourcecode language=’java’]
public class StaticDemo extends ListActivity {
TextView selection;
String[] items={“lorem”, “ipsum”, “dolor”, “sit”, “amet”,
“consectetuer”, “adipiscing”, “elit”, “morbi”, “vel”,
“ligula”, “vitae”, “arcu”, “aliquet”, “mollis”,
“etiam”, “vel”, “erat”, “placerat”, “ante”,
“porttitor”, “sodales”, “pellentesque”, “augue”,
“purus”};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new ArrayAdapter(this,
R.layout.row, R.id.label,
items));
selection=(TextView)findViewById(R.id.selection);
}

public void onListItemClick(ListView parent, View v,int position, long id) {
selection.setText(items[position]);
}
}
[/sourcecode]

This example, derived from one of the ones in my book, displays a list of random words, and puts the currently-selected word in a TextView.

The key in this example is that you have told ArrayAdapter that you want to use your custom layout (R.layout.row) and that the TextView where the word should go is known as R.id.label within that custom layout.

The result is a ListView with icons down the left side. In particular, all the icons are the same.

This technique — supplying an alternate layout to use for rows — handles simple cases very nicely. However, it falls down when you have more complicated scenarios for your rows, such as:

  • Not every row uses the same layout (e.g., some have one line of text, others have two)
  • You need to configure the widgets in the rows (e.g., different icons for different cases)

In those cases, the better option is to create your own subclass of your desired Adapter, override getView(), and construct your rows yourself. The getView() method is responsible for returning a View, representing the row for the supplied position in the adapter data.

For example, let’s rework the above code to use getView(), so we can have different icons for different rows — in this case, one icon for short words and one for long words:

[sourcecode language=’java’]
public class DynamicDemo extends ListActivity {
TextView selection;
String[] items={“lorem”, “ipsum”, “dolor”, “sit”, “amet”,
“consectetuer”, “adipiscing”, “elit”, “morbi”, “vel”,
“ligula”, “vitae”, “arcu”, “aliquet”, “mollis”,
“etiam”, “vel”, “erat”, “placerat”, “ante”,
“porttitor”, “sodales”, “pellentesque”, “augue”,
“purus”};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new IconicAdapter(this));
selection=(TextView)findViewById(R.id.selection);
}

public void onListItemClick(ListView parent, View v, int position, long id) {
selection.setText(items[position]);
}

class IconicAdapter extends ArrayAdapter {
Activity context;

IconicAdapter(Activity context) {
super(context, R.layout.row, items);

this.context=context;
}

public View getView(int position, View convertView, ViewGroup parent) {
ViewInflate inflater=context.getViewInflate();
View row=inflater.inflate(R.layout.row, null, null);
TextView label=(TextView)row.findViewById(R.id.label);

label.setText(items[position]);

if (items[position].length()>4) {
ImageView icon=(ImageView)row.findViewById(R.id.icon);

icon.setImageResource(R.drawable.delete);
}

return(row);
}
}
}
[/sourcecode]

In our getView() implementation, we first get our hands on a ViewInflate object, as described in the previous Building ‘Droids post, so we can use it to “inflate” our row layout XML and give us a View representing that row.

Then, we tailor that View to match our needs:

  • We fill in the text label into our label widget, using the word at the supplied position
  • We see if the word is longer than four characters and, if so, we find our ImageView icon widget and replace the stock resource with a different one

Now, we have a ListView with different icons based upon context of that specific entry in the list. Obviously, this was a fairly contrived example, but you can see where this technique could be used to customize rows based on any sort of criteria, such as other columns in a returned Cursor.

Next time, we’ll take a closer look at the convertView parameter to our getView() method and see how we can make use of it to more efficiently render our list.



  • http://www.curious-creature.org Romain Guy

    You really should use not introduce a tutorial without talking about convertView. I know your article will deal with this in the next installment, but developers might not find it. ListView examples should be carefully crafted to only give the best example :)

    You can improve performance further by using the “ViewHolder pattern.” Simply do the following to avoid the calls to findViewById. A future SDK will contain examples of how to efficiently implement a ListView Adapter, and it presents how to use the ViewHolder pattern along with the convertView.

    class ViewHolder {
    ImageView image;
    TextView text;
    }

    public View getView(…) {
    ViewHolder holder;
    if (convertView == null) {
    convertView = inflate()…
    holder = new ViewHolder();
    holder.image = (ImageView) convertView.findViewById(R.id.image);
    holder.text = // find text view
    convertView.setTag(holder);
    } else {
    holder = (ViewHolder) convertView.getTag();
    }
    holder.image.setImageDrawable(…);
    holder.text = “foo”;
    }

  • http://commonsware.com Mark Murphy

    @Mr. Guy:

    I didn’t include convertView because it gets complicated, and I’m not a huge fan of premature optimization. This particular set of blog posts is aimed at non-experts in Android, and I’m a big believer in walking before running.

    That being said, you’re absolutely correct that convertView should be used where possible, and it’s the focus of the second installment (http://androidguys.com/2008/07/17/fancy-listviews-part-two/).

    With respect to ViewHolder, thanks for reminding me! I keep forgetting you put the tag hook into View for situations like this. I’ll probably work that into a future entry in this series.

    Thanks for your input! Always good to hear from a real live Googler! ;-)

  • Josh Elser

    I’m a little confused, and I’m not sure if I’m just missing something, but in this example, as well with the latter parts of this series,

    In your 2nd code block, in the StaticDemo class on this page, line 17, you use R.id.selection. But you don’t have this in the first xml code block. Looking at part 4 of the Fancy ListVIew, you also have the same line.

    I don’t ever see an id with the name ‘selection’.

    Thanks.

  • http://commonsware.com/Android/ Mark Murphy

    The XML in the first code listing is for a row (res/layout/row.xml), not the overall activity’s layout (res/layout/main.xml). The overall activity’s layout is not shown, but consists of a ListView and TextView, the latter being named selection.

    In StaticDemo, then, you see the row layout XML referenced in the ArrayAdapter constructor as R.layout.row.

    I apologize for the confusion — some of these posts skip files that aren’t essential to the points I’m trying to get across.

    Barring unforeseen problems, you’ll be able to download the full source code to all these projects sometime this weekend from http://commonsware.com/Android/ — they’ll be packaged with version 1.1 of my book, along with a new chapter that expands upon the Fancy ListViews series of posts. But, you don’t have to buy the book to get the source code, as that’s a free download off the Web site at the aforementioned page.

    If you have additional questions, let me know!

  • kuldipsinnh

    hai friend is it possible to use
    setContentView(R.layout.main); when we extend ListActivity
    this code when i run giveerror to me.
    pls help

  • Vincent Bodinier

    I am using the Android SDK 1.1 r1 and this code doesn’t work at all (an error appears).
    However, note that if I replace “setListAdapter(new ArrayAdapter(this,R.layout.row, R.id.label,items));”
    by
    “setListAdapter(new ArrayAdapter (this,android.R.layout.simple_list_item_1,items));”,
    it works correctly but obviously I get the Android defined format of the row for the ListView (not what I want).
    So, it seems that the setListAdapter method doesn’t accept your own Layout. Quite annoying !
    Could you tell me if I am right or if it exits a solution to define your own layout for the adapter to use ?
    Thanks.

  • elham

    How to add multi-choice to the customized row ?

  • http://www.androiddev.pl glock45

    You are doing it wrong :)

  • http://www.androiddev.pl glock45

    In this simple case i think it’s also better to use: TextView.setCompoundDrawablesWithIntrinsicBounds(Drawable,Drawable,Drawable,Drawable)

    About optimization and missing them at tutorials…… coders reads this tutorial -> build application with ListView -> app goes to market -> users says that Android is not smooth :(

  • http://commonsware.com/Android/ Mark Murphy

    @glock45:

    This is the first in a series of blog posts on the topic. I encourage you to read them all. Moreover, these are nearly a year old — I only refer back to them because there is nothing else out there that is better that I have found. If you feel you can write better material on this subject, please do so.

    With respect to TextView.setCompoundDrawablesWithIntrinsicBounds(), I disagree:

    1. That technique is not general (i.e., there are many row layouts that cannot be achieved using that technique)

    2. That technique does not support padding, as this example uses, without modifying the image

    3. That technique has not been demonstrated to be faster or more efficient ( and I am assuming you lack such proof, otherwise you might have linked to it)

    You are welcome to use TextView.setCompoundDrawablesWithIntrinsicBounds() in whatever cases you feel are appropriate. I have used it myself, though usually from layout XML. For the purposes of this specific example in this specific blog post, I do not feel its use is a good idea.

  • Mark Murphy

    A more up-to-date copy of the material from this blog post series can be found in this free excerpt from The Busy Coder’s Guide to Android Development.

  • John

    Dear Mark,

    Thank you for this nice tutorial, I’m trying to apply your code now, with some changes.

    Right after the setListAdapter(new IconicAdapter(this)); in your onCreate method, I’ve added: getListView().setTextFilterEnabled(true);

    While the original example (which does not use the IconicAdapter, but instead a simple String[]) did work, the IconicAdapter does not work together with the text filter (I get that it always displays the first item only, no matter what I’m typing).

    I guess I should override some methods somewhere, but I’m a bit stuck here, could you give me a hint?

    Thanks,
    John.

  • http://commonsware.com/Android/ Mark Murphy

    I have not played much with setTextFilterEnabled(). If I had to guess, perhaps ArrayAdapter knows how to handle getFilter() to support the Filterable interface, and IconicAdapter does not.

    Grab the 1.5-compatible IconicAdapter code out of the sample code for one of my books:

    http://commonsware.com/Android/

    (scroll down, click on Source Code, then look in FancyLists for up-to-date editions of the code shown in this series of blog posts)

    Perhaps there’s something in the newer material that will help.

    If you have additional questions about this, please head over to the cw-android Google Group and ask there:

    http://groups.google.com/group/cw-android

    That’s where I provide assistance with the book samples.

  • John

    yeah! I’ve got it working now.
    I changed the class into
    “public class IconicAdapter extends BaseAdapter implements Filterable”
    and used the same implementation for the getView() method.

    Furthermore I extended the Filter class and made the my IconicAdapter class return this filter in the overridden getFilter method.

    Thanks for your help,
    John.

    • Abhinav

      Hi john,
      Can you please guide me how you did the extending filter class part … m not sure what will go in performFiltering and publishResults functions .. also is there any other function to be overridden …

      kindly help
      thanks in advance

      Abhinav

  • http://twitter.com/Dayn77 Dayn

    Hi

    I want to know if its possible to make a listview (text+icon) on a class extends Activity and note on a class extends ListActivity.

    So I make a little code with class extends.

    on the onCreate:

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

    ArrayList mStrings = new ArrayList();

    ArrayAdapter mAdapter = new ArrayAdapter (this,android.R.layout.simple_list_item_1, mStrings);

    mAdapter.add("itemAdd ");
    Log.v(TAG, mAdapter.getItem(0));
    mAdapter.add("itemAdd1 ");
    mAdapter.add("itemAdd2 ");
    mAdapter.add("itemAdd3");
    mAdapter.add("itemAdd4");
    mAdapter.add("itemAdd5");
    mAdapter.add("itemAdd6");
    mAdapter.add("itemAdd7");
    mAdapter.add("itemAdd8");
    mAdapter.add("itemAdd9");

    list.setAdapter(mAdapter);

    so I find on the ApiDemos android the List14.java class using EfficientAdapter class.

    http://developer.android.com/intl/fr/guide/samples/ApiDemos/src/com/example/android/apis/view/List14.html

    My question is, Can I just use class extends Activity to use the same things like List14.java, with getView(int position, View convertView, ViewGroup parent) ????

  • unamed

    when people don't show the whole content of the project's file (like the row.xml file) makes the post so silly for newbies like me to make things work…

  • Fryday

    Great Tutorial, very illuminating.

    Just as a FYI:
    The formatting on this site has completely destroyed some of the code samples, had to go to source to see them.

  • Bob

    Can anybody understand the source code??? looks crazy…. not helpful at all.

  • http://www.jfhostreview.com jfhostreview

    great tutorial and list views. informative.

  • KingLeigh

    This looks like a very informative series – unfortunately, the formatting makes the code and text almost impossible to follow.

    Any chance of this getting cleaned up?

    Thanks!

  • http://webmania.cc rrd

    Please check the source code because your < are interpreted as html start tags, so parts of the source code is missing.

  • Eugene

    I consider myself a seasoned programmer. I have been programming in Java and PHP for 13 years. I’m recently starting to do stuff in the mobile platform arena. I’m still quite new to Android, and the challenge of changing the text color of ListView objects is proving quite painful. I tried what you said above, and the app crashes without anything in the logcat. I have 2 requests: 1) can you please fix your site to properly handle the [sourcecode] tags and 2) puh-lease provide an updated example that works with android 2.2? All I want to do is change the text color of my ListView so I can see it against my background, and I literally have been looking for a workable solution for weeks–it’s pissing me off.

    Thanks
    Eugene

  • Keith

    The formatting is broke. Source-code is not appearing properly. Browsing with Ubuntu 10.04/Firefox 3.6.10…

  • Pingback: Very simple adapter rearrangement technic in Android : Android Community - For Application Development

  • Pingback: alkyloxy adipolysis beghard