Tuesday, September 11, 2012

Android: Creating Custom ListView for Beginners

After you get your hands on Android, you will find the most useful widget is the ListView. You may also feel that it's required on every activity. Android being Open-Source, it is the most flexible mobile platform out there. Everyone, wants to customize it according to their taste. You would hardly see an app that uses the default look and feel of the widgets. But with flexibility comes complexity.
You can judge the importance of the ListView widget by the fact that there was a complete session in Google I/O on ListViews. A ListView can take any shape from a colorful contact list in whatsapp to a text filled tweet in twitter.



Unlike other tutorials, I want this article to be more general and I would try my best so that you can create a listview according to your needs rather than just a custom listview developed in this article.

By default android renders only a single TextView in each of the ListView's row and binds string data through an adapter. But you can customize the ListView's row and place your own widget items in it and then bind data through your own custom adapter.

First create a simple layout and declare a ListView in it. You can also place other components in this layout if you want, like a TextView to give some heading or information.
main.xml
    <?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"

        android:padding="2dp"

        >       

        <ListView

            android:id="@+id/MessageList"

            android:layout_width="match_parent"

            android:layout_height="0dip"       

            android:layout_weight="1" >

        </ListView>

    </LinearLayout>

Now we will create the layout for listview row. This will be the layout in which you can customize your list row. This will be re-inflated every time a new entry is made to the listview. The widgets in this layout will depend on your requirement, like if you are developing a contact list then you will only need a ImageView and TextView or if you are developing a songs list then you might consider adding another TextView for the artist name apart from album icon and album name.

For this tutorial I made something like a message app which includes a ImageView and 4 TextViews in each list item. Here is the xml layout for the same.

list_item_message.xml
    <?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" >

        <LinearLayout

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:orientation="horizontal" >

            <ImageView

                android:id="@+id/icon"

                android:layout_width="wrap_content"

                android:layout_height="40dp" />

            <TextView

                android:id="@+id/From"

                android:layout_width="fill_parent"

                android:layout_height="wrap_content"

                android:layout_gravity="center"

                android:textAppearance="?android:attr/textAppearanceMedium"

                android:textColor="#003366" />

        </LinearLayout>

        <TextView

            android:id="@+id/subject"

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:textStyle="bold" />

        <TextView

            android:id="@+id/description"

            android:layout_width="fill_parent"

            android:layout_height="wrap_content" />

        <TextView

            android:id="@+id/time"

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:gravity="right" />

    </LinearLayout>

Now we will create a simple class that would help us in binding data and getting the reference to a particular component's value in each row.
        public class MessageDetails {
            int icon ;
            String from;
            String sub;
            String desc;
            String time;

            public String getName() {
                return from;
            }

            public void setName(String from) {
                this.from = from;
            }

            public String getSub() {
                return sub;
            }

            public void setSub(String sub) {
                this.sub = sub;
            }
           
            public int getIcon() {
                return icon;
            }

            public void setIcon(int icon) {
                this.icon = icon;
            }
           
            public String getTime() {
                return time;
            }

            public void setTime(String time) {
                this.time = time;
            }
           
            public String getDesc() {
                return desc;
            }

            public void setDesc(String desc) {
                this.desc = desc;
            }
        }
Next up is the brain of our ListView, yes our very own Custom Adapter.
            public class CustomAdapter extends BaseAdapter {

                private ArrayList<MessageDetails> _data;
                Context _c;
               
                CustomAdapter (ArrayList<MessageDetails> data, Context c){
                    _data = data;
                    _c = c;
                }
              
                public int getCount() {
                    // TODO Auto-generated method stub
                    return _data.size();
                }
               
                public Object getItem(int position) {
                    // TODO Auto-generated method stub
                    return _data.get(position);
                }
           
                public long getItemId(int position) {
                    // TODO Auto-generated method stub
                    return position;
                }
              
                public View getView(int position, View convertView, ViewGroup parent) {
                    // TODO Auto-generated method stub
                     View v = convertView;
                     if (v == null)
                     {
                        LayoutInflater vi = (LayoutInflater)_c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                        v = vi.inflate(R.layout.list_item_message, null);
                     }

                       ImageView image = (ImageView) v.findViewById(R.id.icon);
                       TextView fromView = (TextView)v.findViewById(R.id.From);
                       TextView subView = (TextView)v.findViewById(R.id.subject);
                       TextView descView = (TextView)v.findViewById(R.id.description);
                       TextView timeView = (TextView)v.findViewById(R.id.time);

                       MessageDetails msg = _data.get(position);
                       image.setImageResource(msg.icon);
                       fromView.setText(msg.from);
                       subView.setText("Subject: "+msg.sub);
                       descView.setText(msg.desc);
                       timeView.setText(msg.time);                             
                                   
                    return v;
            }
            }
