Android Code Snippet Grid in a CustomListView for B4A: Icon Selector with Lazy Loading

TECHNIQUE
- Builds a grid from CustomListView rows using nested panels (outer loop = rows, inner loop = cells)
- itemHeight scales automatically from itemWidth via aspect ratio
- Elements centered programmatically (photoLeft, lblHeight)

Screenshot_20260411_173743_b4a_customlistviewgrid_main.jpg
Screenshot_20260412_162755_b4a_customlistviewgrid_main.jpg


ARCHITECTURE
- MainPage layout contains the CLV; lyItemGrid is the layout for each cell
- Fixed view hierarchy:
pnlCell (dynamic, CreatePanel)
└─[0] pnlBase ← lyItemGrid root
├─[0] ivPhoto
└─[1] lblName
Dim iv As ImageView = pCell.GetView(0).GetView(0)
- pnlCell is the dynamic container that receives the click event
and the visual selection effect (SetColorAnimated)

DATA & LOADING
- Monochrome vector icons loaded from AddResources folder via AndroidResources
- pnlCell.Tag (Map) is the data source: holds the icon name and "loaded" flag
- Lazy loading via CLV1_VisibleRangeChanged
- Memory is not released between views: few images, lightweight vectors
- For larger images, see post 2

NOTES
- Built for B4A (icon selector use case)
- Developed with AI assistance
 

Attachments

  • CustomListViewGrid.zip
    90.5 KB · Views: 18
Last edited:

GeoT

Active Member
Licensed User
Longtime User
If there are many large image files:
  • We add image unloading from memory in VisibleRangeChanged(First, Last)

B4X:
Sub CLV1_VisibleRangeChanged(FirstIndex As Int, LastIndex As Int)
    Dim loadFrom    As Int = Max(0, FirstIndex - 1)
    Dim loadTo      As Int = Min(CLV1.Size - 1, LastIndex + 1)
    Dim unloadFrom  As Int = 0
    Dim unloadTo    As Int = Max(0, FirstIndex - 3)
    Dim unloadFrom2 As Int = Min(CLV1.Size - 1, LastIndex + 3)
    Dim unloadTo2   As Int = CLV1.Size - 1

    For i = 0 To CLV1.Size - 1
        Dim pRow As B4XView = CLV1.GetPanel(i)
        Dim shouldLoad As Boolean = (i >= loadFrom And i <= loadTo)
        Dim shouldUnload As Boolean = (i <= unloadTo) Or (i >= unloadFrom2)

        For c = 0 To pRow.NumberOfViews - 1
            Dim pCell As B4XView = pRow.GetView(c)
            If Not(pCell.Tag Is Map) Then Continue

            Dim cellTag As Map = pCell.Tag
            Dim iv As ImageView = pCell.GetView(0)

            If shouldLoad And Not(cellTag.Get("loaded")) Then
                Try
                    iv.Background = AR.GetApplicationDrawable(cellTag.Get("name"))
                    cellTag.Put("loaded", True)
                Catch
                    Log("Failed to load: " & cellTag.Get("name"))
                End Try
            Else If shouldUnload And cellTag.Get("loaded") Then
                iv.Background = Null
                cellTag.Put("loaded", False)
            End If
        Next
    Next
End Sub

Prevents OutOfMemoryError with high-resolution JPEG images and hundreds of items.

  • Explanation of Dim shouldLoad As Boolean = (i >= loadFrom And i <= loadTo) Dim shouldUnload As Boolean = (i <= unloadTo) Or (i >= unloadFrom2):
With concrete numbers, assuming FirstIndex=5, LastIndex=8:
loadFrom = 4 (First - 1)
loadTo = 9 (Last + 1)
unloadTo = 2 (First - 3)
unloadFrom2 = 11 (Last + 3)

Scheme.png


' True if the row is inside the load buffer [4..9]
Dim shouldLoad As Boolean = (i >= loadFrom And i <= loadTo)

' True if the row is OUTSIDE the unload buffer:
' - above: i <= 2
' - below: i >= 11
' (Or, since the unload zones are on opposite ends of the buffer)
Dim shouldUnload As Boolean = (i <= unloadTo) Or (i >= unloadFrom2)

' The intermediate zones [3] and [10] do nothing — neither load nor unload.
' They act as a safety margin between both operations,
' preventing the same row from being loaded and unloaded in the same event.
 
Last edited:
Top