Android Tutorial Custom ListView library

Here's a library tutorial to hopefully show the basic steps required to create a custom ListView.

The custom ListView creates each ListView ListItem from a layout XML file - your imagination is the limit when deciding on the appearance of your ListView!

Why not just use a ScrollView and create your layouts with the Designer or in your B4A code?

The ListView is optimised to display many ListItems.
If you have a ListView containing 100 ListItems and your device screen is showing let's say 8 ListItems then there are only 8, 9, 10 maybe one or two more ListItems created and in memory at any one time.
As you scroll the ListView and a ListItem is no longer visible, that ListItem is recycled.
It's values are changed to match a new ListItem that is now visible.

A ScrollView with 100 ListItems added will contain 100 ListItems - each ListItem being an object in memory all the time that the ScrollView exists.

Not a problem with a small list containing basic ListItems, but imagine a large list where each ListItem contains one or more ImageViews and lots of text.
Maybe each ListItem has one or more click event listeners attached - with a large list that's more memory resources inefficiently used.

The optimisation is achieved by using an Adapter.

This tutorial shows how to implement an ArrayAdapter.

This is a tutorial for you forum members that are familiar with java and library creation...
The emphasis is on implementing an Adapter, MyListView is not a particularly useful or polished library as it is.


I have created four classes:
  • MyListView is a native Android implementation of the native Android ListView.
  • MyListViewWrapper is just that - a wrapper for the MyListView class so that it can be used in a B4A project.
  • MyListViewAdapter is the custom ArrayAdapter that populates MyListView with ListItems.
  • MyDataClass is a data container. MyListViewAdapter uses an Array of MyDataClass objects.

Here's the MyDataClass:

B4X:
public class MyDataClass {
   /*
    * A simple data only class.
    * Not exposed to the IDE but if it was then it would be a non-activity object.
    * It could be declared as a process global - no need for it to be destroyed and re-created on device orientation change for example.
    */
   private HashMap<String, Object> mMyDataValues;
   
   public MyDataClass(){
      mMyDataValues=new HashMap<String, Object>();
   }
   
   /*
    * Just two methods are used to enable any Object type to be added or retrieved.
    * 
    * http://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html
    * 
    * See the documentation if you want to add more methods to manipulate the HashMap.
    * 
    */
   public Object get(String pKey){
      return mMyDataValues.get(pKey);
   }
   
   public void put(String pKey, Object pValue){
      mMyDataValues.put(pKey, pValue);
   }
}

