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

moster67

Expert
Licensed User
Longtime User
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")

Thanks for your reply.

Yes, changing MyListViewAdapter.this to pParent got your example of using Sender working (would not work with MyListViewAdapter.this).

However, I was trying another example using Sender which Erel posted in the AHQuickAction-thread and this is the one which causes the classcastexception. This example of using Sender still crashes even when using pParent.

The code I was to trying to use was the following one:

B4X:
Sub MyListView1_ItemClick(ItemIndex As Int, FirstDescription As String, SecondDescription As String, value As String)
   ac1.Show(GetViewAtPos(Sender,ItemIndex)) '- here I get classcastexception
end sub

Sub GetViewAtPos(LV As ListView, Position As Int) As View
    Dim r As Reflector
    Dim v As View
    r.Target = LV
    Dim first As Int
    first = r.RunMethod("getFirstVisiblePosition")
    v = r.RunMethod2("getChildAt", Position - first, "java.lang.int")
    Return v
End Sub

The problem seems to be Sender because if I modify the code as follows, then everything works:


B4X:
Sub MyListView1_ItemClick(ItemIndex As Int, FirstDescription As String, SecondDescription As String, value As String)
   ac1.Show(GetViewAtPos(ItemIndex)) ' without Sender, everything works and no classcastexception is generated
end sub

Sub GetViewAtPos(Position As Int) As View
    Dim r As Reflector
    Dim v As View
    r.Target = MyListView1 '- assigning MyListView1 directly works so therefore I think it is Sender which causes problems and not Reflection
    Dim first As Int
    first = r.RunMethod("getFirstVisiblePosition")
    v = r.RunMethod2("getChildAt", Position - first, "java.lang.int")
    Return v
End Sub

I am still curious what causes the classcastexception when used with Sender alone (different from your example of using Sender). It might be also that I am using Sender in B4A erroneously although same code works with normal ListView of B4A.
 
Last edited:

luthepa1

Member
Licensed User
Longtime User
That's good to know because in my main app I too am using ahquickaction with the getview method of reflection library. I might take a look at this since I need to rethink my approach on selection mode of custom listview adapter code.

Sent from my GT-I9300 using Tapatalk 2
 

luthepa1

Member
Licensed User
Longtime User
Thanks for your reply.

Yes, changing MyListViewAdapter.this to pParent got your example of using Sender working (would not work with MyListViewAdapter.this).

However, I was trying another example using Sender which Erel posted in the AHQuickAction-thread and this is the one which causes the classcastexception. This example of using Sender still crashes even when using pParent.

The code I was to trying to use was the following one:

B4X:
Sub MyListView1_ItemClick(ItemIndex As Int, FirstDescription As String, SecondDescription As String, value As String)
   ac1.Show(GetViewAtPos(Sender,ItemIndex)) '- here I get classcastexception
end sub

Sub GetViewAtPos(LV As ListView, Position As Int) As View
    Dim r As Reflector
    Dim v As View
    r.Target = LV
    Dim first As Int
    first = r.RunMethod("getFirstVisiblePosition")
    v = r.RunMethod2("getChildAt", Position - first, "java.lang.int")
    Return v
End Sub

The problem seems to be Sender because if I modify the code as follows, then everything works:


B4X:
Sub MyListView1_ItemClick(ItemIndex As Int, FirstDescription As String, SecondDescription As String, value As String)
   ac1.Show(GetViewAtPos(ItemIndex)) ' without Sender, everything works and no classcastexception is generated
end sub

Sub GetViewAtPos(Position As Int) As View
    Dim r As Reflector
    Dim v As View
    r.Target = MyListView1 '- assigning MyListView1 directly works so therefore I think it is Sender which causes problems and not Reflection
    Dim first As Int
    first = r.RunMethod("getFirstVisiblePosition")
    v = r.RunMethod2("getChildAt", Position - first, "java.lang.int")
    Return v
End Sub

I am still curious what causes the classcastexception when used with Sender alone (different from your example of using Sender). It might be also that I am using Sender in B4A erroneously although same code works with normal ListView of B4A.

