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

corwin42

Expert
Licensed User
Longtime User
Thanks very much for this great tutorial! :sign0098:

The topics here are getting more and more advanced. This shows how powerful B4A is and how great this community here is.

And I totally agree on Title #10 in your screenshot. :icon_clap:
 

thedesolatesoul

Expert
Licensed User
Longtime User
warwound,
this is an amazing post! :sign0188:
it will come in very handy.
Anyone wanting to read more about how listviews work: See here

This first step is quite major!

In order to further the discussion here are some ideas:

1. Adding multiple layouts.
This can be done in many ways. We can either use multiple layouts to be used in getView depending on a type in MyDataClass and inflate a different layout and return it. Or we can use the same layout, but hide/show different views based on a type in MyDataClass.
In this way we can add separators (for categories etc)

2. Handling events of views
We should be able to handle and raise any events for views in the layout we want. This can be easy by setting onClickListener etc again in the getView method. Then you can set the state in MyDataClass somehow.

3. Handling states (like Selectable)
As you can see the listview recycles all the views, so you want the 'selectable' state to be stored in MyDataClass again and maybe change the background color etc based on that.

4. One other thing I am wondering if you could use a .bal file as a layout for the listview (as this is the B4A way of doing things). The D4A LoadLayout method returns a ViewGroup I think, while the B4A LoadLayout method returns the LayoutValues. However, even if you load a .bal file, you probably wont be able to use designer scripts (I am not sure how you would use FILL_PARENT or WRAP_CONTENT).

I am not sure what more people will want from a listview. These are just ideas im throwing into the mix.
 

warwound

Expert
Licensed User
Longtime User
1. Adding multiple layouts.
This can be done in many ways. We can either use multiple layouts to be used in getView depending on a type in MyDataClass and inflate a different layout and return it. Or we can use the same layout, but hide/show different views based on a type in MyDataClass.
In this way we can add separators (for categories etc)

There is a potential problem here - the ArrayAdaper class requires either just the resource id of a TextView OR the resource id of the XML ListItem layout and a resource id of a TextView in that layout.

If you want to use more than one ListItem layout there is no way to tell the ArrayAdapter that.

BUT i think the resource ids passed to the ArrayAdapter constructor are ONLY used when you haven't overridden the ArrayAdapter getView method.
Looks like if you don't override getView then there is a default getView behaviour that will automatically inflate the ListItem layout and populate a single TextView in that layout.

As we're overriding getView we might i think/hope find that any resource ids passed to the ArrayAdapter constructor are NOT USED.

So we are hopefully free to use different ListItem layouts in one list based on the data to be displayed.


2. Handling events of views
We should be able to handle and raise any events for views in the layout we want. This can be easy by setting onClickListener etc again in the getView method. Then you can set the state in MyDataClass somehow.

I have been working on this and this is what i have found.

When you inflate a layout you can add a View.OnClickListener to any View in the ListItem layout or even the layout itself.

You could create and add the View.OnClickListener inline or your ArrayAdapter can implement the View.OnClickListener interface.

In a project i'm working on using a CursorAdapter, my custom CursorAdaptor implements the View.OnClickListener.
I added the required onClick method to my custom CursorAdapter class:

B4X:
@Override
public void onClick(View pView) {
    // handle the click on pView here
}

I added the View.OnClickListener to the ListItem itself not to a child View of the ListItem.
So my list displays summary info from a database and a tap on a ListItem toggles display of detailed info to/from visible/hidden.
This works fine BUT required creating an array to keep track of whether when last visible a ListItem was showing summary or detailed info.
I also had to use the ListItem Tag property to be able to get a reference in the onClick method to the index of the clicked ListItem (pView).

So that was yesterday - today i have discovered the AdapterView.OnItemClickListener!

The AdapterView.OnItemClickListener is the interface intended to be used - it exposes the clicked item and much more so no need to use a View Tag to identify the clicked View as you have to do with View.OnClickListener.

Here's the updated MyListViewAdapter class;

B4X:
// two new imports required

import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;

public class MyListViewAdapter extends ArrayAdapter<MyDataClass> implements OnItemClickListener{
   