Not exposed to the B4A IDE, this sample custom ListView exposes only the MyListViewWrapper to the IDE.
(But note that in the IDE the MyListViewWrapper class appears as MyListView - it's ShortName).

So MyDataClass is basically a Map, you can put or get any Java Object using a String key name.
The MyListViewAdapter contains an Array of MyDataClass objects - each element of the Array corresponding to a ListView ListItem.

MyListViewWrapper is the next class in order of simplicity:

B4X:
public class MyListViewWrapper extends ViewWrapper<MyListView> {
   
   public void AddItem(String Title, String Description){
      MyDataClass myDataClass=new MyDataClass();
      myDataClass.put("item_title", Title);
      myDataClass.put("item_description", Description);
      getObject().addItem(myDataClass);
   }
   
   public void ClearList(){
      getObject().clearList();
   }
   
   @Override
   public void Initialize(BA pBA, String EventName) {
      /*
       * There is no need to override the Initialize method here.
       * I have done so though just so that the IDE auto-complete shows EventName rather than arg0.
       * 
       * EventName is not used - for simplicity MyListView raises no events.
       */
      super.Initialize(pBA, EventName);
   }
   
   @Hide
   public void innerInitialize(BA pBA, String pEventName, boolean pKeepOldObject) {
      if (!pKeepOldObject){
         setObject(new MyListView(pBA.context));
      }
      super.innerInitialize(pBA, pEventName, true);
   }
}

Anyone that has used the B4A ViewWrapper class will recognise what this class does.

AddItem (Title As String, Description As String)

Simply creates a new ListItem.

So in the IDE you can create and Initialize a MyListView, you can add ListItems to it and you can clear all ListItems.

Next is the MyListView class.
This extends the native Android ListView class, sets a few default values and adds the custom ArrayAdapter:

B4X:
public class MyListView extends ListView {
   /*
    * This class extends the ListView class and uses a custom ArrayAdapter to populate the ListView.
    */
   private MyListViewAdapter mMyListViewAdapter;
   
   public MyListView(Context pContext) {
      super(pContext);
      
      this.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
      
      /*
       * The custom adapter is constructed with an empty data set.
       */
      List<MyDataClass> myData = new ArrayList<MyDataClass>();
      
      /*
       * Here's where you define the name of the layout file to be used for each ListItem
       */
      int itemLayoutResourceId=BA.applicationContext.getResources().getIdentifier("my_list_view_item", "layout", BA.packageName);
      
      /*   as MyListViewAdapter overrides it's native class method getItem() i think aTextViewResourceId is NOT used.
       * I think it would only be used if the ArrayAdapter's default getItem() method is used.
       */
      int aTextViewResourceId=BA.applicationContext.getResources().getIdentifier("item_title", "id", BA.packageName);
      
      /*
       * Construct the custom adapter and set it as this ListView's adapter.
       */
      mMyListViewAdapter=new MyListViewAdapter(pContext, itemLayoutResourceId, aTextViewResourceId, myData);
      this.setAdapter(mMyListViewAdapter);
      
      //   FastScroll hardcoded to true
      this.setFastScrollEnabled(true);
   }
   
   /*
    * http://developer.android.com/reference/android/widget/ArrayAdapter.html
    * 
    * There are many methods to manipulate the data in the ListView.
    * addAll() looks useful is is only available with Android API 11 onwards.
    * 
    */
   
   public void addItem(MyDataClass pMyDataClass){
      mMyListViewAdapter.add(pMyDataClass);
   }
   
   public void clearList(){
      mMyListViewAdapter.clear();
   }
}

Hopefully my comments explain enough...

And here it is - the magical custom ArrayAdapter MyListViewAdapter class:

B4X:
public class MyListViewAdapter extends ArrayAdapter<MyDataClass> {
   
   /*
    * This is the custom ArrayAdapter class.
    * 
    * http://developer.android.com/reference/android/widget/ArrayAdapter.html
    * 
    */
   
   private int mItemLayoutResourceId;
   private LayoutInflater mLayoutInflater;
   private List<MyDataClass> mMyDataClassList;
   private String[] mTextViewIds;
   
   public MyListViewAdapter(Context pContext, int pItemLayoutResourceId, int pTextViewResourceId, List<MyDataClass> pMyDataClassList) {
      super(pContext, pItemLayoutResourceId, pTextViewResourceId, pMyDataClassList);
      
      mItemLayoutResourceId=pItemLayoutResourceId;
      
      /*
       * A reference to the LayoutInflater is kept to avoid calling it each time a new ListItem is required.
       * 
       * But look at the Log output - when the ListView is created the LayoutInflater is used, after that it is seldom used as ListItems are recycled.
       * If your ListItems vary in height then there is a chance the LayoutInflater will again be re-used.
       * 
       * You choose whether keeping a reference to it is a resource waste or not!
       * 
       */
      mLayoutInflater=LayoutInflater.from(pContext);
      
      mMyDataClassList=pMyDataClassList;
      
      /*
       * These are the ids of the textViews in my ListItem layout.
       * 
       * MyListViewWrapper uses the same ids to add values to a MyDataClass instance.
       * You'll probably want to use your own technique instead of my technique track TextView ids and Array values.
       * 
       */
      mTextViewIds=new String[]{"item_title", "item_description"};
   }
   
   /*
    * A helper method to find a View in the ListItem.
    */
   private static View findViewById(View pView, String pId) {
      return pView.findViewById(BA.applicationContext.getResources().getIdentifier(pId, "id", BA.packageName));
   }
   
   @Override
   public View getView(int pPosition, View pConvertView, ViewGroup pParent) {
      /*
       * getView() is called when the ListView needs to make a ListItem visible.
       * 
       * A previously visible but no longer visible ListItem may be recycled otherwise a new ListItem is created.
       * 
       */
      if (pConvertView == null) {
         Log.i("B4A", "New ListItem created");
         pConvertView = mLayoutInflater.inflate(mItemLayoutResourceId, null);
      } else {
         Log.i("B4A", "Existing ListItem recycled");
      }
      MyDataClass dataItem = mMyDataClassList.get(pPosition);
      if (dataItem != null) {
         TextView textView;
         for(int i=0, j=mTextViewIds.length; i<j; i++){
            textView=(TextView)findViewById(pConvertView, mTextViewIds[i]);
            textView.setText((String) dataItem.get(mTextViewIds[i]));
         }
      }
      /*
       * If dataItem is null and this is a recycled View then the recycled View will retain it's old values..
       * 
       * It's probably not possible/likely that dataItem will be null?
       * 
       */
      return pConvertView;
   }
}

A month or so back both i and desolatesoul were wanting to create custom ListViews and put off by the complexity of having to use an Adapter.
Like many things it's far simpler when you know how!

The ArrayAdapter getView() method is called by the ListView when it needs to display a ListItem.
If a ListItem has been scrolled out of view then the ListView can pass that no-longer visible ListItem to getItem() where it is re-cycled and used for a ListItem that has now scrolled into view.

The size of your ListView and the size of the ListItems dictates more or less how many ListItems will exist and be in memory at any one time.

A simple B4A project shows how to use MyListView:

B4X:
Sub Process_Globals
End Sub

Sub Globals
   Dim MyListView1 As MyListView
End Sub

Sub Activity_Create(FirstTime As Boolean)
   MyListView1.Initialize("")
   
   Activity.AddView(MyListView1, 0, 0, 100%x, 100%y)
   
   Dim SomeText As List
   SomeText.Initialize2(Array As String("Lorem ipsum dolor sit amet, consectetur adipiscing elit", "Pellentesque elementum pharetra odio ut tristique. Sed et odio libero, vel scelerisque velit. Vestibulum suscipit pellentesque erat convallis convallis.", "Mauris sodales turpis eu elit condimentum condimentum. Pellentesque at odio magna, et auctor ante. Nam lacinia, erat eu molestie varius, purus nunc volutpat nisi, sed interdum lorem purus vel diam. Donec dignissim suscipit dui, eget eleifend purus iaculis ac.", "Sed quam urna, porttitor id scelerisque ut, vestibulum id nibh. Pellentesque id velit a dolor consectetur egestas sit amet quis risus. Quisque scelerisque porttitor urna vitae ullamcorper. Phasellus elementum elementum nibh, sit amet eleifend sem porttitor sed. Proin convallis aliquet est et aliquet. Duis pretium congue bibendum. Etiam tincidunt, eros in dapibus tincidunt, mauris nulla rutrum risus, vitae sodales lacus lectus ac dolor.", "In elementum, odio vitae facilisis scelerisque, sapien purus rhoncus odio, eget aliquam dolor diam id lacus. Suspendisse tristique cursus velit, id volutpat mi pulvinar quis. Duis suscipit nibh malesuada leo tincidunt venenatis vel eu libero. Maecenas auctor tortor eget justo sodales fringilla.", "Donec luctus risus ac dui ullamcorper molestie. Nullam in ipsum vitae nisl sodales fringilla quis non felis.", "Suspendisse lobortis risus sed est adipiscing nec hendrerit augue gravida.", "Ut sollicitudin adipiscing lobortis. Aliquam erat tellus, tincidunt et pulvinar at, eleifend et metus. Suspendisse potenti.", "Ut est lacus, hendrerit et luctus eu, tristique vitae ligula. Morbi eget est sit amet augue feugiat ultrices in nec odio. Vivamus justo lacus, laoreet sit amet consectetur nec, pretium eu ante. Nulla tincidunt congue ultricies. Sed venenatis suscipit diam, eu lacinia nisi pretium sed.", "Phasellus lacinia ultrices condimentum.", "B4A ROCKS!!", "Nulla convallis, justo ut pulvinar fringilla, turpis urna posuere nisl, auctor volutpat justo turpis sed orci. Nulla sagittis scelerisque auctor.", "Praesent dolor nisl, sollicitudin et lobortis eu, pulvinar eu ipsum. Vivamus id aliquam magna.", "Suspendisse potenti. Nullam felis nisi, facilisis non convallis sit amet, mattis a nisl. Aliquam in ipsum risus. In hac habitasse platea dictumst.", "Donec non nisl in mauris malesuada consectetur eget ultrices leo.", "Nulla eleifend dui ut orci tempor egestas. Donec rutrum, eros tincidunt facilisis venenatis, leo enim rutrum enim, sed porta libero est eu ipsum. Sed scelerisque commodo purus a auctor. Quisque ut mi non velit aliquam hendrerit et non risus. Nunc vehicula nunc at sapien iaculis vestibulum. Sed at ligula id neque ornare tempor vitae ac dolor.", "Cras congue, tellus a iaculis lacinia, turpis lacus interdum erat, sed elementum sapien libero vel arcu.", "Nullam sodales hendrerit vulputate. Nulla in nisl nibh, id mollis nunc. Nunc semper neque euismod lorem laoreet rhoncus. Vivamus vel risus et augue posuere egestas.", "In commodo porttitor placerat. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "Nullam in risus justo, a dapibus urna. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam quis augue neque. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris dapibus enim ut felis sollicitudin ut sodales arcu dapibus. Donec eget mi libero, non placerat mauris. Nullam bibendum diam quis mi aliquam egestas. Donec eget ante tortor, in laoreet est. Nam et ante pharetra urna lobortis hendrerit vitae dictum nisi. Nulla pellentesque odio sit amet neque pulvinar ut condimentum dui vehicula.", "Vivamus vehicula aliquam massa et venenatis."))

   Dim i As Int
   For i=0 To SomeText.Size-1
      MyListView1.AddItem("Title #"&i, SomeText.Get(i))
   Next
   
   Log(SomeText.Size&" items added to the ListView")
   
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
End Sub

And a screengrab shows the not very useful but functional MyListView in action:

my_list_view.jpg


Each ListItem is of variable height.
A new MyListView method could be created to remove one or more ListItems and the ListView would automatically adjust things so it all looks ok - this is a dynamic fluid layout.

So anyone that's still reading and wondering how to create their own custom ListView will need to:
  • Create an XML layout file to suit the data they want to display.
    That file must be set as read-only and added to your project's Objects\res\layout folder.
    The layout should contain at least one TextView with an id defined.
  • Update MyListView.java:
    Line 31 set the name of the layout file:
    B4X:
    /*
     * Here's where you define the name of the layout file to be used for each ListItem
     */
    int itemLayoutResourceId=BA.applicationContext.getResources().getIdentifier("my_list_view_item", "layout", BA.packageName);
    Line 36 set the id of a TextView in your layout file:
    B4X:
    /*   as MyListViewAdapter overrides it's native class method getItem() i think aTextViewResourceId is NOT used.
     * I think it would only be used if the ArrayAdapter's default getItem() method is used.
     */
    int aTextViewResourceId=BA.applicationContext.getResources().getIdentifier("item_title", "id", BA.packageName);
    This seems to be a requirement to define at least one TextView - my comments explain that.
  • Now you need to make some more advanced modifications to MyListViewAdapter.java.
    You mainly need to update the getView()method, getting values from the mMyDataClassList Array and getting Views from the ListItem layout and using the Array values to set your ListItem's appearance.
    Things are now a bit advanced so anyone that gets this far and needs help had best post in this thread for help.
    My example code is unlikely to be perfect as it is for anyone else's custom ListView but with a little patience can be modified to suit many other uses cases.
  • Finally you'll want to update MyListViewWrapper.java with methods to add, modify and remove your custom ListItems.
    You'll probably also want methods to get and set the current ListView scroll position to enable you to save and restore the scroll position on device orientation change.

I'm currently working on a project that involves a CursorAdaptor.
The CursorAdaptor as it's name suggests populates a ListView with the result set from a database query.
Run-time changes to the database are dynamically applied to the ListView and it's ListItems - that might be another post in few days!

Complete Eclipse library source and B4A demo project attached.

And that's the end of what must be my longest forum post to date, LOLs!!!

Martin.
 

Attachments

  • MyListView.zip
    31.1 KB · Views: 4,549

Sinan Tuzcu

Well-Known Member
Licensed User
Longtime User
OK,
can you create it for me and I will pay you?
because, i have not Java programming skills.
 
Last edited:

Sinan Tuzcu

Well-Known Member
Licensed User
Longtime User
@ Informatix,

I've watched your ULV, it really is very good but I do not know if I can so realize that, what I would like to create.

Unless you realize me that
It should already look like at Whatsupp, so with the balloons.

or
@ warwound,
do you want to create it me?
 

Informatix

Expert
Licensed User
Longtime User
@ Informatix,

I've watched your ULV, it really is very good but I do not know if I can so realize that, what I would like to create.

Unless you realize me that
It should already look like at Whatsupp, so with the balloons.

or
@ warwound,
do you want to create it me?

1) The good news: it's a demo I planned to do for the ULV to demonstrate how the list can go up from the bottom. That doesn't mean I'm going to do it right now but I will surely help you to do it;

2) If you're able to create the balloons with the B4A designer (it's just a panel with a background image and two labels), then you're able to create your ULV items.