Our custom adapter takes in a ArrayList of our MessageDetails class and a context as parameters. You can easily notice that all the work is done by the getView method which is called every time a list item is instantiated.
It returns a View corresponding to the data at the specified position. Here ViewGroup is the parentView i.e the ListView. We check if the convertView is not null which helps android to re-use the old View and reduce battery consumption.
I have used app's icon as the image for all my list item but you can use your own algorithm.

Now we are ready with the setup, we just need to fill our data into the ListView. So here's the onCreate method:
public class Messages extends Activity {
    ListView msgList;
    ArrayList<MessageDetails> details;
    AdapterView.AdapterContextMenuInfo info;
       
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // TODO Auto-generated method stub
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
           
            msgList = (ListView) findViewById(R.id.MessageList);
           
            details = new ArrayList<MessageDetails>();
               
            MessageDetails Detail;
            Detail = new MessageDetails();
            Detail.setIcon(R.drawable.ic_launcher);
            Detail.setName("Bob");
            Detail.setSub("Dinner");
            Detail.setDesc("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla auctor.");
            Detail.setTime("12/12/2012 12:12");
            details.add(Detail);
           
            Detail = new MessageDetails();
            Detail.setIcon(R.drawable.ic_launcher);
            Detail.setName("Rob");
            Detail.setSub("Party");
            Detail.setDesc("Dolor sit amet, consectetur adipiscing elit. Nulla auctor.");
            Detail.setTime("13/12/2012 10:12");
            details.add(Detail);
           
            Detail = new MessageDetails();
            Detail.setIcon(R.drawable.ic_launcher);
            Detail.setName("Mike");
            Detail.setSub("Mail");
            Detail.setDesc("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
            Detail.setTime("13/12/2012 02:12");
            details.add(Detail);       
           
            msgList.setAdapter(new CustomAdapter(details , this));
}
So, here we are with our very own custom listview, piece of cake naa?
Cool?

Now when you have your own ListView you may also want to add some functionality to it like a context menu and click listeners.

Context Menu:
First, register your ListView for context menu on onCreate.
registerForContextMenu(msgList);
and here is the onCreateContextMenu and onContextItemSelected.
    @Override
 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
            // TODO Auto-generated method stub
            super.onCreateContextMenu(menu, v, menuInfo);      
                    
                info = (AdapterContextMenuInfo) menuInfo;
 
           menu.setHeaderTitle(details.get(info.position).getName());      
                menu.add(Menu.NONE, v.getId(), 0, "Reply");
                menu.add(Menu.NONE, v.getId(), 0, "Reply All");
                menu.add(Menu.NONE, v.getId(), 0, "Forward");                  
        }
        
 @Override
 public boolean onContextItemSelected(MenuItem item) {
         if (item.getTitle() == "Reply") {
            //Do your working
                }
          else if (item.getTitle() == "Reply All") {
             //Do your working
                }
          else if (item.getTitle() == "Reply All") {
                //Do your working
            }
          else     {
                return false;
                }
        return true;
        }
Context Menu
List Item Click listener:
For having a item click listener on your lisview you have to add the following lines to each of your widget in the list_item_message.xml.
            android:focusable="false"
            android:focusableInTouchMode="false"

Here's the click listener:
     msgList.setOnItemClickListener(new OnItemClickListener() {
      public void onItemClick(AdapterView a, View v, int position, long id) {
                    
              String s =(String) ((TextView) v.findViewById(R.id.From)).getText();
              Toast.makeText(Messages.this, s, Toast.LENGTH_LONG).show();
                  }
          }); 

Item Click Listener

We retrieve the text in the 'From TextView ' by this line (String) ((TextView) v.findViewById(R.id.From)).getText(); which you can also retrieve through this code: details.get(position).getName().

Click Listener for a specific component:
You can also have click listener for a specific widget. For this tutorial, I have implemented it on the ImageView which made the most sense. Click listener for a widget has to be added on the getView method in our Custom Adapter. So here's is the modified getView method. Notice you have to declare the int position as final.
public class CustomAdapter extends BaseAdapter {
 
            private ArrayList<MessageDetails> _data;
            Context _c;
          