   /*
    * This is the custom ArrayAdapter class.
    * 
    * http://developer.android.com/reference/android/widget/ArrayAdapter.html
    * 
    * Version 1.1 implements the android.widget.AdapterView.OnItemClickListener.
    * I had success implementing the View.OnItemClickListener but that required a lot of workaround code to identify the clicked View.
    * android.widget.AdapterView.OnItemClickListener is the interface intended to be used to handle clicks on adapter items.
    * 
    * http://developer.android.com/reference/android/widget/AdapterView.OnItemClickListener.html
    * 
    */
   
   // all other code remains as before and snipped to save space

   @Override
   public void onItemClick(AdapterView<?> pParent, View pView, int pPosition, long pId) {
      /*   this is the method that must be implemented as part of the AdapterView.OnItemClickListener
       * 
       * pParent is the MyListView instance
       * pView is the inflated ListItem
       * pPosition is the index of the clicked ListItem
       * pId is i think only of use with a CursorAdapter as it will be the row id of the clicked ListItem in the Cursor.
       */
      MyDataClass dataItem = mMyDataClassList.get(pPosition);
      Log.d("B4A", "The clicked ListItem item_title is '"+dataItem.get("item_title")+"'");
      
   }
}

MyListViewAdapter now handles clicks on a ListItem.
The onItemClick method needs to be implemented and as you can see it is passed various useful parameters that enable you to handle the ListItem click.

Now all that's required is to update the MyListView constructor to set the adapter as the OnItemClickListener:

B4X:
   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);

      //   this is where we set the ListView's OnItemClickListener to the instance of the MyListViewAdapter which now implements android.widget.AdapterView.OnItemClickListener
      //   note that you must set the OnItemClickListener AFTER setting the Adapter
      this.setOnItemClickListener(mMyListViewAdapter);
      

      //   FastScroll hardcoded to true
      this.setFastScrollEnabled(true);
   }

Impressed?

It didn't work!!! LOLs

No matter what i did, a click on a ListItem failed to Log a single thing...

Anyway a bit of Googling revealed the problem: android - ListView with clickable/editable widget - Stack Overflow

Having a focusable item in a row of a ListView causes the OnItemClickListener NOT to be invoked.

I removed the unused ImageButtons from the XML ListItem layout and it works perfectly.

There are also the AdapterView.OnItemLongClickListener and AdapterView.OnItemSelectedListener interfaces to investigate and they can probably be implemented in the same was as the OnItemClickListener interface.

3. Handling states (like Selectable)
As you can see the listview recycles all the views, so you want the 'selectable' state to be stored in MyDataClass again and maybe change the background color etc based on that.

Yes you could set a value in each ListItem's corresponding MyDataClass, let's assume a state expanded:

B4X:
dataItem.set("expanded", false);

You could update the MyDataItem constructor to set a default expanded (or any other) value, otherwise you'd have to do that in code for each and every instance of MyDataItem.

Or more elegant perhaps is to have two constructors:

B4X:
   public MyDataClass(){
      mMyDataValues=new HashMap<String, Object>();
      mMyDataValues.put("expanded", false);
   }
   
   public MyDataClass(boolean pExpanded){
      mMyDataValues=new HashMap<String, Object>();
      mMyDataValues.put("expanded", pExpanded);
   }

So default states are set if the default constructor is used otherwise you can create a MyDataClass object with custom state.


4. One other thing I am wondering if you could use a .bal file as a layout for the listview (as this is the B4A way of doing things). The D4A LoadLayout method returns a ViewGroup I think, while the B4A LoadLayout method returns the LayoutValues. However, even if you load a .bal file, you probably wont be able to use designer scripts (I am not sure how you would use FILL_PARENT or WRAP_CONTENT).

Maybe in the library java you could use the B4A LoadLayout method to load a B4A layout into a container, but as you say there could be problems with layouts that use Designer scripts?
If you want to do this then i think you'd be a customer for the D4A product?

Anyway it's time for breakfast here in sunny Norfolk, updated project files attached.

Martin.
 

Attachments

  • MyListView_v_1_10.zip
    29.7 KB · Views: 1,743

thedesolatesoul

Expert
Licensed User
Longtime User
man...long post
and good detective work Watson!!