I have tried many things but I am unable to use the Sender method also, for the AHQuickAction plugin using the GetViewAtPos function that makes use of the Reflectionr library as per the code you posted. I am using the same code in my project as well.

If your ClassCastException error is like the one I see its because our custom list adapter class "MyListView" is not compatible with B4A SimpleListView class or something along those lines. So I changed the list view variable to MyListview types but I am still having no joy :sign0148:

In the library code I will then try pParent or pView as the Sender Object in the raiseEvent function. Then in B4A MyListView long click event I declare a variable as either ListView or MyListView or View (depending what I am sending back in raiseEvent function in library) and then using my variable say is equal to Sender (I think this is needed since in the raiseEvent in Library we are sending an Object). Example
B4X:
   Dim send As ListView
   send = Sender

   
   MyQuickAction.show(GetViewAtPos(send, Position))

And then depending on what type of variable I am passing in GetViewAtPos you need to change the type of the LV variable in GetViewAtPos function (e.g. LV As ListView or LV As View or LV As MyListView).

But no matter what I try I either get ClassCastException or MyListView cannot be cast to anywhere software simplelistview, or endless others.

Currently if I use
B4X:
   Dim send As View
   send = Sender

   
   MyQuickAction.show(GetViewAtPos(send, Position))


Sub GetViewAtPos(LV As View, Position As Int) As View 'Note: LV As View***
    Dim r As Reflector
    Dim v As View
    r.Target = LV
    Dim first As Int
    first = r.RunMethod("getFirstVisiblePosition")
    v = r.RunMethod2("getChildAt", Position - first, "java.lang.int")
    Return v
End Sub

I will get
java.lang.IllegalStateException: setContentView was not called with a view to display.

When passing either pParent or pView in raiseEvent sender object.

I'm stuck! I'm just a "hobby programmer" - LOL.

Love to hear any advice of solution from anyone.
 

warwound

Expert
Licensed User
Longtime User
I think the problem lies with the type of the Sender object.

pParent is not a View type, it is a MyListViewAdapter.
The example code comment is incorrect and should read:

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 MyListViewAdapter 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});
   }

If B4A does not recognise the type of the Sender object then using the B4A Object type is the way to go:

B4X:
Dim send As Object
send=Sender
' now use Reflection on send object

That should work as all types descend from the Java Object class.

A better solution which would do away with the need to use Reflection would be to update the library so that B4A recognises the MyListViewAdapter type and add methods to MyListViewAdapter that do what you want.

First you'd need to update the MyListViewAdapter class so it is not hidden from the B4A IDE:

B4X:
@ActivityObject
@ShortName("MyListViewAdaper")
public class MyListViewAdapter extends ArrayAdapter<MyDataClass> implements OnItemClickListener{
   // code removed
}

Now add new methods to that class:

B4X:
@ActivityObject
@ShortName("MyListViewAdaper")
public class MyListViewAdapter extends ArrayAdapter<MyDataClass> implements OnItemClickListener{

    public void DoSomething(){
        // do something

    }

}

And in B4A you can now Dim a MyListViewAdapter and call it's public methods:

B4X:
Dim send As MyListViewAdapter
send=Sender
send.DoSomeThing

The same applies if you want to use the MyListView or MyDataClass types in B4A - remove the @Hide attribute and add an @ShortName attribute and then add public methods to the class to do as you want.

It all depends upon what you want to do.
If you want to show a QuickAction on the clicked item i'd have thought this would work:

MyListViewWrapper:

B4X:
@Events(values = { "ItemClick(ItemIndex As Int, ClickedItem As View)" })

MyListViewAdapter:

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 MyListViewAdapter 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.
       */
      mBA.raiseEvent(MyListViewAdapter.this, mEventName+"_itemclick", new Object[]{pPosition, pView});
   }

In B4A:

B4X:
Sub MyListView1_ItemClick(ItemIndex As Int, ClickedItem As View)
    MyQuickAction.show(ClickedItem)
