B4A Library MSDynamicGridView Library

This library implements one kind of GridViews:

Android Version Support
This GridView doesnt work 100% on Andriod 2.3 and lower. The animations are not supported and also the events are not raised (due to a bug).

1. DynamicGridView
This gridview is very flexible. All items must be of the same height/width.
It can load layouts into a panel, so you can have different layouts in each grid cell. It is very flexible and powerful. More features include the ability to drag/drop.

Credits:
This is very important. Most of these libraries are based on the work and research of warwound. Also, I got some ideas from Informatix. But warwound's custom listview library and explanations are the reason there libraries are here, if you are not going to donate to me, I appreciate donating to him.

Usage:
The gridview is not as simple to use like the B4A Listview.
It is more complex and rely on the concept of Adapters. If you have used Informatix's ULV you will be more familiar with the format.

Databinding:
The Gridview itself does not hold any data (String/Int/Customtype anything).
As a user it is your job to bind the data to a cell in a gridview using an ItemID or a ItemTag.
An ItemID is a unique ID for each cell of the Grid. This is generated internally.
An ItemTag is also unique for each item but it allows you to pass more complex data. This can allow you to change the appearance or layout of items by passing some tags.

This is where we get to the concept of an Adapter.

Adapters:
The adapter is responsible to create a layout and fetch the data to display in the views of the listview. Since listviews/gridviews can scroll and hold large amounts of data, the Adapters can recycle items into and out of the view.

MSDynamicAdapter:
The adapter needs to know the number of columns and width and height of each cell on initialization.
B4X:
DynamicAdapter.Initialize("DA",NumCols, 100%x/NumCols, 100dip)
You can add items to the Adapter at any time:
B4X:
    For i = 0 To 50
        DynamicAdapter.Add("Item " & i)
    Next
The ItemTag does need to be unique but we also internally always generate a unique ItemID. You can use ItemTag to make certain cells look similar, or hold certain kinds of data.

The Adapter will raise two events:
- GetLayout
- GetContent

In GetLayout you must create a new panel, load the layout by code or designer, and return the panel.
You should not really bind any content to your views here.

In GetContent you will get the ContentPanel in the parameters. Use ContentPanel.GetView to find your labels and imageviews etc, and then assign the values here like label.text and imageview.background etc.

It is important to understand adapters before you really move on.

Adapter Events Signature:
GetLayout
(Position as Int, ItemID as Long , ItemTag as String) as Panel
Create a panel in this event and return it, that will be used as the layout for the cell.
You can use Postion/ItemID/ItemTag to differenciate the layout.

GetContent (Position as Int , ItemID as Long , ItemTag as String , ContentPanel as Panel) as Panel
In this event you can used ItemID/ItemTag/Position to pull your data (from a list/map/db) and put that data onto the views in ContentPanel. You can find the views using GetView, or maybe use Tags.

GridView:

The Gridview is very simple. Just initialize it with an eventname, and add it to your activity.
You can set the adapter with:
B4X:
'Set the Gridview's adapter
    DynamicGrid.Adapter = DynamicAdapter.GetAdapter
This allows you to quickly change all the data the grid is holding!

Set the NumColums(DynamicGrid) the same as that set for the adapter.

Screenshots:
1127vxl.jpg


2r78rx2.jpg


15heghv.jpg


Samples:
Drag drop demo: https://www.b4x.com/android/forum/threads/drag-and-drop-gridview.46242/#content
Public demo: https://play.google.com/store/apps/details?id=com.maximussoft.picpuzzle

<to be continued>
 
Last edited:

shashkiranr

Active Member
Licensed User
Longtime User
Hi @thedesolatesoul ,

Here is the below code. It is added in the layout creator of the ULV.