As we're overriding getView we might i think/hope find that any resource ids passed to the ArrayAdapter constructor are NOT USED.
Yes, this is what I'm betting on. Its easy to inflate a layout right here in getView. There doesnt seem to be any restriction in ArrayAdapter that it can use only one layout.

Impressed?

It didn't work!!! LOLs

No matter what i did, a click on a ListItem failed to Log a single thing...

Anyway a bit of Googling revealed the problem: android - ListView with clickable/editable widget - Stack Overflow

Quote:
Having a focusable item in a row of a ListView causes the OnItemClickListener NOT to be invoked.
I removed the unused ImageButtons from the XML ListItem layout and it works perfectly.

There are also the AdapterView.OnItemLongClickListener and AdapterView.OnItemSelectedListener interfaces to investigate and they can probably be implemented in the same was as the OnItemClickListener interface.
Yes, I was impressed!!! until that point :p
but the link you provided does give two solutions...maybe just set focusable to false on all child views...but we should still be able to listen for clicks on children...but edittexts within a listview would be a problem...im not too bothered about them...i think buttons, and checkboxes are reasonably enough in a listview

anyway its surprisingly sunny here too...and im sure you made some progress between breakfast and lunch...it will be a while before i get time to look at this more deeply

:sign0098:
 

luthepa1

Member
Licensed User
Longtime User
Thanks warwound for this awesome tute for custom list adapter. However I am stuck in that when I go to run your example program it crashes right away :confused:

I have followed the tutes on how to make a library plugin for B4A and create the java and xml files with out error.