End Sub

No need to use the Sender when you can simply pass the required object as a parameter.

Martin.
 

luthepa1

Member
Licensed User
Longtime User
Thanks Martin for spending the time to read the issues I am having.

Unfortunately still no joy. I have change library code back to the way it was originally (e.g raiseEvent sender object is now back to MyListViewAdapter.this).

If B4A does not recognise the type of the Sender object then using the B4A Object type is the way to go:

Code:
Dim send As Object
send=Sender
' now use Reflection on send object

Tried this but now I get
java.lang.ClassCastException: drp.mylistview.arrayadapter.MyListViewAdapter cannot be cast to anywheresoftware.b4a.objects.ListViewWrapper$SimpleListView
I then tried using this solution of dimming Send as ListView/MyListView/view all of which have IllegalStateException Errors of setContent was not called with view to display, or ClassCastExceptions errors or NoSuchMethodException getFirstVisiblePosition depending on what type I am using (NOTE: I also change the LV variable type to match my send variable type as well as now trying send = ClickedItem instead of sender since that did not work).

Now to try next suggestion
If you want to show a QuickAction on the clicked item i'd have thought this would work:

MyListViewWrapper:

Code:
@Events(values = { "ItemClick(ItemIndex As Int, ClickedItem As View)" })
MyListViewAdapter:

Code:
@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 MyListViewAdapter 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.
*/
mBA.raiseEvent(MyListViewAdapter.this, mEventName+"_itemclick", new Object[]{pPosition, pView});
}
In B4A:

Code:
Sub MyListView1_ItemClick(ItemIndex As Int, ClickedItem As View)
MyQuickAction.show(ClickedItem)
End Sub
No need to use the Sender when you can simply pass the required object as a parameter.

I had tried this also but when I pass the View directly to MyQuickAction.Show() I now get the error
java.lang.IllegalStateException: setContentView was not called with a view to display.
Weird!?? I am starting to wonder if the issue lies in the AHQuickAction library (no offence to author).


I guess what I need to do is with the first attempt that I tried above is I need the library MyListViewAdapter class to return the MyListView object in raiseEvent instead of MyListViewAdapter.this. I am not sure if this is possible or not as I am still learning about Android/Java?




The only way forward that I can see now is to try your other suggestion of unhiding the classes in the library and adding methods to execute directly from B4A.

back to the grind....

Thanks again Martin for your help so far

Paul.
 
Last edited:

warwound

Expert
Licensed User
Longtime User
Did you update the MyListViewWrapper @Events attribute:

B4X:
@Events(values = { "ItemClick(ItemIndex As Int, ClickedItem As View)" })

What's your MyListViewAdapter onItemClick method look like?

ClickedItem (if this all works) should be the LinearLayout created from my_list_view_item.xml, a LinearLayout derives from ViewGroup which derives from View so i would have thought you could use pass that LinearLayout object to QuickAction's show() method.

What is the contents of your my_list_view_item.xml file?
Maybe you'd have more success passing the show() method a View that is a child of the LinearLayout instead of the LinearLayout itself?

Martin.
 

luthepa1

Member
Licensed User
Longtime User
Yeah not a bad point at all about passing a child view of my layout. Shall tie that a try. In the mean time here are my codes.

The wrapper class
B4X:
@Events(values = {"ItemClick(Position As Int, Value As Object)", 
              "ItemLongClick(Position As Int, ClickedItem As Object, Value As Object)",
              "OnScroll(firstVisibleItem As Int, visibleItemCount As Int, totalItemCount As Int)"})
@ShortName("MyListView")
@Version(1.2f)
public class MyListViewWrapper extends ViewWrapper<MyListView> {
   