Now, if you prefer to pay someone many times the price of ULV to do it...
 

Sinan Tuzcu

Well-Known Member
Licensed User
Longtime User
Helo Informatix,

why a panel with a background image and two Labels?
in my opinion, should a Label and two Background Images for right arrow and left arrow. ?

or what do you think?
 

Informatix

Expert
Licensed User
Longtime User
Helo Informatix,

why a panel with a background image and two Labels?
in my opinion, should a Label and two Background Images for right arrow and left arrow. ?

or what do you think?

You paint your balloon (9-patch drawable) in the background of the panel and you put two labels in the balloon (to mimic the Whatsapp appearance). But you can do this differently. There's not an unique way. I think that the panel with two labels is the best option.
 

Sinan Tuzcu

Well-Known Member
Licensed User
Longtime User
Helo,

sorry for ask but what is (9-patch Drawable) ?? :)

how to paint the balloon in the background on the panel?
and how the labels come in the balloon?


Please do not Forget, i am newbie


gruß
sinan
 

Informatix

Expert
Licensed User
Longtime User
Helo,

sorry for ask but what is (9-patch Drawable) ?? :)

how to paint the balloon in the background on the panel?
and how the labels come in the balloon?


Please do not Forget, i am newbie

You should read the tutorials on this forum, like this one : http://www.b4x.com/forum/basic4android-getting-started-tutorials/14283-nine-patch-images-tutorial.html#post80908
and the guides for newbies: Basic4android (Basic for Android) - Android programming with Gui designer
 

