B4A Library [Lib] UltimateListView

I've been working on this project for a long time and I'm very proud to release the version 4 today.

The UltimateListView is, as its pompous name says, THE ListView.

  • It can handle very long lists. This is a screenshot of a list with 245813 items, all different:

    verylonglist.jpg


  • It can mix different layouts (and they can be changed dynamically). You can use it as an expandable ListView:

    layouts.jpg


  • It has a low memory footprint and is very fast (this report comes from the Performance demo where the list has to display 128901 distinct words read from a database and the used device is a Huawei Honor single core 1.4 Ghz):

    performance.png


  • It can scroll in both directions thanks to its swipe detector:

    tables.jpg


  • The swipe detector can also be used to implement a swipe-to-dismiss or a swipe-to-reveal:

    swipedetector.png
  • You can easily add editors to your table to change its content:

    celledit.jpg


  • You can animate the items when they are added, removed, replaced or when the list is scrolled (with your own custom animation):

    animationclap.png


  • It can stack items from the bottom:

    stackfrombottom.png


  • It supports drag & drop operations (internal & external):

    dragndrop.png


  • You can synchronize lists with different item heights:

    grid.jpg
The examples will show you how to implement a Pull-to-Refresh, create sticky headers or combine several lists to make a wheel. One of the examples is an improved version of my File Explorer class.

All texts and images can be loaded asynchronously (from Internet, from a database or from a local folder), so you can scroll even if the data are not fully loaded.

The list has its own state manager.

Since September 2018, ULV is available for free. You can still donate for it if you wish.
To send the money, just click on the Donate button below (the amount to enter is in euros):


Note that UltimateListView is not a wrapper around the work of someone else. It is 100% my own code and it is based upon the standard Java ListView of Android.

The UltimateListView does not work with Android versions < 2. It cannot work with B4J or B4i.

Current version: 4.50

DOWNLOAD HERE:
 
Last edited:

MarcoRome

Expert
Licensed User
Longtime User
Hi Informatix.
I have this code, but i dont understand why ULV_ItemClick dont work

B4X:
Sub ULV_ItemClick(ItemID As Long, Position As Int, ClickedPanel As Panel)
    Log(Position)
End Sub

This is code:
B4X:
Sub Process_Globals
    Dim dbSQL As SQL
    Dim su As StringUtils
    Dim Padding As Int = 10dip
    Dim FontSize As Int = 18
End Sub

Sub Globals
    Dim ULV As UltimateListView
    Dim dbCursor As Cursor
    Dim bmpAndroid As Bitmap
    Dim lblMeasure As Label
    Dim HoloDark As Boolean
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Duplicates and opens the database
    If Not(File.Exists(File.DirInternal, "wikipedimo.db")) Then
        File.Copy(File.DirAssets, "wikipedimo.db", File.DirInternal, "wikipedimo.db")
    End If
    dbSQL.Initialize(File.DirInternal, "wikipedimo.db", False)

    'Gets a cursor on data
    dbCursor = dbSQL.ExecQuery("SELECT * FROM Android")

    'Creates the invisible label used to measure the height of each item
    lblMeasure.Initialize("")
    lblMeasure.TextSize = FontSize
    lblMeasure.Typeface = Typeface.DEFAULT
    lblMeasure.Visible = False
    Activity.AddView(lblMeasure, Padding, Padding, 100%x - (Padding * 2), 100%y - (Padding * 2))

    'Initializes the ULV
    ULV.Initialize(0, 0, "", "")
    Activity.AddView(ULV, 0, 0, 100%x, 100%y)


        ULV.SetStyle(ULV.STYLE_HOLO_DARK)
        HoloDark = True


    'Loads the Android image and adds the layout for this image
    bmpAndroid = LoadBitmap(File.DirAssets, "voice_memos.png")
    Dim Height As Int = (bmpAndroid.Height * Density) + (Padding * 2)

    For i = 0 To dbCursor.RowCount - 1
        'Each item has a different content, with a different height. For each new height, a layout is created.
        dbCursor.Position = i
        Height = su.MeasureMultilineTextHeight(lblMeasure, dbCursor.GetString("Text")) + (Padding * 2)
        If Not(ULV.LayoutExists("TEXT_" & Height)) Then
            ULV.AddLayout("TEXT_" & Height, "Item_LayoutCreator", "Item_ContentFiller", Height + 60dip,True)
        End If
        ULV.AddItem("TEXT_" & Height, i)       
    Next



End Sub




Sub Activity_Resume
End Sub