            CustomAdapter (ArrayList<MessageDetails> data, Context c){
                _data = data;
                _c = c;
            }

            @Override
            public int getCount() {
                // TODO Auto-generated method stub
                return _data.size();
            }

            @Override
            public Object getItem(int position) {
                // TODO Auto-generated method stub
                return _data.get(position);
            }

            @Override
            public long getItemId(int position) {
                // TODO Auto-generated method stub
                return position;
            }

           @Override
            public View getView(final int position, View convertView, ViewGroup parent) {
                // TODO Auto-generated method stub
                 View v = convertView;
                 if (v == null)
                 {
                    LayoutInflater vi = (LayoutInflater)_c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    v = vi.inflate(R.layout.list_item_message, null);
                 }
 
                   ImageView image = (ImageView) v.findViewById(R.id.icon);
                   TextView fromView = (TextView)v.findViewById(R.id.From);
                   TextView subView = (TextView)v.findViewById(R.id.subject);
                   TextView descView = (TextView)v.findViewById(R.id.description);
                   TextView timeView = (TextView)v.findViewById(R.id.time);
 
                   MessageDetails msg = _data.get(position);
                   image.setImageResource(msg.icon);
                   fromView.setText(msg.from);
                   subView.setText("Subject: "+msg.sub);
                   descView.setText(msg.desc);
                   timeView.setText(msg.time);                          
             
            @Override
            image.setOnClickListener(new OnClickListener() {              
                    public void onClick(View v) {
                    // TODO Auto-generated method stub
                    AlertDialog.Builder adb=new AlertDialog.Builder(Messages.this);
                    adb.setMessage("Add To Contacts?");
                    adb.setNegativeButton("Cancel", null);
                    final int selectedid = position;
                    final String itemname= (String) _data.get(position).getName();
 
                    adb.setPositiveButton("OK", new AlertDialog.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                                  
                               //Your working   
                            }});
                  
                    adb.show();  
                }                        
            });  
ImageView Click Listener

I hope I helped you in making your own custom ListView. Please Comment if you have any query and remember sharing is caring!

[UPDATE]
The thing you asked the most in comments - here's the link to download the whole Eclipse Project. Hope this will clear all your doubts and issues if faced any.

Don't forget to like ThePCWizard on Facebook for latest tech news, geeky discussions and best of tech trolls. Also subscribe to RSS feeds and YouTube channel for being updated with the latest tech tutorials.