B4X:
Sub ULV_TYPE_Technologies_LayoutCreator(LayoutName As String, LayoutPanel As Panel)
    Log("*** tech "&LayoutName.SubString(17))
    Dim card As MSCardView
    card.Initialize("")
    card.MaxElevation = 4dip
    card.Elevation = 2dip
    card.Radius = 2dip
    LayoutPanel.AddView(card, 10dip, 0, LayoutPanel.Width-20dip, LayoutPanel.Height)
   
    Dim tech As Technologies_Content = List_Variables_Functions.Type_Technologies_List.Get(LayoutName.SubString(21))
   
    Dim lbltitle As Label
    lbltitle.Initialize("")
    lbltitle.color = Colors.Transparent
    lbltitle.textcolor = Colors.RGB(66,66,66)
    lbltitle.textsize = List_Variables_Functions.textsizeall
    lbltitle.Gravity =  Gravity.left
    lbltitle.Typeface = Typeface.DEFAULT_BOLD
    card.Panel.AddView(lbltitle, 5dip, 5dip, LayoutPanel.Width - 10dip, OriginalHeight)
    lbltitle.Height = StrUtil.MeasureMultilineTextHeight(lbltitle,tech.title)
    Dim r As Reflector
    r.Target = lbltitle
    r.RunMethod2("setMaxLines", 1, "java.lang.int")
    r.RunMethod2("setHorizontallyScrolling", False, "java.lang.boolean") 
   
        Dim GridPanel As Panel
        GridPanel.Initialize("")
        card.Panel.AddView(GridPanel,5dip,lbltitle.Height+5dip,card.Panel.Width,card.Panel.Height)
        Dim GridViewtech As MSDynamicGridView 
        Dim GridAdaptertech As MSDynamicAdapter 
        Dim NumCols As Int 
        NumCols  = Activity.Width/110dip
        GridViewtech.Initialize("GView")
        GridViewtech.NumColumns = NumCols
        GridViewtech.Selection = -1
        GridViewtech.EditModeEnabled = False
        GridAdaptertech.Initialize("GA",NumCols, 110dip,110dip)
        GridPanel.AddView(GridViewtech,0,0,GridPanel.Width,GridPanel.Height)
        GridViewtech.Adapter = GridAdaptertech.GetAdapter
        GridPanel.Tag = tech.title
       
        For i = 0 To tech.images.Length-1
            GridAdaptertech.Add(tech.title)
        Next

End Sub

Regards,
SK
 

thedesolatesoul

Expert
Licensed User
Longtime User
Thanks.
Just a few notes about this code.
Height of card = LayoutPanel.Height
Height of gridpanel = card.panel.height
Height of GridViewtech = gridpanel.Height
So if LayoutPanel.Height is known then we can find the height of GridViewTech. I cant remember in ULV if you have to provide the height of the layout, so LayoutPanel.Height will be known. So it may be possible to indirectly calculate the height of gridpanel.height.

I believe the issue arises because of the cardview panel is set to -1 i.e. FILL_PARENT so any views below it will probably not give you the correct height.

Currently I dont know of a way to get the height at runtime after its set to fill_parent or wrap_content.
 

shashkiranr

Active Member
Licensed User
Longtime User
Hi @thedesolatesoul ,

Yes you are right. I also tried to use wrap content so that the card view automatically adjusts after the gridview is added but it didnt work either.

Thank you for your kind attention.

Regards,
SK
 

thedesolatesoul

Expert
Licensed User
Longtime User
I'm still not convinced using a grid is the best idea, unless you know exactly how many items you have (as you cannot use scrolling).
For e.g. there each item is 110dipx110dip and NumCols = 100%x/110dip
So if you have NumItems images, your gridview height will be:
NumRows = Ceil(NumItems / NumCols)
GridHeight = NumRows * 110dip (This is the height of the grid required when there will be no scrolling and all items are visible)
 

jotaele

Member
Licensed User
Longtime User
Solved again with this code:
B4X:
Sub DA_GetContent(Position As Int, ItemID As Long, ItemTag As String, ContentPanel As Panel) As Panel
    Dim Bitmap1 As Bitmap
    Bitmap1.Initialize(File.DirAssets ,GridItems.Get(ItemID))    'relat_02

    Dim iv1 As ImageView = ContentPanel.GetView(0)
    iv1.Bitmap = Bitmap1

    Return ContentPanel
End Sub