Sub Activity_Pause(UserClosed As Boolean)
    'When the user quits, the database is deleted
    If UserClosed And File.Exists(File.DirInternal, "wikipedimo.db") Then
        File.Delete(File.DirInternal, "wikipedimo.db")
    End If

    'Number of layouts used in this example
    If ULV.IsInitialized Then Log("Nb. of layouts = " & ULV.NumberOfLayouts)
End Sub

Sub PanelWithWhiteGradient(pnl As Panel)
    'Creates a panel with a gradient background (from white to very light gray)
    Dim gd As GradientDrawable
    Dim Couleurs(2) As Int
    If HoloDark Then
        Couleurs(0) = Colors.RGB(30, 30, 30)
        Couleurs(1) = Colors.Black
    Else
        Couleurs(0) = Colors.White
        Couleurs(1) = Colors.RGB(240, 240, 240)
    End If
    gd.Initialize("TOP_BOTTOM", Couleurs)
    pnl.Background = gd
End Sub

Sub Item_LayoutCreator(LayoutName As String, LayoutPanel As Panel)
    PanelWithWhiteGradient(LayoutPanel)

    'Very simple layout: just an ImageView
    Dim IV As ImageView
    IV.Initialize("")
    IV.Gravity = Gravity.LEFT
    LayoutPanel.AddView(IV, 0, 0, 100%x, 60dip)
   
    'Aggiungo una label affianco ad immagine
    Dim lbl0 As Label
    lbl0.Initialize("")
    lbl0.Color = Colors.Transparent
    'If HoloDark Then
        lbl0.TextColor = Colors.White
    'Else
    '    lbl.TextColor = Colors.Black
    'End If
    lbl0.TextSize = FontSize
    lbl0.Typeface = Typeface.DEFAULT
    lbl0.Gravity = Gravity.TOP
    LayoutPanel.AddView(lbl0, 95dip, 5dip, LayoutPanel.Width - 100dip, 60dip)
   
    'Aggiungo una label affianco ad immagine
    Dim lbl1 As Label
    lbl1.Initialize("")
    lbl1.Color = Colors.Transparent
    'If HoloDark Then
        lbl1.TextColor = Colors.White
    'Else
    '    lbl.TextColor = Colors.Black
    'End If
    lbl1.TextSize = FontSize
    lbl1.Typeface = Typeface.DEFAULT
    lbl1.Gravity = Gravity.TOP
    LayoutPanel.AddView(lbl1, 95dip, 25dip, LayoutPanel.Width - 100dip, 60dip)
   
    'Panel come linea
    Dim linea As Panel
    linea.Initialize("")
    LayoutPanel.AddView(linea, 0, 65dip, LayoutPanel.Width, 1dip)
   


   

    'Very simple layout: just a label
    Dim lbl As Label
    lbl.Initialize("")
    lbl.Color = Colors.Transparent
    'If HoloDark Then
        lbl.TextColor = Colors.White
    'Else
    '    lbl.TextColor = Colors.Black
    'End If
    lbl.TextSize = FontSize
    lbl.Typeface = Typeface.DEFAULT
    lbl.Gravity = Gravity.TOP
    'LayoutPanel.AddView(lbl, Padding, Padding, 100%x - (Padding * 2), LayoutPanel.Height - (Padding * 2))
    LayoutPanel.AddView(lbl, Padding, 70dip, 100%x - (Padding * 2), LayoutPanel.Height - (Padding * 2))
    'LayoutPanel.AddView(lbl, Padding, IV.Height, 100%x - (Padding * 2), LayoutPanel.Height - (Padding * 2) + IV.Height)

        'Panel come linea
    Dim linea1 As Panel
    linea1.Initialize("")
    LayoutPanel.AddView(linea1, 0, lbl.Height + 10dip, LayoutPanel.Width, 1dip)
   
End Sub

Sub Item_ContentFiller(ItemID As Long, LayoutName As String, LayoutPanel As Panel, Position As Int)
    'Inserisci Immagine
    Dim IV As ImageView = LayoutPanel.GetView(0)
    IV.Bitmap = bmpAndroid
   
    'Reads the data and sets the label text
    Dim lbl0 As Label = LayoutPanel.GetView(1)
    'dbCursor.Position = Position - 1
    dbCursor.Position = Position
    lbl0.Text = dbCursor.GetString("altro")
   
    'Reads the data and sets the label text
    Dim lbl1 As Label = LayoutPanel.GetView(2)
    'dbCursor.Position = Position - 1
    dbCursor.Position = Position
    lbl1.Text = "ore: 434434 - Messaggio"
   
    'Linea