One possible difference is that I am using java v1.7 only on my dev system. So under the java build path libraries for the project I changed to java v1.7 from java 1.6. This meant I also had to change a depreciated statement of FILL_PARENT to MATCH_PARENT. I hope that is right?
B4X:
   private MyListViewAdapter mMyListViewAdapter;

   public MyListView(Context pContext) {
      super(pContext);

      this.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

When I load the android app it says "Unfortunately MyListView stopped" That's it. The log view in B4A does show "NoClassDefFoundError".
 

warwound

Expert
Licensed User
Longtime User
Can you run the example again and post the full unfiltered log - uncheck the Filter box.

I need to know which class is missing.

Martin.
 

luthepa1

Member
Licensed User
Longtime User
Thanks warwound for this awesome tute for custom list adapter. However I am stuck in that when I go to run your example program it crashes right away :confused:

I have followed the tutes on how to make a library plugin for B4A and create the java and xml files with out error.

One possible difference is that I am using java v1.7 only on my dev system. So under the java build path libraries for the project I changed to java v1.7 from java 1.6. This meant I also had to change a depreciated statement of FILL_PARENT to MATCH_PARENT. I hope that is right?
B4X:
   private MyListViewAdapter mMyListViewAdapter;

   public MyListView(Context pContext) {
      super(pContext);

      this.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

When I load the android app it says "Unfortunately MyListView stopped" That's it. The log view in B4A does show "NoClassDefFoundError".

Ok I am finding more info now. Could be java version issue as android is java 6 based but Eral says v7 is now supported. I am running B4A v2.00

http://www.b4x.com/forum/basic4android-updates-questions/19189-error-java-lang-noclassdeffounderror.html#post110478
 

luthepa1

Member
Licensed User
Longtime User
OK all sorted it seems. I can now run the demo custom listview app :D

I had install java 6 and use that for compiling the library under eclipse and I also decided to set java 6 in the config paths of B4A.

Now I can run the demo app.
 

luthepa1

Member
Licensed User
Longtime User
very quick post incase its a simply solution but fear I might need to provide more info.

I am now customising this custom list adapter code to make my own layout list view. only real change to the code I have made is to add to the AddItem method to include 2 bitmap objects to pass to the list view. I think my code changes will work but I have an issue it seems when trying to inflate my xml layout. My layout has 3 text views and 2 image views in a relative layout.

When I run the demo app by warwound witht he slightly modded library I get the error "android.content.res.Resource$NotFoundException: Resource ID #0x0"

I added more logging to the code to see what is happening and where. What I find is in MyListView before declaring MyListViewAdapter the itemLayoutResourceId = 0 and aTextViewResourceId = 0. Is that right? I am assuming they should not be zero.

So in MyListView class we have "mMyListViewAdapter = new MyListViewAdapter(pContext, itemLayoutResourceId, aTextViewResourceId, myData);" call to which we goto MyListViewAdapter class and execute "pConvertView = mLayoutInflater.inflate(mItemLayoutResourceId, null);" but this seems to be where execution stops with above error and I assume because itemLayoutresourceId is zero.

Originally in code we have Log.i("B4A", "New ListItem created"); before the inflater statement which we can see "New List Item created in log cat window but if I move the logging statement to after the inflater statement then I don't see this log message. And when mItemLayoutResourceId is zero I assume this is the problem and something about inflating my xml layout is not working correctly?

Here is my xml layout that also depends on 3 other xml files for selector highlighting which are all located in the drawable folder (including icons) with the layout xml in the layout folder and all read only.

B4X:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/list_selector"
    android:orientation="horizontal"
    android:padding="5dip" >
 
    <!--  ListRow Left side Thumbnail image -->
    <LinearLayout android:id="@+id/thumbnail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="3dip"
        android:layout_alignParentLeft="true"
        android:layout_marginRight="5dip">
 


        <ImageView
            android:id="@+id/list_image"
            android:layout_width="50dip"
            android:layout_height="50dip"
            android:src="@drawable/icon" >
            
        </ImageView>
    </LinearLayout>
 
    <!-- Title Of Exercise-->



    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/thumbnail"
        android:layout_toLeftOf="@+id/status"
        android:layout_toRightOf="@+id/thumbnail"
        android:text="Tricep French Presses Dumbbell"
        android:textColor="#ffffffff"
        android:textSize="15dip"
        android:textStyle="bold"
        android:typeface="sans" />
 
    <!-- Description Name -->



    <TextView
        android:id="@+id/description"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/title"
        android:layout_marginTop="1dip"
        android:layout_toLeftOf="@+id/status"
        android:layout_toRightOf="@+id/thumbnail"
        android:text="Lower Back - testing 123456789 the cat sat on the mat"
        android:textColor="#ffcccccc"
        android:textSize="10dip" />
 
    <!-- Rightend Workout Sessions Count -->

    <TextView
        android:id="@+id/session_count"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignTop="@id/title"
        android:gravity="right"
        android:text="3"
        android:textColor="#ffcccccc"
        android:textSize="10dip"
        android:textStyle="bold" />
 
     <!-- Rightend Arrow -->
     <ImageView
         android:id="@+id/status"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentRight="true"
         android:layout_below="@id/session_count"
         android:layout_centerVertical="true"
         android:src="@drawable/ic_status" />
 
</RelativeLayout>

Any help much appreciated.
 
Last edited:

warwound

Expert
Licensed User
Longtime User
Hi.

Uncomment the Filter checkbox in the IDE log viewer and run your project again - it should give you more detail about exactly which resource cannot be found.

If you post the complete log output relating to this exception i'll take a look and try to decipher it.

Martin.
 

luthepa1

Member
Licensed User
Longtime User
Thanks! I shall give that a shot when I get back to pc to take another look. I did not even think of filter option so perhaps I might find the issue.

Sent from my GT-I9300 using Tapatalk 2
 

moster67

Expert
Licensed User
Longtime User
Hi Martin - amazing tutorial and what an excellent listview it produces. :sign0098:

I managed to compile it in Eclipse without a breeze and got a great working library as a result which works nicely with the B4A-sample.

Of course, many times a scrollview would be easier to use to produce the same result but I read your comments why to use a listview instead of scrollview. In either case, you did a great job and I will experiment with your code to learn more.

As always, thanks for your time and useful libraries!
 

luthepa1

Member
Licensed User
Longtime User
OMG!!!!! I can't believe it!!!

Found the problem. What is the problem... its me! :signOops:

I am using the code from your very nice tutee to implant into my current project I am working on to I can make more informative list views. But before I go and implant into my current app I am using your demo app to see the changes I make to the library plugin for B4A in eclipse.

So... the xml layout files I made.... where am I putting them??? into my current project app but not into you demo app location so no wonder when I run your demo app it can't find my list item layout file - DOH!

I will be slapping myself for a while on this one.

Anyway I copies my layout files into both locations and all it working! YAY!

Now to get down to business and implement the long click listers and the other suggestions you made.
 

moster67

Expert
Licensed User
Longtime User
I noted that demo shows in the log the item clicked which is from this code:

B4X:
@Override
   public void onItemClick(AdapterView<?> pParent, View pView, int pPosition, long pId) {
      /*   this is the method that must be implemented as part of the AdapterView.OnItemClickListener
       * 
       * pParent is the MyListView instance
       * pView is the inflated ListItem
       * pPosition is the index of the clicked ListItem
       * pId is i think only of use with a CursorAdapter as it will be the row id of the clicked ListItem in the Cursor.
       */
      MyDataClass dataItem = mMyDataClassList.get(pPosition);
      Log.d("B4A", "The clicked ListItem item_title is '"+dataItem.get("item_title")+"'");
      
   }

If I now want to make this event available from B4A like:

B4X:
Sub MyListView1_ItemClick (Position As Int, Value As Object)
   Log("I was here - " & Position & " - " & Value) 
End Sub


where would I create the methods to expose the events (and pass on the values) to B4A? Any hints?
 
Last edited:

warwound

Expert
Licensed User
Longtime User
You need to use the raiseEvent method from the onItemClick method and to do that you need a reference to the Activity BA object of the Activity that created your ListView.

That BA object is passed to the MyListViewWrapper Initialize method but no reference to it is kept.
By updating the MyListView and MyListViewAdapter constructors you can pass the BA object from MyListViewWrapper to MyListView then finally to MyListViewAdapter where you need it.
As well as a reference to the BA object you also need a reference to the EventName in MyListViewAdapter.

So the best way to do this is pass the BA object and EventName from parent class to child class and to do that you need to update the class constructors so the BA object and EventName can be passed.

MyListViewWrapper, look at innerInitialize and setObject:

B4X:
@ActivityObject
@Author("Martin Pearman")
@Events(values = { "ItemClick(ItemIndex As Int)" })
@ShortName("MyListView")
@Version(1.1f)
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, pEventName.toLowerCase(BA.cul)));
      }
      super.innerInitialize(pBA, pEventName, true);
   }
}

