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,661

socialnetis

Active Member
Licensed User
Longtime User
Ok I knew I was close!
- 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).
As I said before, for each view in the listItem, we can call view.setOnClickListener.

And then implement the method onClick.
I have a LinearLayout, containing a textview and a ImageView.
So now we can play with the getParent and getChild methods to get all the listItem views:
B4X:
public void onClick(View arg0) {
      LinearLayout l  = (LinearLayout) arg0.getParent();
      TextView t = (TextView) l.getChildAt(0);
      Log.d("B4A", "Clicked view is '"+t.getText());
   }
In this example, I can click on the Image or the text, and always Log the text.
So in the future, if I add some Like and share buttons, I can have complete control of the listview-item by clicking in any view.
 

socialnetis

Active Member
Licensed User
Longtime User
What is the equivalent in a Layout made in xml, to the %x and %y used in the designer of B4A? For example, I would like to have a linearlayout at 10%x to the left, and 90%x to the right. Is there any equivalent?
 

socialnetis

Active Member
Licensed User
Longtime User
I was developing this in 2 ICS devices (phone and tablet). Now I tried in a gingerbread phone, and a emulator with gingerbread, the items have no margins (I set a top and bottom margin in the layout), and I see black edges (left and right) when I scroll. Does anybody had the same problem?
 

socialnetis

Active Member
Licensed User
Longtime User
I was developing this in 2 ICS devices (phone and tablet). Now I tried in a gingerbread phone, and a emulator with gingerbread, the items have no margins (I set a top and bottom margin in the layout), and I see black edges (left and right) when I scroll. Does anybody had the same problem?

Ok, I solved the problem (I hope that this could help to someone, I have been talking alone for a while haha).

First of all, the margins were a problem with frameLayout, I had to change it to LinearLayout, and problem solve.

And with the black edges when scrolling (I read that also they can be white), is an optimization that android does, using a "cache color hint". So, if you don't want this black or white thinkg when scrolling, you need to set this color to transparent (you can read more about this in here)
You can do it in the MyListViewWrapper initialization:
B4X:
getObject().setCacheColorHint(Color.TRANSPARENT);

I'm almost done with the app what is going to use this Custom ListView, the final result its really awesome, I will be uploading it to the market in a few days, if anyone want to see it, I'll provide the link.
 

Lostmymind

Banned
Is there a way to use Listviews without knowning Java or creating Jave classes?
examples would be most appreciated .
 

Lostmymind

Banned
NJDude,
I know this is a custom Listview I am trying to get my list view to refresh it self which leads me to this post.
 

TAK

Member
Licensed User
Longtime User
Hi, nice work. How I can add Buttons in a List View? I need this Buttons to delete the line or to move the line in a other ListView.
 

touchvip

Member
Licensed User
Longtime User
When using the test below error, this is what the cause
android.content.res.Resources$NotFoundException: Resource ID #0x0
 

mshafiee110

Active Member
Licensed User
Longtime User
Hi,dear @warwound
can you upload source eclipse(that include image and 2textview)?
tnx
 

SNOUHyhQs2

Member
Licensed User
Longtime User
B4X:
public View getView(int pPosition, View pConvertView, ViewGroup pParent)

is it possible to expose getView as an event? then let the developer inflate convertview using something like panel.loadlayout?
 
Top