'    Dim linea As Panel =  LayoutPanel.GetView(3)
'    linea.Color = Colors.Red
   
   
    'Reads the data and sets the label text
    Dim lbl As Label = LayoutPanel.GetView(4)
    'dbCursor.Position = Position - 1
    dbCursor.Position = Position
    lbl.Text = dbCursor.GetString("Text")
   
    'Linea 2
    Dim linea As Panel =  LayoutPanel.GetView(5)
    linea.Color = Colors.Red
   
End Sub



Sub ULV_CellClick(RowID As Long, CellIndex As Byte, Position As Int, ClickedPanel As Panel)
    'Shows a MsgBox with some data of the clicked cell
    dbCursor.Position = RowID
    Msgbox("Test=" & dbCursor.GetString("altro"), "Click")
End Sub

Sub ULV_ItemClick(ItemID As Long, Position As Int, ClickedPanel As Panel)
    Log(Position)
End Sub

In attachment source.

Where i wrong ?

Thank you
Marco
 

Attachments

  • MioTestULV.zip
    21 KB · Views: 263

Informatix

Expert
Licensed User
Longtime User
Hi Informatix.
I have this code, but i dont understand why ULV_ItemClick dont work

B4X:
Sub ULV_ItemClick(ItemID As Long, Position As Int, ClickedPanel As Panel)
    Log(Position)
End Sub

This is code:
B4X:
Sub Process_Globals
    Dim dbSQL As SQL
    Dim su As StringUtils
    Dim Padding As Int = 10dip
    Dim FontSize As Int = 18
End Sub

Sub Globals
    Dim ULV As UltimateListView
    Dim dbCursor As Cursor
    Dim bmpAndroid As Bitmap
    Dim lblMeasure As Label
    Dim HoloDark As Boolean
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Duplicates and opens the database
    If Not(File.Exists(File.DirInternal, "wikipedimo.db")) Then
        File.Copy(File.DirAssets, "wikipedimo.db", File.DirInternal, "wikipedimo.db")
    End If
    dbSQL.Initialize(File.DirInternal, "wikipedimo.db", False)

    'Gets a cursor on data
    dbCursor = dbSQL.ExecQuery("SELECT * FROM Android")

    'Creates the invisible label used to measure the height of each item
    lblMeasure.Initialize("")
    lblMeasure.TextSize = FontSize
    lblMeasure.Typeface = Typeface.DEFAULT
    lblMeasure.Visible = False
    Activity.AddView(lblMeasure, Padding, Padding, 100%x - (Padding * 2), 100%y - (Padding * 2))

    'Initializes the ULV
    ULV.Initialize(0, 0, "", "")
    Activity.AddView(ULV, 0, 0, 100%x, 100%y)


        ULV.SetStyle(ULV.STYLE_HOLO_DARK)
        HoloDark = True


    'Loads the Android image and adds the layout for this image
    bmpAndroid = LoadBitmap(File.DirAssets, "voice_memos.png")
    Dim Height As Int = (bmpAndroid.Height * Density) + (Padding * 2)

    For i = 0 To dbCursor.RowCount - 1
        'Each item has a different content, with a different height. For each new height, a layout is created.
        dbCursor.Position = i
        Height = su.MeasureMultilineTextHeight(lblMeasure, dbCursor.GetString("Text")) + (Padding * 2)
        If Not(ULV.LayoutExists("TEXT_" & Height)) Then
            ULV.AddLayout("TEXT_" & Height, "Item_LayoutCreator", "Item_ContentFiller", Height + 60dip,True)
        End If
        ULV.AddItem("TEXT_" & Height, i)      
    Next



End Sub




Sub Activity_Resume
End Sub

Sub Activity_Pause(UserClosed As Boolean)
    'When the user quits, the database is deleted
    If UserClosed And File.Exists(File.DirInternal, "wikipedimo.db") Then
        File.Delete(File.DirInternal, "wikipedimo.db")
    End If

    'Number of layouts used in this example
    If ULV.IsInitialized Then Log("Nb. of layouts = " & ULV.NumberOfLayouts)
End Sub

Sub PanelWithWhiteGradient(pnl As Panel)
    'Creates a panel with a gradient background (from white to very light gray)
    Dim gd As GradientDrawable
    Dim Couleurs(2) As Int
    If HoloDark Then
        Couleurs(0) = Colors.RGB(30, 30, 30)
        Couleurs(1) = Colors.Black
    Else
        Couleurs(0) = Colors.White
        Couleurs(1) = Colors.RGB(240, 240, 240)
    End If
    gd.Initialize("TOP_BOTTOM", Couleurs)
    pnl.Background = gd