Sinan Tuzcu

Well-Known Member
Licensed User
Longtime User
Helo,

how do i adjust the label to the text?
if the text is long, then the label must also increase.
 

warwound

Expert
Licensed User
Longtime User
You are presumably creating this list item layout in an XML file?

So in that list item layout file you'd set the dimensions of the list item - you can set the list item height to wrap_content and then it should grow or shrink to match the height of it's content.

If you have problems you will have to upload the layout file that you are using for your list items.

Martin.
 

Sinan Tuzcu

Well-Known Member
Licensed User
Longtime User
yes,

i have this library in my lib_directory of B4A.
but i don't know, how i used this library for my Problem!

gruß from germany
sinan
 

socialnetis

Active Member
Licensed User
Longtime User
If I make each list item a simple Panel. Will I be able to manipulate that panel from b4a, to put any view I want?
If not, I would like each ListItem to be a panel containing an ImageView and a Label. Each ImageView will have different Height, if the panel have the property to Wrap_Content, am I gonna be able to see the full ImageView1 height?
 

warwound

Expert
Licensed User
Longtime User
If I make each list item a simple Panel. Will I be able to manipulate that panel from b4a, to put any view I want?
If not, I would like each ListItem to be a panel containing an ImageView and a Label. Each ImageView will have different Height, if the panel have the property to Wrap_Content, am I gonna be able to see the full ImageView1 height?