Now update the MyListView constructor so it accepts a BA object and the EventName:


B4X:
@Hide
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(BA pBA, String pEventName) {
      super(pBA.context);

      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(pBA, itemLayoutResourceId, aTextViewResourceId, myData, pEventName);
      this.setAdapter(mMyListViewAdapter);

      //   this is where we set the ListView's OnItemClickListener to the instance of the MyListViewAdapter which now implements android.widget.AdapterView.OnItemClickListener
      //   note that you must set the OnItemClickListener AFTER setting the Adapter
      this.setOnItemClickListener(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();
   }
}

See how the updated constructor accepts the BA object and EventName and now passes them on to the MyListViewAdapter constructor?
Where previously a reference to a Context was required i have replaced that with pBA.context.

Lastly in MyListViewAdapter:

B4X:
@Hide
public class MyListViewAdapter extends ArrayAdapter<MyDataClass> implements OnItemClickListener{
   
   /*
    * This is the custom ArrayAdapter class.
    * 
    * http://developer.android.com/reference/android/widget/ArrayAdapter.html
    * 
    * Version 1.1 implements the android.widget.AdapterView.OnItemClickListener.
    * I had success implementing the View.OnItemClickListener but that required a lot of workaround code to identify the clicked View.
    * android.widget.AdapterView.OnItemClickListener is the interface intended to be used to handle clicks on adapter items.
    * 
    * http://developer.android.com/reference/android/widget/AdapterView.OnItemClickListener.html
    * 
    */
   
   private BA mBA;
   private String mEventName;
   private int mItemLayoutResourceId;
   private LayoutInflater mLayoutInflater;
   private List<MyDataClass> mMyDataClassList;
   private String[] mTextViewIds;
   