End Sub

Sub Item_LayoutCreator(LayoutName As String, LayoutPanel As Panel)
    PanelWithWhiteGradient(LayoutPanel)

    'Very simple layout: just an ImageView
    Dim IV As ImageView
    IV.Initialize("")
    IV.Gravity = Gravity.LEFT
    LayoutPanel.AddView(IV, 0, 0, 100%x, 60dip)
  
    'Aggiungo una label affianco ad immagine
    Dim lbl0 As Label
    lbl0.Initialize("")
    lbl0.Color = Colors.Transparent
    'If HoloDark Then
        lbl0.TextColor = Colors.White
    'Else
    '    lbl.TextColor = Colors.Black
    'End If
    lbl0.TextSize = FontSize
    lbl0.Typeface = Typeface.DEFAULT
    lbl0.Gravity = Gravity.TOP
    LayoutPanel.AddView(lbl0, 95dip, 5dip, LayoutPanel.Width - 100dip, 60dip)
  
    'Aggiungo una label affianco ad immagine
    Dim lbl1 As Label
    lbl1.Initialize("")
    lbl1.Color = Colors.Transparent
    'If HoloDark Then
        lbl1.TextColor = Colors.White
    'Else
    '    lbl.TextColor = Colors.Black
    'End If
    lbl1.TextSize = FontSize
    lbl1.Typeface = Typeface.DEFAULT
    lbl1.Gravity = Gravity.TOP
    LayoutPanel.AddView(lbl1, 95dip, 25dip, LayoutPanel.Width - 100dip, 60dip)
  
    'Panel come linea
    Dim linea As Panel
    linea.Initialize("")
    LayoutPanel.AddView(linea, 0, 65dip, LayoutPanel.Width, 1dip)
  


  

    'Very simple layout: just a label
    Dim lbl As Label
    lbl.Initialize("")
    lbl.Color = Colors.Transparent
    'If HoloDark Then
        lbl.TextColor = Colors.White
    'Else
    '    lbl.TextColor = Colors.Black
    'End If
    lbl.TextSize = FontSize
    lbl.Typeface = Typeface.DEFAULT
    lbl.Gravity = Gravity.TOP
    'LayoutPanel.AddView(lbl, Padding, Padding, 100%x - (Padding * 2), LayoutPanel.Height - (Padding * 2))
    LayoutPanel.AddView(lbl, Padding, 70dip, 100%x - (Padding * 2), LayoutPanel.Height - (Padding * 2))
    'LayoutPanel.AddView(lbl, Padding, IV.Height, 100%x - (Padding * 2), LayoutPanel.Height - (Padding * 2) + IV.Height)

        'Panel come linea
    Dim linea1 As Panel
    linea1.Initialize("")
    LayoutPanel.AddView(linea1, 0, lbl.Height + 10dip, LayoutPanel.Width, 1dip)
  
End Sub

Sub Item_ContentFiller(ItemID As Long, LayoutName As String, LayoutPanel As Panel, Position As Int)
    'Inserisci Immagine
    Dim IV As ImageView = LayoutPanel.GetView(0)
    IV.Bitmap = bmpAndroid
  
    'Reads the data and sets the label text
    Dim lbl0 As Label = LayoutPanel.GetView(1)
    'dbCursor.Position = Position - 1
    dbCursor.Position = Position
    lbl0.Text = dbCursor.GetString("altro")
  
    'Reads the data and sets the label text
    Dim lbl1 As Label = LayoutPanel.GetView(2)
    'dbCursor.Position = Position - 1
    dbCursor.Position = Position
    lbl1.Text = "ore: 434434 - Messaggio"
  
    'Linea
'    Dim linea As Panel =  LayoutPanel.GetView(3)
'    linea.Color = Colors.Red
  
  
    'Reads the data and sets the label text
    Dim lbl As Label = LayoutPanel.GetView(4)
    'dbCursor.Position = Position - 1
    dbCursor.Position = Position
    lbl.Text = dbCursor.GetString("Text")
  
    'Linea 2
    Dim linea As Panel =  LayoutPanel.GetView(5)
    linea.Color = Colors.Red
  
End Sub



