Saturday, November 21, 2009

Android: Custom List Item with nested clickable Button

This tutorial will show you how to add a button (or any other clickable item) to a customized list view item.



In the screenshot above you can see a ListView with custom built items. Each list item consists of
  • TextView for the title
  • TextView for the content
  • RelativeLayout (clickable) consisting of
    • TextView: "For more information click here"
    • ImageView for the arrow icon
We start this tutorial at a point where you should already know how to create your own customized ListViews and Adapters. If you have no clue how to do so please let me know in the comments. If there's a demand I will eventually compile another tutorial about creating custom list items and list adapters in the future.
1) Build custom adapter that extends BaseAdapter and implement all the required inherited abstract methods from the parent class:

2) Implement View getView(int position, View convertView, ViewGroup parent):
This is where you create and manipulate the view that is used for a list item. I highly recommend that you take advantage of a view holder as it is described in this API example to improve speed and efficiency.

The crucial part is now that you, after you have inflated the list item's main layout

you also need to get hold of the sub-layout (bottom part). That's the part you want to be clickable.

3) Create an onClickListener for the bottom layout and associate the data (in this case a web URL) with the current view:

While for a OnItemClickListener, which is associated to each item of a ListView, we always know which item of the list has been selected through the int position paremeter. We don't have this information for our OnClickListener that is used in the bottom layout. So how do we get the neccessary information that is associated to the current list item?

The trick is to use View.setTag() and View.getTag(). The Android documentation states: “Sets the tag associated with this view. A tag can be used to mark a view in its hierarchy and does not have to be unique within the hierarchy. Tags can also be used to store data within a view without resorting to another data structure.”

In this example we use setTag() to store an URL with each sub-layout and getTag() in the onClickListener to extract that URL whenever a sub-layout was clicked. Then we create an Intent for our WebView, pass the URL to it and start a new Activity. And VoilĂ ! You should have a web browser now displaying the website associated to the item you clicked before.

However, there's one little detail that keeps annoying me. Even though I now can click/tap the bar at the bottom via the touch screen interface I'm not able to select it via the trackball. It always selects the whole list item (see screenshot below) and from there goes directly to the next list item ignoring the bottom bar, even though I set .setFocusable(true) and setClickable(true) for the bottom bar's RelativeLayout in getView().



If anyone knows a solution to this please let me know in the comments or respond to my question asked on stackoverflow.

12 comments:

snigdha

Hi Stefan
it's a very helpful example indeed but could you please elaborate as i am stuck with on click of a button in a list.
thanks

Stefan Klumpp

Hey snigdha. Sure. Just let me know what your problem is and I'll try to help.

Mike

Stefan

Thanks for the tutorial. One question, how would you go about removing an item from the list when you click a button in that item? I'm currently using a custom cursor adapter and in my onclicklistener I remove the item form the database and then do a cursor requery, but all the items in my list disappear. If I go away and come back it correctly shows the remaining items. It's driving me crazy. Do you have any ideas? Thanks again.

Stefan Klumpp

Hey Mike,

Sorry, I've not worked with cursors/databases on Android, yet. So I'm not really able to help you at the moment. I'd suggest you ask your problem on http://stackoverflow.com/ - there you should usually get a response very quickly.

level32

@Mike

extend ArrayAdapter instead of BaseAdapter.

You can simply call remove(item) on the adapter.

Don't forget to call notifyDataSetChanged() on the adapter as well. This will update the ui.

ajree

Hi Stefan,

Thanks for this example - it saved me many hours.

I tried to optimize it a bit more and it seems to be working. Basic idea is to have only one View.OnClickListener per clickable element in all rows. So instead of calling new View.OnClickListener() on every clickable in every row, I put these listeners as members of the adapter class. Since they operate on tags, they know which widget triggered them.

public class MyAdapter extends BaseAdapter {

private View.OnClickListener mMoreInfoClickListener = new View.OnClickListener() {
//(...)
};

@Override
public View getView(int position, View convertView, ViewGroup parent) {
//(...)
holder.layout_bottom.setOnClickListener(mMoreInfoClickListener);
}


I'm pretty new to Java and Android, and I'm not sure if this is valid approach, but as I said it seems to be working nicely.

Stefan Klumpp

Hey ajiree,

Thanks for sharing your improvement. Using just one Listener definitely makes sense.

anais

Hi,

Thx for this tutorial.

But, I have some questions ??

When I click on the button, the OnClick in the Adapter works, but where I do the link with my Activity where is my List?

I Have a list with one ImageView and TextView. When user click on textview , it open another Activity. When user click on Button, I would like to open a dialog.

Anonymous

Thanks Stefan. This helped.

Punit

chrisonline

Hi Stefan!

I've tried your solution and its great, but i have a problem.

After adding the .isClickable to the Layout my button is clickable but not the whole listview anymore.

I need click on the ListView (expands more fields) and click on the button inside the ListView.

Like the Gmail app.

Do you have a solution?

Anonymous

Hi Stefan,

thank you for this helpful tutorial .
I want to make my list look like yours, I would like alternate the backround color of raws . would you please help me and indicate wich method can i use . or a source code if you have .
thank you

Stefan Klumpp

Just use a simple "if (rowNumber % 2) { set color 1 } else { set color 2}

  © Blogger template 'Minimalist F' by Ourblogtemplates.com 2008

Back to TOP