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.



I've just launched a new side project of mine:

Bugfender - A modern remote logger tailor-made for mobile development

It's currently free and you can sign up here: https://app.bugfender.com/signup

Any kind of feedback is more than appreciated :-)

29 comments:

  1. 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

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

    ReplyDelete
  3. 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.

    ReplyDelete
  4. 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.

    ReplyDelete
  5. @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.

    ReplyDelete
  6. 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.

    ReplyDelete
  7. Hey ajiree,

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

    ReplyDelete
  8. 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.

    ReplyDelete
  9. Thanks Stefan. This helped.

    Punit

    ReplyDelete
  10. 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?

    ReplyDelete
  11. 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

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

    ReplyDelete
  13. @chrisonline

    try onitemclick of listview
    your whole listitem is then clickable

    ReplyDelete
  14. @chrisonline

    try onitemclick of listview

    like this

    yourList..setOnItemClickListener(new AdapterView.OnItemClickListener() {
    public void onItemClick(AdapterView parent, View view, int position, long id) {
    //to do when item is clicked
    }
    };
    your whole listitem is then clickable

    ReplyDelete
  15. Thanks for helping clear this up! Your tutorial was great.

    The API Example link in the article appears to be broken, btw. The List14 example has moved to
    http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html

    ReplyDelete
  16. Answer to focus from stack overflow:

    Probably you've found how to do it, but you can call ListView.setItemsCanFocus(true), and your buttons will catch focus...

    ReplyDelete
  17. Hello SteFan,

    I m confused on one things, i have a situation in which i have a list view with imageview (Which i want to make clickable), so if user clicks on any item, it will do some process and if user click on imageview, then a google map will open which will show position marked using latitude and longitude. i have a array of latitude and longitude. Now how i need to settag, so i can get a particular set of latitude and longitude for a google map, when user click on image view ?

    ReplyDelete
  18. Hi,
    I am pretty new to android and hence dont know much about customized list views and adapters. I would be thankful if you could provide me with the complete code and a bit of explanation regarding it.
    Thnx..

    ReplyDelete
  19. Did you manage to solve your focus problem?

    Thanks.

    ReplyDelete
  20. Posted a follow-up on SO on how to get the adapter position for a clicked View in a ListView row:
    http://stackoverflow.com/questions/1709166/android-listview-elements-with-multiple-clickable-buttons/5320540#5320540

    ReplyDelete
  21. Thank you for the great blog in here...but could you please help me on the same lines...I have posted my question in SO..kindly have a look

    http://stackoverflow.com/questions/6535558/call-notifyondatasetchanged-from-another-adapter

    ReplyDelete
  22. hi there, i also newbie here..
    i really hope that you can give more code for this example..please

    i had my hard time trying to make this works..

    ReplyDelete
  23. I would be grateful if you could do a write-up for custom ListViews!

    Thanks

    ReplyDelete
  24. Hello thnx for tutorial
    but i have a little problem which is related to ListView.

    I have a list view to which i bind a user information list having 2 TextViews and 1 button.
    On click of the button i want to get whole object of that user and pass it to next page.
    I have written everything i could but my button click is not working.
    Can u please help me in it?

    ReplyDelete
  25. si eres de barcelona, debes hablar español no? en todo caso te queria preguntar si sabes acerca de personalizar tableLayouts en Android, porque tengo una duda hacerca de incluir ListViews dentro de una celda en la tabla, no se como se podria o que tanta versatilidad tiene, asimismo con la lista dentro de un GridView, estoy tratando de pensar como hacerlo y honestamente no se me ocurre nada, saludos y que buen trabajo

    ReplyDelete
  26. Great post.!

    but I wonder why did you use TextView and ImageView inside RelativeLayout instead of using single TextView with drawable at right property.

    However the post was very helpful for me.

    ReplyDelete
  27. Hi stefan,

    I am beginner in android. I created a listview with elements which i fetched from online. I want to add a button once i select a listelement. For eg: if i select the third row, i want to add a button on that row. Can you please help me with the code. I have my code here:


    package com.pxr.tutorial.xmltest;

    import java.util.ArrayList;
    import java.util.HashMap;

    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.NodeList;

    import android.app.ListActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.ListAdapter;
    import android.widget.ListView;
    import android.widget.SimpleAdapter;
    import android.widget.Toast;

    public class Main extends ListActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.listplaceholder);

    ArrayList> mylist = new ArrayList>();


    String xml = XMLfunctions.getXML();
    Document doc = XMLfunctions.XMLfromString(xml);

    int numResults = XMLfunctions.numResults(doc);

    if((numResults <= 0)){
    Toast.makeText(Main.this, "Geen resultaten gevonden", Toast.LENGTH_LONG).show();
    finish();
    }

    NodeList nodes = doc.getElementsByTagName("product");

    for (int i = 0; i < nodes.getLength(); i++) {
    HashMap map = new HashMap();

    Element e = (Element)nodes.item(i);
    map.put("id", XMLfunctions.getValue(e, "productId"));
    map.put("name", "Name:" + XMLfunctions.getValue(e, "name"));
    map.put("id", "ProductId: " + XMLfunctions.getValue(e, "productId"));
    map.put("type", "Type: " + XMLfunctions.getValue(e, "type"));
    map.put("rprice", "RegularPrice: " + XMLfunctions.getValue(e, "regularPrice"));
    map.put("Score", "Sale Price: " + XMLfunctions.getValue(e, "salePrice"));
    mylist.add(map);
    }

    ListAdapter adapter = new SimpleAdapter(this, mylist , R.layout.main,
    new String[] { "name", "id","type","rprice","Score"},
    new int[] { R.id.item_title, R.id.item_subtitle,R.id.item_subtitle1,R.id.item_subtitle8,R.id.item_subtitle9 });

    setListAdapter(adapter);

    final ListView lv = getListView();
    lv.setTextFilterEnabled(true);
    //lv.setOnItemClickListener(new OnItemClickListener() {
    //public void onItemClick(AdapterView parent, View view, int position, long id) {
    // @SuppressWarnings("unchecked")
    // HashMap o = (HashMap) lv.getItemAtPosition(position);
    // Toast.makeText(Main.this, "ID '" + o.get("id") + "' was clicked.", Toast.LENGTH_LONG).show();
    //}
    //});
    lv.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
    // TODO Auto-generated method stub

    Button button1=new Button(this);
    button1.setText("Click");
    this.setContentview(button1);

    }
    });
    }
    }





    Thanks in advance,
    Gowtham.

    ReplyDelete