Sub ULV_CellClick(RowID As Long, CellIndex As Byte, Position As Int, ClickedPanel As Panel)
    'Shows a MsgBox with some data of the clicked cell
    dbCursor.Position = RowID
    Msgbox("Test=" & dbCursor.GetString("altro"), "Click")
End Sub

Sub ULV_ItemClick(ItemID As Long, Position As Int, ClickedPanel As Panel)
    Log(Position)
End Sub

In attachment source.

Where i wrong ?

Thank you
Marco
Support is only done by email from monday to friday.
 

wildfandango

Member
Licensed User
Longtime User
Hi again Informatix.

When i use PreLoadCallback function defined in SetLoadImageCallbacks

  • Sub PreLoad (ImageViewWidth As Int, ImageViewHeight As Int) As Object

Is possible (from inside of PreLoadCallback Sub PreLoad) any way to know the imageid or something similar to identify the actual image in the ULV?

Like always gratz! for your library and thanks in advance for your help...
 

Informatix

Expert
Licensed User
Longtime User
Hi again Informatix.

When i use PreLoadCallback function defined in SetLoadImageCallbacks




    • Sub PreLoad (ImageViewWidth As Int, ImageViewHeight As Int) As Object
Is possible (from inside of PreLoadCallback Sub PreLoad) any way to know the imageid or something similar to identify the actual image in the ULV?

Like always gratz! for your library and thanks in advance for your help...
No, you cannot return a different bitmap according to the ID. The purpose of the Preload callback is to return a generic placeholder.
 

Informatix

Expert
Licensed User
Longtime User
A user asked me an interesting question. He was surprised that changing the file name of an image did not change the image after a call to RefreshContent (and thus a call to his Filler sub). My answer:
When you declare a cache, memory or disk, images are first searched in this cache. The search is done with the ImgID passed to LoadImageAsync. That's why changing the filename has no effect when the ImgID does not change. If you want to keep the same ImgID, you have to remove the copy in cache with RemoveBitmapFromCache before calling RefreshContent.
To let enough time to the Cache manager to delete the files, I suggest to put a DoEvents between RemoveBitmapFromCache and RefreshContent (it's not mandatory, it's just to avoid a possible issue).
 
Last edited:

Rusty

Well-Known Member
Licensed User
Longtime User
I am receiving the following error: Unexpected event (missing RaiseSynchronousEvents)
Have you updated your .xml file to eliminate this?
Thanks
Rusty
 

Rusty

Well-Known Member
Licensed User
Longtime User

wizard699

Active Member
Licensed User
Longtime User
I've a cart ... with some product.
When I press SEND ORDER ... I've to calculate the total from each price present in each item of the ULV ....
 

Informatix

Expert
Licensed User
Longtime User
I've a cart ... with some product.
When I press SEND ORDER ... I've to calculate the total from each price present in each item of the ULV ....
You cannot browse items in an ULV because there are no items but the visible ones. Please read the chapter about recycling in the User's Guide.
I suppose that your cart is a table, map or list, so why don't you access directly this table, map, or list ? Why do you try to get data through the ULV views ? If some items has a selected state, you can get the list of selected ID with the GetSelectedIDs function.
 

wizard699

Active Member
Licensed User
Longtime User
The problem is this: the ULV was linked to a map. But, when I delete an Item, this item was not removed automatically from the map. Is correct?
Ok .... then ... If i remove an item from a ULV ... I've to remove the same item from map to allineate these two objects.
And .. it's not so much simple ....
I will try ....
Perhaps I've to reload the ULV after I've removed an item from ULV and from the map object.
 

wizard699

Active Member
Licensed User
Longtime User
Ok .. I've made in this manner. Map alleate with ULV ... and a method to reload the ULV when an alement was removed from ULV (namely from the map, first).
 

Informatix

Expert
Licensed User
Longtime User
Ok .. I've made in this manner. Map alleate with ULV ... and a method to reload the ULV when an alement was removed from ULV (namely from the map, first).
If you remove an item in the ULV with RemoveItemAt, you do not need to reload anything.
And once removed in the ULV, you just have to call myMap.Remove(key) to remove the corresponding data. I recommend that the map key is the item ID or position (depending on your need) to ease things.
 

Informatix

Expert
Licensed User
Longtime User
I started to send emails to all UltimateListView users to give the link to the new version. As usual, some of these emails were rejected because of your spam software and I think that many were redirected to the spam folder. Please note that I won't resend these emails so check your spam folder.
I will continue to send emails in the days to come so wait until the end of the week before asking for the link.
The new link is intended to be permanent. You will use the new link and password for all future updates.
 
Top