45 comments:

  1. fantastic code helped me a lot thanks...........:)

    ReplyDelete
  2. very impressive keep it up and post more examples

    ReplyDelete
  3. Thank You Very Very Much!

    ReplyDelete
  4. thanks dude...i guess this will surely help me in my projest though i wud nid to change it to my needs....still thnx...kip up the spirit...

    ReplyDelete
  5. MessageDetails msg = _data.get(position);

    why it is askin me to cast here as in program u didnt cast it

    i am not getting list please help

    ReplyDelete
    Replies
    1. and what it is asking to cast to?? Go through the code again maybe you missed something!

      Delete
    2. Same for me, asking to cast to MessageDetails or declare msg as Object

      Delete
    3. There was a problem with syntax highlighting. The code is fixed now. All ArrayList declarations were missing <MessageDetails> . Check it now.
      Sorry for the inconvenience.

      Delete
  6. got it!!great tutorials thanks man

    ReplyDelete
  7. Hi,
    on the first screenshot (twitter app), there is a dropdown menu on the right of each item, do you know how to implement that?
    great tutorial by the way

    ReplyDelete
  8. Hi great tutorial, but could you show how change one textview with button click in one row? like in this problem http://stackoverflow.com/questions/14976458/android-listview-with-active-buttons-

    ReplyDelete
  9. Hi, I am starting to learn android dev, and this is a nice tutorial but the last addition to custom addapter is giving me many errors, maybe I place it wrongly?

    Thank you very much for this tutorial it shows many concepts

    descView.setText(msg.desc);
    timeView.setText(msg.time);
    //error from here
    @Override
    image.setOnClickListener(new OnClickListener() {
    public void onClick(View v) {
    // TODO Auto-generated method stub
    AlertDialog.Builder adb=new AlertDialog.Builder(Messages.this);
    adb.setMessage("Add To Contacts?");
    adb.setNegativeButton("Cancel", null);
    final int selectedid = position;
    final String itemname= (String) _data.get(position).getName();

    adb.setPositiveButton("OK", new AlertDialog.OnClickListener() {
    public void onClick(DialogInterface dialog, int which) {

    //Your working
    }});

    adb.show();
    }
    });

    return v;
    }

    ReplyDelete
  10. Replies
    1. Check download link in the last lines of the article. Sorry for not posting it earlier!

      Delete
  11. Nicely explained..

    ReplyDelete
  12. Very good demonstartion of Listview activity. good man great....example helpful for all developer.

    ReplyDelete
  13. Thanks for this fantastic help~~~. Finally my project can continue...cheers!! simply the best so far for listview

    ReplyDelete
  14. Thank u vry much ....... fantastic tutorial for list.....

    ReplyDelete
  15. Can i download your code?
    so sory i'm a dummy beginer, i found many error i can't understand... please let me download your project.. thanks for the great tutorial...

    ReplyDelete
    Replies
    1. Yes, check download link at bottom of the article. Sorry for not posting it earlier!

      Delete
  16. dude i tried ur code n it had almost only 1 or 2 bugs (dats great). But when i run it on my device (android 4.2) it doesnt display any content on the main page. Just a white space :(
    please help me out. I am working on a project n need help very soon. Thank. Nice Blog :)

    ReplyDelete
    Replies
    1. The tutorial is now updated, you can download the Eclipse Project. Link is situated at the last line of the article

      Delete
  17. Thanks for nice tutorial for custom list view .....really it help me lot and working fine.

    ReplyDelete
  18. Awesome code! Very easy to understand and very well explained! Clarified all my basic concepts in custom views! Keep posting!!! :)

    ReplyDelete
  19. Good example...it works on me..thank you..!!!~~

    ReplyDelete
  20. hi,

    thanks for your post...can u tel me,How to use setonclicklisterner in Listview for changing text in textview?

    ReplyDelete
  21. in onclick event of list item i want to delete a row & want to update list without getting being scrolled to 1st row. how to do this? please help

    ReplyDelete
  22. Thank you sir, this really is a nice tutorial. I understood how to create custom list views in more simple terms.

    Slightly to make it complex, I would like to have a check box to each of the messages in the list and then have a actionbar menu to delete those selected. Could you help me on this?

    ~Kiran

    ReplyDelete
  23. Thanks Dude , i was messing around from so long with this Custom List View.. and you are a Savior . !!!
    Thanks for the Tutorial and for CODE . !!!!
    - Vinay

    ReplyDelete
  24. thank you sir this is great tutorial

    ReplyDelete
  25. Thanks, it was a very useful example. Very simple to understand!

    ReplyDelete
  26. I'm Getting NullPointerException at

    v = vi.inflate(R.layout.list_item_message, null);

    Can you please help me

    ReplyDelete
  27. Thank you for the excellent AND detailed tutorial, this helped me understand custom listviews a lot.

    ReplyDelete
  28. One word : THANKS

    ReplyDelete
  29. How can I refresh your list?
    list.invalidate()
    and
    ((BaseAdapter) List.getAdapter()).notifyDataSetChanged();
    don't seem to work.

    ReplyDelete
  30. Only one bug in CustomAdapter constructor...

    this.data = data;
    this.c = c;

    ReplyDelete
  31. Best tutorial out there. Really on the point. Thanks a lot!
    You get a very special place in my bookmarks :D

    ReplyDelete
  32. Thanks, Very simple to understand! Best tutorial out there..

    ReplyDelete
  33. This is the best tutorial of listview that i have seen

    ReplyDelete
  34. I have put imageview in getView Method class that extends from BaseAdapter class and i am getting image name from database and i need to get bitmap image from assets folder and then set in imageview.setImageBitmap for that reason i have made one function
    private Bitmap getBitmapFromAsset(String strName) {
    AssetManager assetManager = getAssets();
    InputStream istr = null;
    Bitmap bitmap = null;

    try {
    istr = assetManager.open(strName);
    if(istr == null)
    {
    Log.i("getBitmapFromAsset isStr",""+istr);
    bitmap = BitmapFactory.decodeStream(assetManager.open("save_fatwa.jpg"));
    }
    else
    {
    bitmap = BitmapFactory.decodeStream(istr);
    }
    } catch (Exception e) {
    Log.i("getBitmapFromAsset",""+bitmap);
    e.printStackTrace();
    }

    return bitmap;
    }
    when i am writing this function inside CustomAdapter class i am getting error at getAssets() line by indicating that this function is not applicable for this class what can i do ?

    ReplyDelete