In theory there's no reason why you couldn't update the Adapter getView method so that it calls a b4a Sub, the b4A Sub creates or recycles a ListItem and returns the ListItem to the getView method.

That'd enable you to take complete control over all ListItem contents in your b4a code.

Your getView method could raise a b4a event and the event handling Sub would create or recycle the ListItem.

Martin.
 

socialnetis

Active Member
Licensed User
Longtime User
Ok, thanks!
By now I went for the label + imageview way, and is working fine.

Thank you very much for this helpful tutorial!
 

socialnetis

Active Member
Licensed User
Longtime User
Hi, I have some more questions, I have a label and a ImageView.
- We have the OnItemClick method, which tells me that an item of the listview have been clicked. Is there anyway to detect if the click was on the label or the ImageView? (I would like to raise an event when the click is on the ImageView, and do nothing if it was on the label).
- Is there a way to change the color of the item click? Moreover, I would like to not having any color (like no feedback when clicking an item. Maybe using a transparent color?).
- Can we change de color of the lines that separate each listitem?
 

socialnetis

Active Member
Licensed User
Longtime User
Ok I have found some partial answers to my questions.
- We have the OnItemClick method, which tells me that an item of the listview have been clicked. Is there anyway to detect if the click was on the label or the ImageView? (I would like to raise an event when the click is on the ImageView, and do nothing if it was on the label).
In MyListViewAdapter, in the getView method, you can use:
textview.setOnClickListener(this);
imageView.setOnClickListener(this);
And implement the onClick method ( public void onClick(View arg0)). The problem with this, is that you don't have any reference of the listitem, just the view that was clicked. In the future I would like to add a "Like" button, and so, I need to detect if the click was on that button, and in which listitem was. So if anyone knows how to do it, please let me know!