I was looking your code of the example of the grid2.zip and, if I am not wrong, you must NO load the images in this event.

B4X:
Sub DA_GetLayout(Position As Int, ItemID As Long, ItemTag As String) As Panel
   Dim p As Panel
   p.Initialize("")
   Log("ItemTag: " & ItemTag)
   Dim iv As ImageView
   Dim bmp As Bitmap
   bmp.Initialize(File.DirAssets, ItemTag)
   iv.Initialize("iv")
   iv.Gravity = Gravity.Fill 'ajustar imagem
   iv.Bitmap = bmp

'   p.AddView(iv, 1dip, 1dip,(100%x/NumCols)-2dip,(100%x/NumCols)-2dip)
   p.AddView(iv, 1dip, 1dip,(Panel1.Width/NumCols)-2dip,(Panel1.Width/NumCols)-2dip)
   
   Return p
End Sub

The code can be more simple and faster. Only load the layout:

B4X:
   Dim p As Panel
   p.Initialize("")
   Dim iv As ImageView
   iv.Initialize("iv")
   iv.Gravity = Gravity.Fill 'ajustar imagem
   p.AddView(iv, 1dip, 1dip,(Panel1.Width/NumCols)-2dip,(Panel1.Width/NumCols)-2dip)
   Return p

End Sub

Correctme if I am wrong.
 

thedesolatesoul

Expert
Licensed User
Longtime User
I was looking your code of the example of the grid2.zip and, if I am not wrong, you must NO load the images in this event.
This is an interesting point.
If all of your images are the same then you can load it in GetLayout, if they are all different based on Item then you should load it in GetContent.
The way most recycleable views work is that they will reuse the layouts generated in GetLayout, if they cant find one, then they create a new one and call GetLayout. This means that if you only load the bitmaps in GetLayout sometimes you would see duplicates depending on how the device is handling the layouts.
Btw...thanks for your donation :)
 

jotaele

Member
Licensed User
Longtime User
This is an interesting point.
If all of your images are the same then you can load it in GetLayout, if they are all different based on Item then you should load it in GetContent.
The way most recycleable views work is that they will reuse the layouts generated in GetLayout, if they cant find one, then they create a new one and call GetLayout. This means that if you only load the bitmaps in GetLayout sometimes you would see duplicates depending on how the device is handling the layouts.
Btw...thanks for your donation :)

Thanks for the explanation.
 

asales

Expert
Licensed User
Longtime User
Hi @thedesolatesoul

I want to make a grid with images and colored panels (without images) in same grid. See image0.

This is my code (B4A 5. See the example in attached):

B4X:
Sub GridImages
    DynamicGrid.Initialize("DG")
    DynamicAdapter.Initialize("DA", NumCols, pnlGrid.Width/NumCols, pnlGrid.Width/NumCols)
   
    DynamicGrid.NumColumns = NumCols
   
    DynamicGrid.EditModeEnabled = False 'No drag/drop
    DynamicGrid.WobbleInEditMode = False
   
    pnlGrid.AddView(DynamicGrid, 0, 0, pnlGrid.Width, pnlGrid.Height)
   
    GridItems.Initialize

    'LOAD IMAGES   
    For i = 0 To qtimg - 1
        Dim ItemString As String = i & ".jpg"
        GridItems.Add(ItemString)
        DynamicAdapter.Add(ItemString)       
    Next
   
    'CREATE PANELS
    For i = 0 To 9
        Dim ItemString As String = "[C:B]"
        GridItems.Add(ItemString)
        DynamicAdapter.Add(ItemString)
    Next   

    DynamicGrid.Adapter = DynamicAdapter.GetAdapter
End Sub   

Sub DA_GetLayout(Position As Int, ItemID As Long, ItemTag As String) As Panel
    Dim p As Panel
    p.Initialize("")

    If ItemTag.Contains("[C:B]") = False Then
        Dim iv As ImageView
        iv.Initialize("iv")
        iv.Gravity = Gravity.Fill
        p.AddView(iv, 1dip, 1dip,(pnlGrid.Width/NumCols)-2dip,(pnlGrid.Width/NumCols)-2dip)
    End If

    Return p
