B4A Library [B4X] PreoptimizedCLV - Lazy loading extension for xCustomListView

This is a cross platform class that extends xCustomListView and makes it "lazier".
Lazy loading is explained here: https://www.b4x.com/android/forum/t...ew-lazy-loading-virtualization.87930/#content and here: https://www.b4x.com/android/forum/threads/?-part-1-basics-creating-long-lists-using-xcustomlistview-with-lazy-loading-newer-developers.114096/#content
This is a performance optimization. Instead of creating all items with all the views when the app starts, we create the views as the items become visible.

There are all kinds of lazy loading methods.
An incomplete list:
  • Level 0: No lazy loading at all. Most applications should choose this one. It is simplest to implement and the easiest one to customize. This method is good for up to several hundred items, depending of course on the items content.
  • Level 1: Creating all layout except of the "heavy parts", usually bitmaps.
  • Level 2: Only creating layouts for the visible items.
There are several variants for level 2, you can load a new layout each time and discard old layouts or you can cache the layouts and reuse them (example: https://www.b4x.com/android/forum/t...-imageviews-and-many-rows.101431/#post-636920).

When you add items to xCLV, each item is made of two panels. The first panel is an internal panel and the second one is the panel passed in the CLV.Add call.
This is true even with lazy loading. If you are creating lists with thousands of items or more than these panels become an issue. PreoptimizedCLV solves this issue.
PreoptimizedCLV implements the level 2 method without the need to create the two panels. The internal panel is reused and the second panel is created for visible items only.

Edit: When I first developed this library I was a bit skeptic about its usefulness, as users will never scroll more than a few hundred items at most. However Klaus suggested to add fast scrolling feature and it was a great idea! With fast scrolling this library becomes useful.

It looks like this:


Full example:
B4X:
Sub Globals
    Private CustomListView1 As CustomListView
    Private PCLV As PreoptimizedCLV
    Private xui As XUI
    Private Label1 As B4XView
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("1")
    PCLV.Initialize(Me, "PCLV", CustomListView1)
    Dim words As List = File.ReadList(File.DirAssets, "english.txt")
    For Each word As String In words
        PCLV.AddItem(100dip, xui.Color_White, word)
    Next
    PCLV.Commit
End Sub

Sub PCLV_HintRequested (Index As Int) As Object
    Dim word As String = CustomListView1.GetValue(Index)
    Return word
End Sub

Sub CustomListView1_VisibleRangeChanged (FirstIndex As Int, LastIndex As Int)
    For Each i As Int In PCLV.VisibleRangeChanged(FirstIndex, LastIndex)
        Dim item As CLVItem = CustomListView1.GetRawListItem(i)
        Dim pnl As B4XView = xui.CreatePanel("")
        item.Panel.AddView(pnl, 0, 0, item.Panel.Width, item.Panel.Height)
        'Create the item layout
        pnl.LoadLayout("Item")
        Label1.Text = item.Value
    Next
End Sub

Step #1:
Add the items with PCLV.AddItem. You need to pass the item size (height for vertical lists and width for horizontal lists), background color and value. Value can be any object you like including complex custom types that hold all the data needed to later build the layout.

Step #2:
Call PCLV.Commit.

Step #3:
Implement the VisibleRangeChanged event. The code should look like:
B4X:
For Each i As Int In PCLV.VisibleRangeChanged(FirstIndex, LastIndex)
  Dim item As CLVItem = CustomListView1.GetRawListItem(i)
  'Create the layout for item i and add it to item.Panel.
Next

Step #4:
Implement the HintRequested event and return the string or CSBuilder that will be displayed when the user scrolls the list.

That's it.

Once the items are committed you can add or remove items by modifying CLV directly and calling PCLV.
B4X:
CustomListView1.InsertAtTextItem(Index, "New Item", "")
PCLV.ListChangedExternally
PCLV ignores these items.

Notes

  • The item layout should have a transparent background.
  • Better to remove AutoScaleAll and set the animation duration to 0 in the item layout.
  • The list should scroll very smoothly in release mode.
  • Make sure that "Show Scrollbar" option is unchecked in CLV custom properties.
  • PCLV.pnlOvarlay is the panel that hides the list when it fast scrolls. PCLV.lblHint is the label that appears. Both can be customized.
  • You can change the seek bar interval by setting PCLV.NumberOfSteps, before calling PCLV.Commit. The default value is 20. Don't set it to be too high as it will be difficult for the user to select a specific point.
  • ScrollView height in Android is limited to 16,777,215 pixels. The exact limit depends on the device scale and the items height. As a rule of thumb you can show up to 50,000 100dip tall items. Such lists are not practical anyway: https://www.b4x.com/android/forum/t...ension-for-xcustomlistview.115289/post-721157

Example #1 shows the 10,000 most frequent English words. Source: https://github.com/first20hours/google-10000-english
Example #2 is a port of the "Corona cases" cross platform example based on B4XTable + xChart. It depends on xChart library.
Data source: https://ourworldindata.org/coronavirus-source-data

This is an internal library.

Updates:

v1.21 - Fixes an issue with horizontal orientation in B4i.
 

Attachments

  • PCLV_Example1.zip
    40.1 KB · Views: 1,965
  • PCLV_Example2.zip
    43.8 KB · Views: 1,891
  • PreoptimizedCLV.b4xlib
    6.3 KB · Views: 882
Last edited:

MrKim

Well-Known Member
Licensed User
Longtime User
Also, I have one wish for all of this. A filter on CLV. The list I am implimenting all of this on is a problem not because the list is large, But it is constantly being refreshed - usually from the same 3-6 sources with maybe 1000 total items. It would be much better (and reduce network traffic) if I could load all thousand items and then filter the list for the desired items.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
You can disable the fast scrollbar and show the regular bar.
PCLV.ShowScrollBar = False

And ScrollBar Visible in the designer.

BBCodeView itself is based on ScrollView so I'm not sure that it is the best idea to combine them. It depends on your usage. BBLabel is not based on ScrollView.

Don't use the Tag property at all. Store everything you need in the Value object.
 

MrKim

Well-Known Member
Licensed User
Longtime User
You can disable the fast scrollbar and show the regular bar.
PCLV.ShowScrollBar = False

And ScrollBar Visible in the designer.
Did that. Cleaned the project. It had no effect. See picture in previous post. BOTH scrollbars are there but neither one works although PCLV scrolllbar does track the location when scrolling with the mouse. This is a showstopper.
This is B4J. Should I post in that forum instead of here?

Don't use the Tag property at all. Store everything you need in the Value object.
That works fine for CLV_ItemClick but I don't know how to access Value in the DragDetected event so I also stored the Map in the BBLabel Tag which I can retrieve with Sender in DragDetected.
Does not work
B4X:
Sub UnSchedOpsList_VisibleRangeChanged (FirstIndex As Int, LastIndex As Int)

    For Each i As Int In PCLV.VisibleRangeChanged(FirstIndex, LastIndex)
        Dim item As CLVItem = UnSchedOpsList.GetRawListItem(i)
        'Create the layout for item i and add it to item.Panel.
        Dim M As Map = item.Value
        Dim P As B4XView = xui.CreatePanel("UnschOPs")', TE As BCTextEngine
        Dim DT As String = M.Get("Os_StartbyDate")
        If DADMod.Isnull(DT) Then DT = "N/A" Else DT = DT.SubString2(0, 5)
        P.Tag = M
        P.LoadLayout("BBLabel")
        M.Put("BBLbl", P.GetView(0).Tag)
        TextEngine.Initialize(P)
        UnschOPsBB.Text = ScheduleMod.CheckForLate(M.Get("Os_StartbyDate"), 3) & $"  [Color=blue][b][u]"$ & M.Get("Os_JobNum") & $"[/u][/b][/Color] / [Color=black][b][u]"$ & M.Get("Os_ReleaseNum") & $"[/u][/b][/Color] Op [Color=#0000ff][b][u]"$ & M.Get("Os_SeqNum") & $"[/u][/b][/Color]"$
        item.Panel.AddView(P, 0, 0, 300, 50)
        DandD1.MakeDragSource(item.Panel, "DandD1")
        UnschOPsBB.Tag = M   '.Get("Os_ID")
    Next
End Sub

Sub DandD1_DragDetected(e As MouseEvent)
    Log("DandD1 Drag Detected : Starting drag")
Try
        Dim BB As BBLabel = Sender
        Dim M As Map = BB.Tag
        DandD1.SetDragModeAndData2(TransferMode.ANY, Array As String("AddNewJob", "Os_ID", "Data", "JobIndex", "Os_WCCode"), Array As Object("AddNewJob", M.Get("Os_ID"), "Os_ID", NumberFormat(ActiveLV.GetItemFromView(DandDCtl.mBase), 1, 0), M.Get("Os_WCCode")),  BB.mBase.Snapshot)
Catch
        Log("DandD1_DragDetected" & CRLF & LastException.Message)
End Try

End Sub
The code above starts the drag and drop OK but I have no idea how to retrieve the information I need to continue in DandD1_DragDetected.
Logging Sender gives me
B4X:
DandD1 Drag Detected : Starting drag
(draganddrop) [fx=anywheresoftware.b4j.objects.JFX@f40baf, mode=java.lang.Object@3e558d, dataid=[Ljava.lang.String;@bf429a
, dataobject=[Ljava.lang.Object;@b41f5d, dragboardimg=(Image) Not initialized, dragboardimgoffsetx=0.0
, dragboardimgoffsety=0.0, startdrag=false, callback=class com.stevel05.draganddrop.main
, seventname=DandD1, teventname=DandD1, dateutils=null
, cssutils=null, main=null, dadmod=null
, schedulemod=null, transfermode=null, httputils2service=null
, b4xcollections=null]

BBCodeView itself is based on ScrollView so I'm not sure that it is the best idea to combine them. It depends on your usage. BBLabel is not based on ScrollView.
I am not using BBCodeView so I am not sure what you are referring to here. Combine with what?

Thanks
Kim
 
Last edited:

MrKim

Well-Known Member
Licensed User
Longtime User
Erel, I love it! I was going to give up on BCTextEngine as just too slow for my needs but this solved it but with a couple of problems.

First, the (Fast?)scrolling scroll bar doesn't work. It is odd dragging it doesn't work at all but I can scroll with the mouse and when I do the scrollbar "Dot" "Tracks" it perfectly. For my needs I would prefer to have the option of using the regular scrollbar My list isn't that large (0-250) it just gets updated frequently. I have two other customlistviews on the form and I want them to all look the same. Allos, the scrollbar (Line Really) sticks out into the list.
View attachment 91272

Sigh, also Using the BBLabel I had finally figured out how to store and retrieve my map file in Both the on click event and when dragging and dropping. After switching the code to this I can't get it to work again.

Here is my code snippets:
B4X:
        Do While Crsr.NextRow = True
                Dim M As Map
                M.Initialize
                M.Put("Os_JobNum", Crsr.GetString("Os_JobNum"))
                M.Put("Os_ReleaseNum", Crsr.GetString("Os_ReleaseNum"))
                M.Put("Os_SeqNum", Crsr.GetString("Os_SeqNum"))
                M.Put("Os_WCCode", Crsr.GetString("Os_WCCode"))
                M.Put("Os_ID", Crsr.GetInt("Os_ID"))
                M.Put("Scheduled", False)
                PCLV.AddItem(50, xui.Color_ARGB(255, 186, 204, 210), M)
                zx=zx+1
                If zx = 12 Then
                Sleep(0)  'forces view of what is loaded so far
            End If
            'Sleep(1)
        Loop
        PCLV.Commit
        Crsr.Close


Sub PCLV_HintRequested (Index As Int) As Object
    Dim word As Map = UnSchedOpsList.GetValue(Index)
    Return word
End Sub


Sub UnSchedOpsList_VisibleRangeChanged (FirstIndex As Int, LastIndex As Int)
    'Log("Now")
    For Each i As Int In PCLV.VisibleRangeChanged(FirstIndex, LastIndex)
        Dim item As CLVItem = UnSchedOpsList.GetRawListItem(i)
        'Create the layout for item i and add it to item.Panel.
        Dim M As Map = item.Value
        Dim P As B4XView = xui.CreatePanel("UnschOPs")', TE As BCTextEngine
        Dim DT As String = M.Get("Os_StartbyDate")
        If DADMod.Isnull(DT) Then DT = "N/A" Else DT = DT.SubString2(0, 5)
        P.Tag = M
        P.LoadLayout("BBLabel")
        M.Put("BBLbl", P.GetView(0).Tag)
        TextEngine.Initialize(P)
        UnschOPsBB.Text = ScheduleMod.CheckForLate(M.Get("Os_StartbyDate"), 3) & $"  [Color=blue][b][u]"$ & M.Get("Os_JobNum") & $"[/u][/b][/Color] / [Color=black][b][u]"$ & M.Get("Os_ReleaseNum") & $"[/u][/b][/Color] Op [Color=#0000ff][b][u]"$ & M.Get("Os_SeqNum") & $"[/u][/b][/Color]"$
        'item.Color = xui.Color_ARGB(255, 126, 180, 250)
        item.Panel.AddView(P, 0, 0, 300, 50)
        DandD1.MakeDragSource(item.Panel, "DandD1")
        UnschOPsBB.Tag = M.Get("Os_ID")
    Next
End Sub
For one thing, since we are no longer using CLV.Add but PCLV.AddItem I am no longer sure exactly what is returned inSub CLV_ItemClick. I used to store the Map in My Panel.Tag (P) and and Make P the Value returned. That no longer works. I have now made item.Panel my drag source, which does start the drag event, but again, I cannotfigure out how to retrieve my map from the tag.

Thanks for your help.
I solved the scrollbar issue.
B4X:
       PCLV.ShowScrollBar = False

MUST come AFTER
B4X:
    PCLV.Initialize(Me, "PCLV", UnSchedOpsList)
 

beltrao73

Member
Licensed User
Longtime User
Hi,
Is it possible to combine PreoptimizedCLV and CLVExpandable?
 
Top