- Is there a way to change the color of the item click? Moreover, I would like to not having any color (like no feedback when clicking an item. Maybe using a transparent color?).
All of the answers I found on google, used some .xml files. The problem was that they linked this .xml files in the .xml file of the ListView, and we don't have that! (we only have the layout of the ListView-row).
Luckily for me, I only needed to be transparent or not feedback (not change the color).
And this worked for me:
In the MyListViewWrapper class, in the initialization method:
B4X:
public void Initialize(BA pBA, String EventName) {
      /*
       * There is no need to override the Initialize method here.
       * I have done so though just so that the IDE auto-complete shows EventName rather than arg0.
       * 
       * EventName is not used - for simplicity MyListView raises no events.
       */
      super.Initialize(pBA, EventName);
      getObject().setDividerHeight(0);
      getObject().setVerticalScrollBarEnabled(false);
      Drawable d = getObject().getSelector();
      d.setAlpha(0);
      getObject().setSelector(d);
   }
The setDividerHeight if for my las Question:
- Can we change de color of the lines that separate each listitem?
I needed to be transparent as well, and I found that setting the height to 0, it dissapear.
The VerticalScrollBar was disabled because I'm adding items on demand (download images from internet), and the scrollbar size changes a lot, and I don't like that.
And the getSelector method, gives me the drawable object that is used when clicking an item. I set the Alpha channel to 0 (100% transparent), and then set the drawable again as the selector.

Hope this helps someone, and if anyone knows how to detect a listitem-view click, please let me know!
 
Top