   public void AddItem(String Title, String Description, String SessionCount, Bitmap ListThumbnailImage, Bitmap ListStatusImage){
      MyDataClass myDataClass=new MyDataClass();
      myDataClass.put("title", Title);
      myDataClass.put("description", Description);
      myDataClass.put("session_count", SessionCount);
      myDataClass.put("list_image", ListThumbnailImage);
      myDataClass.put("status", ListStatusImage);
      getObject().addItem(myDataClass);
   }
NOTE: I set ClickedItem as Object because if I state View I keep getting signature mismatch error at run time of app. In fact I always find this is the case with anything extra than Position As Int. I was going to try and find a more suitable post on this issue as to why I anything other than Position I can only declare as Object. Maybe this is my issue to everything? - lol.

Then the added listeners of MyListView class
B4X:
      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);
      this.setOnItemLongClickListener(mMyListViewAdapter);
      this.setOnItemSelectedListener(mMyListViewAdapter);
      this.setOnScrollListener(mMyListViewAdapter);
      //this.setMultiChoiceModeListener(mMyListViewAdapter);

      //   FastScroll hard coded to true
      this.setFastScrollEnabled(true);
      
   }

I will just provide all of MyListViewAdapter class
B4X:
@ActivityObject
@ShortName("MyListViewAdapter")
public class MyListViewAdapter extends ArrayAdapter<MyDataClass> implements OnItemClickListener, 
                                                         OnItemLongClickListener, 
                                                         OnItemSelectedListener,
                                                         OnScrollListener,
                                                         MultiChoiceModeListener{
   
   /*
    * 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 MyListView MyListView;
   private BA mBA;
   private String mEventName;
   private int mItemLayoutResourceId;
   private LayoutInflater mLayoutInflater;
   private List<MyDataClass> mMyDataClassList;
   private String[] mTextViewIds;
   private String[] mImageViewIds;
   
   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[]{"title", "description", "session_count"};
      mImageViewIds=new String[]{"list_image", "status"};
   }
   
   /*
    * 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) {
         pConvertView = mLayoutInflater.inflate(mItemLayoutResourceId, null);
         Log.i("B4A", "New ListItem created");
      } else {
         Log.i("B4A", "Existing ListItem recycled");
      }
      MyDataClass dataItem = mMyDataClassList.get(pPosition);
      if (dataItem != null) {
         TextView textView;
         ImageView imageView;
         for(int i=0, j=mTextViewIds.length; i<j; i++){
            textView=(TextView)findViewById(pConvertView, mTextViewIds[i]);
            textView.setText((String) dataItem.get(mTextViewIds[i]));
         }
         for(int i=0, j=mImageViewIds.length; i<j; i++){
            imageView=(ImageView)findViewById(pConvertView, mImageViewIds[i]);
            //imageView.setDrawingCacheEnabled(true);
            //imageView.layout(0, 0, imageView.getWidth(), imageView.getWidth());
            //imageView.buildDrawingCache();
            imageView.setImageBitmap((Bitmap)dataItem.get(mImageViewIds[i]));
            //imageView.draw((Canvas) dataItem.get(mBitmapViewIds[i]));
            //imageView.setDrawingCacheEnabled(false);
         }
      }
      /*
       * 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 MyListViewAdapter 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);

      String returnStrings[] = {(String) dataItem.get("title"), (String) dataItem.get("description"), (String) dataItem.get("session_count")};
      
      mBA.raiseEvent(MyListViewAdapter.this, mEventName+"_itemclick", new Object[]{pPosition, returnStrings});
      Log.d("B4A", mEventName+"_itemclick");
   }

   @Override
   public boolean onItemLongClick(AdapterView<?> pParent, View pView, int pPosition, long pId) {
      MyDataClass dataItem = mMyDataClassList.get(pPosition);
      String returnStrings[] = {(String) dataItem.get("title"), (String) dataItem.get("description"), (String) dataItem.get("session_count")};
      
      mBA.raiseEvent(MyListViewAdapter.this, mEventName+"_itemlongclick", new Object[]{pPosition, pView, returnStrings});
      Log.d("B4A", mEventName+"_itemlongclick -- " + pParent);
      
      return true;      
   }
   
   @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 onNothingSelected(AdapterView<?>  pParent)
    {

    }

    @Override
   public void onScroll(AbsListView mParent, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
      mBA.raiseEvent(mParent, mEventName+"_onscroll", new Object[]{firstVisibleItem, visibleItemCount, totalItemCount});
      //Log.d("B4A", Integer.toString(firstVisibleItem));
   }

   @Override
   public void onScrollStateChanged(AbsListView mView, int scrollState) {
      // TODO Auto-generated method stub
      
   }

   @Override
   public boolean onActionItemClicked(ActionMode arg0, MenuItem arg1) {
      // TODO Auto-generated method stub
      return false;
   }

   @Override
   public boolean onCreateActionMode(ActionMode arg0, Menu arg1) {
      // TODO Auto-generated method stub
      return false;
   }

   @Override
   public void onDestroyActionMode(ActionMode arg0) {
      // TODO Auto-generated method stub
      
   }

   @Override
   public boolean onPrepareActionMode(ActionMode arg0, Menu arg1) {
      // TODO Auto-generated method stub
      return false;
   }

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

And that's pretty much the majority of code changes. And now the XML layout
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="Main Title Heading..."
        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="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>
 

warwound

Expert
Licensed User
Longtime User
Try this:

MyListViewAdapter:

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 MyListViewAdapter 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")+"'");

      ViewGroup viewGroup=(ViewGroup) pView;

      //   mBA.raiseEvent(MyListViewAdapter.this, mEventName+"_itemclick", new Object[]{pPosition, viewGroup.getChildAt(0)});
      mBA.raiseEvent(viewGroup, mEventName+"_itemclick", new Object[]{pPosition});
      //   mBA.raiseEvent(pView, mEventName+"_itemclick", new Object[]{pPosition, viewGroup.getChildAt(1)});
      //   mBA.raiseEvent(viewGroup.getChildAt(0), mEventName+"_itemclick", new Object[]{pPosition, viewGroup.getChildAt(1)});
   }

And in B4A:

B4X:
Sub MyListView1_ItemClick(ItemIndex As Int)
   Log("MyListView1_ItemClick")
   Dim v As View
   v=Sender
   ac1.show(v)
End Sub

ac1 is an AHQuickAction object.
It works and the QuickAction menu appears and is anchored to the list item.

Casting the LinearLayout pView to a ViewGroup may be unnecessary as this also worked for me:

B4X:
mBA.raiseEvent(pView, mEventName+"_itemclick", new Object[]{pPosition});

If you uncomment the commented lines you'll see that you can open the QuickAction anchored to either of the two TextViews that are children of the LinearLayout.
(Remember to update the @Events attribute of you change the parameters passed.

My list item XML is:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent" 
   android:layout_height="wrap_content"
   android:orientation="vertical" 
   android:paddingTop="5dip"
   android:paddingBottom="5dip"
   android:paddingLeft="5dip" 
   android:paddingRight="5dip">
   <TextView
      android:layout_height="wrap_content"
      android:layout_width="fill_parent"
      android:id="@+id/item_title"
      android:text="" 
      android:textSize="16dip">
   </TextView>
   <TextView
      android:layout_height="wrap_content"
      android:layout_width="fill_parent" 
      android:id="@+id/item_description"
      android:text="" 
      android:textSize="14dip">
   </TextView>
</LinearLayout>

I was thinking that you could add a plain View to a layout - the View would contain no content but your could pass it to B4A as the QuickAction anchor View.
Perhaps by using a RelativeLayout instead of a LinearLayout you could position a small View in your list item layout to control where the QuickAction menu opens?

B4A and library code attached.

Martin.
 

Attachments

  • MyListView_QuickAction.zip
    94.5 KB · Views: 737

luthepa1

Member
Licensed User
Longtime User
Good news! Finally sorted the issue!

Long story short, I had not initialised the QuickActions object component. Once again slapping my self!!!!! I had declared it which I guess is why I could get so far before experiencing runtime errors. Shame it took me so long to find and realise my mistake. The code for populating the list view and initialising and setup of the QuickActions object I had in their own sub routines. Had forgot to call sub to setup QuickAction.

I knew something was up when I tried your updated demo app with QuickAction component and found it to work without fault. I then tried you code suggestions in my demo app and still found no joy.

So today I used your latest demo app with QA and modified it to make use of my version of the MyListView library and found it to work! YAY! And I also quickly ruled out any issues to do with my xml layout. YAY again!

So all sorted. Sorry to have wasted your time although I kind of see it as the opposite as your help and guidance as taught me a lot about building and interfacing custom libraries with B4A. I had not found much info on libraries besides the simple tutes by Erel to which there seems to be good reason. And what you showed me on how to unhide the classes and add methods that I could execute from within B4A gives me ideas on how to tackle my next task of toggling selection mode on the list view .

So again, thanks Martin for all your help. Very much appreciated and hopefully in the not too distant future I can give back to the community ;-)

Paul.
 
Last edited:

moster67

Expert
Licensed User
Longtime User
@luthepa1

glad you got it sorted.

I haven't had time these days to try further. I will hopefully do so this evening.
 

hypergreatthing

Member
Licensed User
Longtime User
I've got a question regarding the custom listview.

I got the sample all up and running. Started mucking around with it.

My goal is to basically add a group to the layout for a questionnaire of sorts. Basically a header of a group title, then individual questions for that particular group.

So i played around with it, developed a layout that used a LinearLayout then a nested RelativeLayout. Got it running. But each time the additem is called (modified it to accept 4 parameters) it creates both a new Linear and Relative item.

What i'd like to do is to create only a new LinearLayout when the group name changes. That or create a function that will initialize the new group and another function for adding the questions below that group will work as well.

the second issue is that with the nested layouts the events stopped working. I'll look at the previous posts and see if i can figure that out.

I can post my new layout if that will help.
 

hypergreatthing

Member
Licensed User
Longtime User
Look back to this post: http://www.b4x.com/forum/basic4andr...17708-custom-listview-library.html#post101691



Your new layout contains items that are focusable perhaps?

Martin.
thanks for the insight on the events. I'll look into that.

Any idea on how to create a child layout and add it to a parent layout?

Basically it's something like this:

[Linear Layout]
Group Title
[Relative Layout]
Question Test 1 - description - button

Right now when it does the additem, it puts something like this:
Group 1
Question 1
Group 1
Question 2

When i want something like:
Group 1
Question 1
Question 2
etc...
Group 2
Question X
 

warwound

Expert
Licensed User
Longtime User
Hi again.

So your ListView item layout needs a variable number of Questions for each Group - or does every Group contain the same number of Questions?

The obvious solution - whether each Group contains the same number of questions or not - is to create an ExpandableListView B4A library instead of a ListView library.

Have a look at some screengrabs of the ExpandableListView: https://www.google.co.uk/search?q=a...g&biw=1920&bih=930&sei=dLckUL7OJqSf0QWeuYG4Cg

Does that look like it'd work better for you than a ListView?

desolatesoul has created an example here, it's not a java library - it's all done using B4A code.

I think to help you better you need to provide more detail about what you're trying to acheive.

If every Group has fixed number of Questions you could probably still use a the existing ListView example but would need to update the MyListViewWrapper.AddItem() and MyListViewAdapter.getView() methods.

Look at the MyDataClass.
Say each list item is a group title and a fixed number of questions.

B4X:
public void AddItem(String GroupTitle, String[] Questions){
   MyDataClass myDataClass=new MyDataClass();
   myDataClass.put("item_title", GroupTitle);
   myDataClass.put("item_questions", Questions);
   getObject().addItem(myDataClass);
}

That's from the MyListViewWrapper class, so in your B4A code you could call AddItem with a Group title and an array of Strings - each array element being a question.

You'd need to create an XML layout with a View to display the Group title and then a fixed number of other Views to display the Questions.

Have you seen DroidDraw?

WYSIWYG User Interface (UI) designer/editor/builder for cell phone and tablet application programming on the Android Platform

It's not perfect but you'll probably find it useful.

So you have an updated AddItem() method and a new XML layout, next you need to update the adapter getView() method.

B4X:
@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) {
      /* here you will have your MyDataClass instance which will have two properties: 'item_title' and 'item_questions'
      setting the pConvertView text fields will depend on what ids you have given them in the XML layout
      you should be able to easily set the item_title and then use a for statement to set the questions text (iterate through the 'item_questions' array)
      */
   }
   /*
    * 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;
}

That's all assuming each Group has the same number of Questions.
If the number of Questions in each group varies then using a ListView is going to be tricky - the ListView is designed so that each ListView item uses the same basic layout.
It's not suitable where each ListView item potentially requires a different layout - that's where an ExpandableListView would be much more suitable.

Martin.
 

hypergreatthing

Member
Licensed User
Longtime User
Thank you for your response. I think expandablelistview is more or less what i would like to do since the questions and categories will be dynamic and database driven. The number of groups and the questions per each group will change. The expandable part/collapsible part of the expandlistview isn't needed/wanted though (the arrow that allows you to collapse/expand them). And i really do like your custom library with the AHQuickActions. It makes it really neat/professional.

I did use DroidDraw to create my layout
B4X:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:orientation="vertical"
   xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
   android:id="@+id/item_group"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="item_group"
   android:textSize="16sp" />
<RelativeLayout
   android:id="@+id/widget125"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:paddingLeft="15dp">
<TextView
   android:id="@+id/item_title"
   android:layout_width="375dp"
   android:layout_height="fill_parent"
   android:text="item_title"
   android:layout_alignParentTop="true"
   android:layout_alignParentLeft="true" />
<TextView
   android:id="@+id/item_description"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:text="item_description"
   android:layout_alignParentTop="true"
   android:layout_toRightOf="@+id/item_title" />
<Button
   android:id="@+id/item_count"
   android:layout_width="50dp"
   android:layout_height="50dp"
   android:text="0"
   android:layout_alignParentTop="true"
   android:layout_alignParentRight="true" />
</RelativeLayout>
</LinearLayout>
That's my layout. The idea was to create multiple relative layouts inside a single linear layout.

I think the addition of the button caused the events to stop triggering. I think changing that to a text field would work for my purposes. I just wanted a counter for how many times a particular question was selected.

Thank you so much. I think you've helped me figure out how to approach this.
 

Informatix

Expert
Licensed User
Longtime User
I've got a question regarding the custom listview.

I got the sample all up and running. Started mucking around with it.

My goal is to basically add a group to the layout for a questionnaire of sorts. Basically a header of a group title, then individual questions for that particular group.

So i played around with it, developed a layout that used a LinearLayout then a nested RelativeLayout. Got it running. But each time the additem is called (modified it to accept 4 parameters) it creates both a new Linear and Relative item.

What i'd like to do is to create only a new LinearLayout when the group name changes. That or create a function that will initialize the new group and another function for adding the questions below that group will work as well.

the second issue is that with the nested layouts the events stopped working. I'll look at the previous posts and see if i can figure that out.

I can post my new layout if that will help.

Maybe this class can help you: CheckList
 

Sinan Tuzcu

Well-Known Member
Licensed User
Longtime User
Helo,

it is possible to add an image as ListView1.AddTwoLinesAndBitmap for User Picture?

and

is it possible to add in the listview balloon like Whatsapp chat?
and the text in balloon ?

gruß
sinan
 
Last edited:

warwound

Expert
Licensed User
Longtime User
Hi.

Yes that sounds possible.

First step would be to edit the XML layout file that you use for your ListView items and add an Android ImageView, be sure to give this ImageView an id attribute.

Update the MyListViewWrapper AddItem() method so that it accepts a Bitmap as well as the item title and description, adding the Bitmap to the instance of MyDataClass along with the title and description values.

Then you need to update MyListViewAdapter so that it creates a reference to the ImageView id just as it creates references to the TextView ids (mTextViewIds).

In the adapter class you then need to update the getView() method.
After the TextViews have had their text values set you'd want to use findViewById() to find the ImageView (in pConvertView) and set an image. The image would be obtained from the instance of MyDataClass.

I think that outlines the steps required - it's tricky to say the exact steps required without seeing your source code.

So i'll leave it to you to try and modify your source and if you need more help then you will need to upload your source and then i'll give you some detailed instructions.

Martin.
 

Sinan Tuzcu

Well-Known Member
Licensed User
Longtime User
Hello Martin,

I am a newbie in B4A :)

Can you create an example for me?

Her my code, to add text and Image in Listview:
B4X:
Sub LogTable1

Dim Cursor1 As Cursor
Dim Spalte(5) As String
Dim Cursor2 As Cursor
Dim Cursor3 As Cursor
Dim Cep As String
Dim NickName As String
Dim lvd As ListViewData

ListView1.Clear
ListView1.Initialize("ListView1")
ListView1.TwoLinesAndBitmap.ImageView.Height = 55dip
ListView1.TwoLinesAndBitmap.ImageView.Width = 55dip
ListView1.TwoLinesAndBitmap.ItemHeight = 65dip
ListView1.TwoLinesAndBitmap.Label.TextSize = 20
ListView1.TwoLinesAndBitmap.Label.Left = 80dip
ListView1.TwoLinesAndBitmap.SecondLabel.TextSize = 13
ListView1.TwoLinesAndBitmap.SecondLabel.Left = 80dip
ListView1.TwoLinesAndBitmap.Label.TextColor = Colors.Black
ListView1.TwoLinesAndBitmap.SecondLabel.TextColor=Colors.DarkGray
ListView1.TwoLinesAndBitmap.Label.Gravity = Gravity.LEFT
ListView1.TwoLinesAndBitmap.Label.Typeface = Typeface.DEFAULT_BOLD
ListView1.ScrollingBackgroundColor = Colors.Transparent
ListView1.FastScrollEnabled = True
ListView1.Color=Colors.RGB(255,255,255)


Cursor1 = SQL1.ExecQuery("SELECT UserID, UserName, mesaj, LinkAdres, Datum FROM " & Cep)
               
         lvd.Initialize
         Cursor1.Position = Cursor1.RowCount - 1
         lvd.ErsteSpalte = Cursor1.GetString("UserID") 
             Spalte(1) = Cursor1.GetString("UserName")
           Spalte(2) = Cursor1.GetString("Mesaj")
           Spalte(3) = Cursor1.GetString("LinkAdres")
           Spalte(4)= Cursor1.GetString("Datum")   


 If File.Exists(codlar.ProfilResimYeri,lvd.ErsteSpalte & ".png") = True Then ListView1.AddTwoLinesAndBitmap2(NickName,Spalte(2) & "          " & Spalte(4), LoadBitmap(codlar.ProfilResimYeri, lvd.ErsteSpalte & ".png"),lvd)   
         Else
ListView1.AddTwoLinesAndBitmap2(NickName,Spalte(2) & "          " & Spalte(4),LoadBitmap(File.DirAssets, "appicon.png"),lvd)
         End If
Cursor1.Close

Activity.AddView(ListView1, 0, 0, 100%x, 100%y)
ListView1.BringToFront

End Sub


Listview1 Get the Text from Local SQlite Database
 

Informatix

Expert
Licensed User
Longtime User
It seems that Warwound and you are talking of two different listviews. You can't do what you want with the standard B4A ListView.

EDIT:
Explanations: the standard B4A listview can display only two different pictures. One in the background with TwoLinesAndBitmap.Background and one in the ImageView with TwoLinesAndBitmap.ImageView.Bitmap. You can display your balloon in the background, but it will have always the same orientation. You can't have one with a color and an arrow to the left, and another with a different color and an arrow pointing to the right. That's why you have to use another class to do it. Suggestions: CustomListView (the class, not this lib), CheckList, UltimateListView. You can also do it with the lib in this thread (I presume) but you need java programming skills.
 
Last edited:
Top