   public MyListViewAdapter(BA pBA, int pItemLayoutResourceId, int pTextViewResourceId, List<MyDataClass> pMyDataClassList, String pEventName) {
      super(pBA.context, pItemLayoutResourceId, pTextViewResourceId, pMyDataClassList);
      
      mBA=pBA;   //   keep a reference to the Activity BA object
      mEventName=pEventName;
      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(pBA.context);
      
      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;
   }

   @Override
   public void onItemClick(AdapterView<?> pParent, View pView, int pPosition, long pId) {
      /*   this is the method that must be implemented as part of the AdapterView.OnItemClickListener
       * 
       * pParent is the MyListView instance
       * pView is the inflated ListItem
       * pPosition is the index of the clicked ListItem
       * pId is i think only of use with a CursorAdapter as it will be the row id of the clicked ListItem in the Cursor.
       */
      MyDataClass dataItem = mMyDataClassList.get(pPosition);
      Log.d("B4A", "The clicked ListItem item_title is '"+dataItem.get("item_title")+"'");
      mBA.raiseEvent(MyListViewAdapter.this, mEventName+"_itemclick", new Object[]{pPosition});
   }
}

Instance members mBA and mEventName now keep the required references and in onItemClick the BA raiseEvent method can now be called!
Here i'm just passing the index of the clicked item in the list, you can pass more parameters if you need to.

Modified class files attached - untested but should work!

Martin.
 

Attachments

  • RaiseEvent.zip
    3.5 KB · Views: 724

moster67

Expert
Licensed User
Longtime User
Modified class files attached - untested but should work!

Martin.

It does work! :sign0098:

To get it working in b4a, of course we must initialize giving the full name, like this:

B4X:
MyListView1.Initialize("MyListView1")

and not :

B4X:
MyListView1.Initialize("")

I will later today try to add other events.

Thanks for this example. This tutorial is really good and you are explaining everything so well.
 

moster67

Expert
Licensed User
Longtime User
sender-object does not work in B4A

Martin,

Based on your great tutorial, I got carried away and implemented some new methods and now I am working also on adding some other events.

One of the few things, I can't get to work is the sender-object which is the object that the user will get with the Sender keyword in B4A.

In onItemClick I am using this to pass on the event to B4a (this works fine for everything else, only when I use Sender it won't work):

B4X:
mBA.raiseEvent(MyListViewAdapter.this, mEventName+"_itemclick", new Object[]{pPosition,dataItem.get("item_title"),dataItem.get("item_description"),dataItem.get("myvalue")});

By reading the forum, the signature for raiseEvent is:

B4X:
public Object raiseEvent(Object sender, String event, Object... params) {

So MyListViewAdapter.this should be the sender-object but it does not work in B4A since it generates a java.lang.ClassCastException as follows:

B4X:
java.lang.ClassCastException: uk.co.martinpearman.b4a.mylistview.arrayadapter.MyListView
   at de.amberhome.quickactionexample.main._mylistview1_itemclick(main.java:420)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:507)
   at anywheresoftware.b4a.BA.raiseEvent2(BA.java:170)
   at anywheresoftware.b4a.BA.raiseEvent2(BA.java:158)
   at anywheresoftware.b4a.BA.raiseEvent(BA.java:154)
   at uk.co.martinpearman.b4a.mylistview.arrayadapter.MyListViewAdapter.onItemClick(MyListViewAdapter.java:123)
   at android.widget.AdapterView.performItemClick(AdapterView.java:284)
   at android.widget.ListView.performItemClick(ListView.java:3513)
   at android.widget.AbsListView$PerformClick.run(AbsListView.java:1812)
   at android.os.Handler.handleCallback(Handler.java:587)
   at android.os.Handler.dispatchMessage(Handler.java:92)
   at android.os.Looper.loop(Looper.java:130)
   at android.app.ActivityThread.main(ActivityThread.java:3683)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:507)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
   at dalvik.system.NativeStart.main(Native Method)
java.lang.ClassCastException: uk.co.martinpearman.b4a.mylistview.arrayadapter.MyListView

Have you seen this before? Any ideas how to resolve?
 
Last edited:

luthepa1

Member
Licensed User
Longtime User
Martin,

Based on your great tutorial, I got carried away and implemented some new methods and now I am working also on adding some other events.

One of the few things, I can't get to work is the sender-object which is the object that the user will get with the Sender keyword in B4A.