End Sub

Sub DA_GetContent(Position As Int, ItemID As Long, ItemTag As String, ContentPanel As Panel) As Panel

    'PANEL + IMAGEVIEW
    If ItemTag.Contains("[C:B]") = False Then
        Dim Bitmap1 As Bitmap
        Bitmap1.Initialize(File.DirAssets ,GridItems.Get(ItemID))   
       
        Dim iv1 As ImageView = ContentPanel.GetView(0)
        iv1.Bitmap = Bitmap1
    Else
        'ONLY COLORED PANEL
        If Position Mod 2 = 0 Then
            ContentPanel.Color = Colors.Green
        Else
            ContentPanel.Color = Colors.Yellow
        End If
    End If   

    Return ContentPanel
End Sub

I have this problems:
- the first 8 cells it's ok, but the others is not (the images is show again). See image1 and image2.
- if I scrolling the grid in certain point I get this error:
B4X:
main_da_getcontent (java line: 352)
java.lang.RuntimeException: Object should first be initialized (View).
    at anywheresoftware.b4a.AbsObjectWrapper.getObject(AbsObjectWrapper.java:50)
    at b4a.example.msgrid1.main._da_getcontent(main.java:352)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:187)
    at com.maximussoft.gridviews.B4ADynamicGridAdapter.getView(B4ADynamicGridAdapter.java:66)
    at android.widget.AbsListView.obtainView(AbsListView.java:2240)
    at android.widget.GridView.makeAndAddView(GridView.java:1345)
    at android.widget.GridView.makeRow(GridView.java:345)
    at android.widget.GridView.fillUp(GridView.java:386)
    at android.widget.GridView.fillGap(GridView.java:262)
    at android.widget.AbsListView.trackMotionScroll(AbsListView.java:5143)
    at android.widget.AbsListView$FlingRunnable.run(AbsListView.java:4254)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
    at android.view.Choreographer.doCallbacks(Choreographer.java:574)
    at android.view.Choreographer.doFrame(Choreographer.java:543)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:5214)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:814)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:630)
    at dalvik.system.NativeStart.main(Native Method)
java.lang.RuntimeException: Object should first be initialized (View).

I tried, but I don't know how to make the app works.

Thanks in advance for any tip.

image0.png image1.jpg image2.jpg
 

Attachments

  • msgrid1.zip
    317.3 KB · Views: 350

thedesolatesoul

Expert
Licensed User
Longtime User
Hi @AS2
Sorry for the delay in replying, (there will be more in the future)
I think I know what the problem is but Im having some issues with my device connecting to my PC so I cant test it out yet.
This is probably related to post #53, and this is something i havent tested.
As layouts are re-used, so GetLayout is not always called for everytime you scroll and the items cycle out. This means it is better to have the same layout for all the items, but in GetContent you can set the imageview visibility to false.
I'll see if i can post how to do it once i test it.
 

thedesolatesoul

Expert
Licensed User
Longtime User
Hey,
So I made some minimal changes to your project, hopefully you will understand how it goes.
Change1.
In DA_GetLayout we always create the full layout even the imageview. This is so that even though the grid items will look different they have to share the same layout.
Change2.
In DA_GetContent I moved the line:
Dim iv1 As ImageView = ContentPanel.GetView(0)
to the top, since we always have an imageview in the layout so we can always get it.
Change3.
If we only want to show the colored panel, then we can make iv1.Visible = False.
Change4.
This one I didnt anticipate, is a side effect of Change3. We need to set iv1.Visible = True in the other If branch. Otherwise the re-used layout will use the previously set properties when you scroll.

To summarize:
1. In DA_GetLayout you will want to build the full layout even if you dont use all of it (and we can hide the views we dont want in DA_GetContent).
2. In DA_GetContent try to be exhaustive i.e. set the properties in all the branches for completeness otherwise you might see 'leakage' of these properties into other items.

Thanks.
 

Attachments

  • msgrid1.2.zip
    317.3 KB · Views: 480
Top