In onItemClick I am using this to pass on the event to B4a (this works fine for everything else, only when I use Sender it won't work):

B4X:
mBA.raiseEvent(MyListViewAdapter.this, mEventName+"_itemclick", new Object[]{pPosition,dataItem.get("item_title"),dataItem.get("item_description"),dataItem.get("myvalue")});

By reading the forum, the signature for raiseEvent is:

B4X:
public Object raiseEvent(Object sender, String event, Object... params) {

So MyListViewAdapter.this should be the sender-object but it does not work in B4A since it generates a java.lang.ClassCastException as follows:

B4X:
java.lang.ClassCastException: uk.co.martinpearman.b4a.mylistview.arrayadapter.MyListView
   at de.amberhome.quickactionexample.main._mylistview1_itemclick(main.java:420)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:507)
   at anywheresoftware.b4a.BA.raiseEvent2(BA.java:170)
   at anywheresoftware.b4a.BA.raiseEvent2(BA.java:158)
   at anywheresoftware.b4a.BA.raiseEvent(BA.java:154)
   at uk.co.martinpearman.b4a.mylistview.arrayadapter.MyListViewAdapter.onItemClick(MyListViewAdapter.java:123)
   at android.widget.AdapterView.performItemClick(AdapterView.java:284)
   at android.widget.ListView.performItemClick(ListView.java:3513)
   at android.widget.AbsListView$PerformClick.run(AbsListView.java:1812)
   at android.os.Handler.handleCallback(Handler.java:587)
   at android.os.Handler.dispatchMessage(Handler.java:92)
   at android.os.Looper.loop(Looper.java:130)
   at android.app.ActivityThread.main(ActivityThread.java:3683)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:507)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
   at dalvik.system.NativeStart.main(Native Method)
java.lang.ClassCastException: uk.co.martinpearman.b4a.mylistview.arrayadapter.MyListView

Have you seen this before? Any ideas how to resolve?

This worked for me. Change MyListViewAdapter.this to pParent.
B4X:
mBA.raiseEvent(pParent, mEventName+"_itemclick", new Object[]{pPosition});

Then my test code in B4A was
B4X:
Sub customlistview_ItemClick(Position As Int)
   Dim Send As MyListView
   Send = Sender
   
   Msgbox("OK click event works", "Information")
   
   Send.ClearList
End Sub

FYI - In Activity_Create(FirstTime As Boolean) I initialise with different event name
B4X:
   MyListView1.Initialize("CustomListView")
 

luthepa1

Member
Licensed User
Longtime User
Ok I am a little stumped at current implementation. So far I have added in addition to the OnItemClick the following
OnItemLongClick
OnItemSelected
OnScroll (this give us the top position, visibleItems, TotalItems)

Now I am trying to implement multi select mode but can't seem to get the list view to engage multi-select. I have added get and set methods but it does not seem to toggle selection mode on list view.

So in ListViewWrapper I add
B4X:
   public void setChoiceMode(int Mode) {
      getObject().setChoiceMode(Mode);
   }
   
   public int getChoiceMode() {
      return getObject().getChoiceMode();
   }

And in ListViewAdapter class I added
B4X:
   public void setChoiceMode(int Mode) {
       this.setChoiceMode(Mode);
   }
   
   public int getChoiceMode() {
      return this.getChoiceMode();
   }

   @Override
   public void onItemSelected(AdapterView<?> pParent, View pView, int pPosition, long pId) {
      //mBA.raiseEvent(pParent, mEventName+"_itemselected", new Object[]{pPosition}); //, returnStrings);
      
      MyDataClass dataItem = mMyDataClassList.get(pPosition);
      dataItem.put("selected", true);
      
      Log.d("B4A", mEventName+"_itemselected = "+pPosition);
   }

   @Override
   public void onItemCheckedStateChanged(ActionMode mMode, int mPosition, long mId, boolean mChecked) {
      Log.d("B4A", "Item " + mPosition + " Checked Status: " + mChecked);
      
   }

I have a onItemCheckedStateChanged listener but this irrelevant at this time I believe as for the moment I just want to add method to switch between different selection modes of list view. Any ideas?

Thanks in advance.
 
Top