Cursussen/Courses Codesnippets     Top 
B4A-lists - Fine tuning


1. Layout changes
To make the app look better you could use colors for the background of the comboboxes and lists.
Change the drawable properties for the views involved. To fill up the white space around the button you can set the application drawable to the same color as the CustomListViews (clv1, clv2, clv3).
For the button use the StatelistDrawable properties.
With the SetTitle method from B4Xpages you can set the text on the application bar. This code is inserted in the B4XPage_Created method.
B4XPages.SetTitle(Me,"My Items")
B4XPages.SetTitle(Me,"My Items - Category")
B4XPages.SetTitle(Me,"My Items - Subcategory")


2. Adding notes
For each item we will provide a note. In the item_dialog we already have a second EditText field (edt2).
When an item is added, changed or deleted we process this action in 2 subroutines: load_note, save_note.
The file name consists of the categoryname, the subcategoryname and the itemname.
Add the following code to the subroutine btn1_Click and clv1_ItemClick right after the call to save_list:
In the load_list subroutine we add the note to the text of the item (for now).
In the item_dialog layout make sure the first EditText field is at top 50 and height 50!
You need to do a few more adjustments. In the clv1_ItemClick subroutine add the following code:
In the btn1_Click subroutine add the following code:
Sub load_note(cat As String, subcat As String, item As String) As String
	If File.Exists(File.DirInternal,cat & "_" & subcat & "_" & item & ".txt") Then
		Return File.ReadString(File.DirInternal,cat & "_" & subcat & "_" & item & ".txt")
	Else
		Return ""
	End If
End Sub

Sub save_note(cat As String, subcat As String, item As String,note As String)
	File.Delete(File.DirInternal,cat & "_" & subcat & "_" & item & ".txt")
	File.WriteString(File.DirInternal,cat & "_" & subcat & "_" & item & ".txt",note)
End Sub

' in btn1_Click and clv1_ItemClick after the call to save_list
save_list(SelectedItem1,SelectedItem2)
save_note(cbx1.Selecteditem,cbx2.Selecteditem,edt1.Text,edt2.Text)

Sub clv1_ItemClick (Index As Int, Value As Object)
	Dim pnl1 As Panel = setup_dialog("item_dialog","Edit/Delete category")
	Dim rs1 As ResumableSub =  itemdialog.ShowCustom(pnl1, "SAVE", "DELETE", "CANCEL")
	edt1.Text = clv1.GetValue(Index)
	edt2.Text = load_note(cbx1.Selecteditem,cbx2.Selecteditem,edt1.Text)
	Wait For (rs1) Complete (Result As Int)
	If Result = xui.dialogResponse_Positive Then
		If edt1.Text <> "" Then
			clv1.RemoveAt(Index)
			clv1.InsertAtTextItem(Index,edt1.text,edt1.text)
		End If
	else if Result = xui.dialogResponse_Negative Then
		clv1.RemoveAt(Index)
	End If
	If clv1.Size > 0 Then
		save_list(SelectedItem1,SelectedItem2)
		save_note(cbx1.Selecteditem,cbx2.Selecteditem,edt1.Text,edt2.Text)
		clv1.Clear
		load_list
	End If
End Sub

Sub btn1_Click
	If cbx1.Size < 1 Then
		Return
	End If
	If cbx2.Size < 1 Then
		Return
	End If
	edt1.Initialize("")
	Dim pnl1 As Panel = setup_dialog("item_dialog","Add item for " & cbx1.SelectedItem & " - " & cbx2.SelectedItem)
	Wait For (itemdialog.ShowCustom(pnl1, "OK", "", "CANCEL")) Complete (Result As Int)
	If Result = xui.dialogResponse_Positive Then
		If edt1.Text <> "" Then
			clv1.AddTextItem(edt1.text,edt1.text)
			If clv1.Size > 0 Then
				save_list(cbx1.Selecteditem,cbx2.Selecteditem)
				save_note(cbx1.Selecteditem,cbx2.Selecteditem,edt1.Text,edt2.Text)
				clv1.Clear
				load_list
			End If
		End If
	End If
End Sub



3. Expandable setup
Check out the following link: CLVExpandable
To show or hide the notes in the CustomListView you can use the CLVExpandable class (written by Erel).
In the project you make a new class module by selecting Project / Add New Module / Class Module / Standard Class. Give it the name CLVExpandable and add the module to the parent folder.
Copy this code in the module (replacing what is already in it).
'version 1.00
Sub Class_Globals
	Type ExpandableItemData (CollapsedHeight As Int, ExpandedHeight As Int, Value As Object, Expanded As Boolean)
	Private mCLV As CustomListView
	Private xui As XUI
End Sub

Public Sub Initialize (CLV As CustomListView)
	mCLV = CLV
End Sub

Public Sub CreateValue(pnl As B4XView, Value As Object) As ExpandableItemData
	Dim e As ExpandableItemData
	e.Initialize
	e.CollapsedHeight = pnl.GetView(0).Height
	e.ExpandedHeight = pnl.GetView(0).Height + pnl.GetView(1).Height
	e.Value = Value
	Return e
End Sub

Public Sub GetValue (Index As Int) As Object
	If mCLV.GetValue(Index) Is ExpandableItemData Then
		Dim e As ExpandableItemData = mCLV.GetValue(Index)
		Return e.Value
	End If
	Return mCLV.GetValue(Index)
End Sub

Public Sub ExpandItem (Index As Int)
    ResizeItem(Index, False)
    Dim item As CLVItem = mCLV.GetRawListItem(Index)
    Dim delta As Int = item.Offset + item.Size - mCLV.sv.ScrollViewOffsetY - mCLV.AsView.Height
    If delta > 0 Then
        Sleep(5)
        Dim offset As Int = mCLV.sv.ScrollViewOffsetY + delta
        Dim nsv As ScrollView = mCLV.sv
        nsv.ScrollPosition = offset
    End If
End Sub


Sub CollapseItem(index As Int)
	ResizeItem(index, True)
End Sub

Private Sub ResizeItem (Index As Int, Collapse As Boolean)
	Dim item As CLVItem = mCLV.GetRawListItem(Index)
	Dim p As B4XView = item.Panel.GetView(0)
	If p.NumberOfViews = 0 Or (item.Value Is ExpandableItemData) = False Then Return
	Dim NewPanel As B4XView = xui.CreatePanel("")
	MoveItemBetweenPanels(p, NewPanel)
	Dim id As ExpandableItemData = item.Value
	id.Expanded = Not(Collapse)
	mCLV.sv.ScrollViewInnerPanel.AddView(NewPanel, 0, item.Offset, p.Width, id.ExpandedHeight)
	If Collapse Then
		AnimatedArrow(180, 0, NewPanel)
	Else
		AnimatedArrow(0, 180, NewPanel)
	End If
	Dim NewSize As Int
	If Collapse Then NewSize = id.CollapsedHeight Else NewSize = id.ExpandedHeight
	mCLV.ResizeItem(Index, NewSize)
	NewPanel.SendToBack
	Sleep(mCLV.AnimationDuration)
	If p.Parent.IsInitialized Then
		MoveItemBetweenPanels(NewPanel, p)
	End If
	NewPanel.RemoveViewFromParent
End Sub

Private Sub MoveItemBetweenPanels (Src As B4XView, Target As B4XView)
	Do While Src.NumberOfViews > 0
		Dim v As B4XView = Src.GetView(0)
		v.RemoveViewFromParent
		Target.AddView(v, v.Left, v.Top, v.Width, v.Height)
	Loop
End Sub

Private Sub AnimatedArrow(From As Int, ToDegree As Int, Pnl As B4XView)
	Dim title As B4XView = Pnl.GetView(0) 'pnlTitle is the first item
	Dim iv As B4XView = title.GetView(1) 'ImageView1 is the second item
	iv.SetRotationAnimated(0, From)
	iv.SetRotationAnimated(mCLV.AnimationDuration, ToDegree)
End Sub

Public Sub ToggleItem (Index As Int)
	Dim i As ExpandableItemData = mCLV.GetValue(Index)
	If i.Expanded = True Then
		CollapseItem(Index)
	Else
		ExpandItem(Index)
	End If
End Sub


4. Expandable layout
Now we need to make a layout for the item and the expandable part. Go to the designer window and add a new file with the name “Item”.
On the top there is a panel (pnlTitle) with a label (lblTitle) and an imageview (ImageView1). Below the panel there is a label (Label1) that will be used for the note. The height of the label is 190 displaypixels.


5. Expandable code
Select the B4XmainPage tab in the IDE window.
Now declare the variables in the B4XMainPage Class_Globals method. You can do this from the layout file using generate members.
Initialize the expandable variable in the B4Xpage_Created subroutine.
Make the CreateItem subroutine to fill the layout with values.
Now change the load_list subroutine to use the CreateItem subroutine.
For this to work the ImageView1 needs a file “arrow.png”. Search on the internet for an image like this or you can use the image file from the zip-file on the B4X website. Add the file in the Files tab of the IDE.
Change the save_list subroutine to use the expandable object.
Change the click events: a single tap to toggle the note view and a long tap to change/delete the item and the note.
You need to change the edt1.text to retrieve the value from the expandable object (see clv1_ItemLongClick).
This is how it looks like now:
' in Class_Globals
' expandable
Private lblTitle As B4XView
Private pnlTitle As B4XView
Private pnlExpanded As B4XView
Private expandable As CLVExpandable
Private Label1 As Label
Private ImageView1 As ImageView
' in B4XPage_Created
expandable.Initialize(clv1)

Sub CreateItem(clr As Int, Title As String,note As String, ExpandedHeight As Int) As B4XView
	Dim p As B4XView = xui.CreatePanel("")
	p.SetLayoutAnimated(0, 0, 0, clv1.AsView.Width, ExpandedHeight)
	p.LoadLayout("Item")
	p.SetLayoutAnimated(0, 0, 0, p.Width, p.GetView(0).Height) 'resize it to the collapsed height
	lblTitle.Text = Title
	pnlTitle.Color = clr
	pnlExpanded.Color = 0xFFF7D08A
	Label1.Text = note
	Return p
End Sub

Sub load_list
	Private lst As List
	lst.Initialize
	If (File.Exists(File.DirInternal,cbx1.SelectedItem & "_" & cbx2.SelectedItem & ".txt")) Then
		lst = File.ReadList(File.DirInternal,cbx1.SelectedItem & "_" & cbx2.SelectedItem & ".txt")
	End If
	For i = 0 To lst.Size-1
		'clv1.AddTextItem(lst.Get(i) & " - note:" & note, lst.Get(i))
		Dim title As String = lst.Get(i)
		Dim note As String = load_note(cbx1.SelectedItem,cbx2.SelectedItem,lst.Get(i))
		Dim p As B4XView = CreateItem(0xFFFCFFC5, title, note, 400dip + 60dip)
		clv1.Add(p, expandable.CreateValue(p, lst.Get(i)))
	Next
End Sub

Sub save_list(cat As String,subcat As String)
	Private lst As List
	lst.Initialize
	For i = 0 To clv1.Size-1
		lst.Add(expandable.GetValue(i))
		'lst.Add(clv1.GetValue(i))
	Next
	File.Delete(File.DirInternal,cat & "_" & subcat & ".txt")
	File.WriteList(File.DirInternal,cat & "_" & subcat & ".txt",lst)
End Sub

Sub clv1_ItemClick (Index As Int, Value As Object)
	expandable.ToggleItem(Index)
End Sub

Sub clv1_ItemLongClick (Index As Int, Value As Object)
	Dim pnl1 As Panel = setup_dialog("item_dialog","Edit/Delete item for "& cbx1.SelectedItem & " - " & cbx2.SelectedItem)
	Dim rs1 As ResumableSub =  itemdialog.ShowCustom(pnl1, "SAVE", "DELETE", "CANCEL")
	edt1.Text = expandable.GetValue(Index)
	'edt1.Text = clv1.GetValue(Index)
...




6. Expandable wrap-up
When there is not enough room to display the note the CustomListView will scroll up so the note will become visible.
The display of the note is limited to 9 lines. Feel free to find a solution that can display more lines.
To use an easier way for changing or deleting an item you add a button to each item line.
Click of the B4XmainPage tab in the IDE and then go to the designer window en open the file “Item”. Add a button after the ImageView1.
Generate a variable declaration and a click event for this Button1. Then save the file.
Go to the B4XmainPage tab in the IDE.
Add the code to call the clv1_ItemLongClick subroutine in the Button1_Click method.
It looks like the button doesn’t have a text on it.
Add 2 lines in the CreateItem subroutine before the return statement and change the font size and background color of the button.
Sub Button1_Click
	Dim index As Int = clv1.GetItemFromView(Sender)
	clv1_ItemLongClick(index,"")
End Sub

' in CreateItem
Button1.Tag = Title
Button1.Text = Chr(0x270D)


7. CLVDragger setup
The class that is used in this example is written by Erel for B4J and modified by wes58 for B4A.
The link: CLVDragger
If you follow the link you can see that you need to activate 2 additional libraries: xCustomListView and Reflection. The list now look like this:
Make a new class module in the project and name it CLVDragger. Add the module to the parent folder.


8. CLVDragger code
Copy the following code replacing what was already there:
Sub Class_Globals
	Private xui As XUI
	Private list As CustomListView
	Private PressedColor As Int
	Private Top As Int
	Private pnl As B4XView
	Private ListStartY As Int
	Private dWidth, dBackground, dTextColor As Int
End Sub

Public Sub Initialize (clv As CustomListView)
	list = clv
	PressedColor = list.PressedColor
	dTextColor = list.DefaultTextColor
	dBackground = xui.Color_Transparent
	dWidth = 30dip
End Sub

'Set values for dragger width, background color and text color
'or defaults will be used - width=30dip, background color=transparent, text color=clv default color
Public Sub SetDefaults (width As Int, backColor As Int, txtColor As Int)
	dWidth = width
	dBackground = backColor
	dTextColor = txtColor
End Sub

Public Sub RemoveDragButtons
	For i = 0 To list.Size - 1
		Dim p As B4XView = list.GetPanel(i)
		Dim b As Boolean = IsLastViewADragLabel(p)
		If b Then
			p.GetView(p.NumberOfViews - 1).RemoveViewFromParent
		End If
	Next
	list.PressedColor = PressedColor
End Sub

Private Sub IsLastViewADragLabel (p As B4XView) As Boolean
	If p.NumberOfViews > 0 Then
		Dim v As B4XView = p.GetView(p.NumberOfViews - 1)
		If v Is Label And v.Tag = Null Then
			Return False
		End If
		Return v Is Label And v.Tag = list
	End If
	Return False
End Sub

Public Sub AddDragButtons
	list.PressedColor = xui.Color_Transparent
	Dim fnt As B4XFont = xui.CreateMaterialIcons(25)
	For i = 0 To list.Size - 1
		Dim p As B4XView = list.GetPanel(i)
		If IsLastViewADragLabel(p) = False Then
			Dim lbl As Label
			lbl.Initialize("")	'Drag")
			Dim xlbl As B4XView = lbl
			xlbl.Font = fnt
			xlbl.Text = Chr(0x21C5)		'Chr(0xE25D)
			xlbl.TextColor = dTextColor
			xlbl.Color = Colors.Green 		'dBackground
			xlbl.SetTextAlignment("TOP", "CENTER")
			xlbl.Tag = list
			p.AddView(xlbl, p.Width - dWidth -2dip, p.Height / 2 - 15dip, dWidth, 35dip)
			Dim r As Reflector
			r.Target = lbl
			r.SetOnTouchListener("lbl_Touch")
		End If
	Next
End Sub

Private Sub lbl_Touch(ViewTag As Object, Action As Int, X As Float, Y As Float, EventData As Object) As Boolean
	Dim lbl As B4XView = Sender
	Dim list As CustomListView = lbl.Tag
	If Action = 0 Then									'ACTION_DOWN
		Dim r As Reflector
		r.Target = list.sv
		r.RunMethod2("requestDisallowInterceptTouchEvent", True, "java.lang.boolean")
		pnl = list.GetPanel(list.GetItemFromView(lbl)).Parent
		pnl.GetView(0).SetColorAndBorder(xui.Color_Transparent, 3dip, 0xFF503ACD, 0)
		ListStartY = Y + lbl.Top + pnl.Top
		pnl.BringToFront
		Top = pnl.Top
	Else If Action = 1 Then								'ACTION_UP
		Dim index As Int = list.GetItemFromView(lbl)
		pnl = list.GetPanel(index).Parent
		Dim Offset As Int = pnl.Top + pnl.Height / 2
		Dim NewIndex As Int = list.FindIndexFromOffset(Offset)
		Dim UnderlyingItem As CLVItem = list.GetRawListItem(NewIndex)
		If Offset - UnderlyingItem.Offset > UnderlyingItem.Size / 2 Then
			NewIndex = NewIndex + 1
		End If
		Dim ActualItem As B4XView  = pnl.GetView(0)
		ActualItem.RemoveViewFromParent
		ActualItem.SetColorAndBorder(pnl.Color, 0dip, xui.Color_Black, 0)
		Dim RawItem As CLVItem = list.GetRawListItem(index)
		list.RemoveAt(index)
		If NewIndex > index Then
			NewIndex = NewIndex - 1
		End If
		NewIndex = Max(0, Min(list.Size, NewIndex))
		list.InsertAt(NewIndex, ActualItem, RawItem.Value)
		list.GetRawListItem(NewIndex).TextItem = RawItem.TextItem
	Else If Action = 2 Then 							'ACTION_MOVE
		Dim index As Int = list.GetItemFromView(lbl)
		If pnl.Top < list.sv.ScrollViewOffsetY Then
			list.sv.ScrollViewOffsetY = Max(0, list.sv.ScrollViewOffsetY - 10dip)
		Else If list.sv.ScrollViewOffsetY + list.sv.Height < pnl.Top + pnl.Height Then
			list.sv.ScrollViewOffsetY = list.sv.ScrollViewOffsetY + 10dip
		End If
		Dim ListY As Int = Y + lbl.Top 	+ pnl.Top
		Dim delta As Int = ListY - ListStartY
		pnl.Top = Top + delta
	Else
		'Log("action " & Action)
	End If
	Return True
End Sub


9. CLVDragger changes
Now we need to add some code to the pages that contain a CustomListView.
In the CategoryPage tab:
Class_Globals:
B4XPage_Created:
In clv2_ItemClick:
In btn2_Click:
In the load_list subroutine:
In the SubcategoryPage tab.
Change the same code (highlighted code above). Make sure to change the CustomListView variable to the correct name.
In the B4XMainPage tab.
Change the same code (highlighted code above). Make sure to change the CustomListView variable to the correct name. The clv1_ItemLongClick is used for the change or delete of items.
Now the user can change the order of the categories, subcategories and items.
The comboboxes in the B4XMainPage will reflect the order from the Category page and the Subcategory page. So if you want the shops to appear when you start the app then make sure the shops category is the first in the list of categories (in the Category page).
You can order the items in a sequence that is convenient to you.
To wrap it up you can change the mode in the IDE to Release and run the app from the IDE.
The app is installed permenantly (until you remove it) and doesn’t need the debugger any more.
This app is for personal use only. The package is signed with a debug key!
' in Class_Globals
Private dragger As CLVDragger
' in B4XPage_Created
dragger.Initialize(clv2)
dragger.AddDragButtons

' in clv2_ItemClick
	…
	If Result = xui.dialogResponse_Positive Then
		'dialog.Show(ftf1.Text, "OK", "", "")
		If ftf1.Text <> "" Then
			clv2.RemoveAt(Index)
			clv2.InsertAtTextItem(Index,ftf1.text,ftf1.text)
			dragger.Initialize(clv2)
			dragger.AddDragButtons
		End If
	…
' In btn2_Click
	…
	If Result = xui.dialogResponse_Positive Then
		If edt1.Text <> "" Then
			clv2.AddTextItem(edt1.text,edt1.text)
			dragger.Initialize(clv2)
			dragger.AddDragButtons
			If clv2.Size > 0 Then
				save_list
			End If
		End If
	End If
	…

Sub load_list
	Private lst As List
	lst.Initialize
	If (File.Exists(File.DirInternal,"category.txt")) Then
		lst = File.ReadList(File.DirInternal,"category.txt")
	End If
	For i = 0 To lst.Size-1
		clv2.AddTextItem(lst.Get(i), lst.Get(i))
	Next
	If lst.Size > 0 Then
		dragger.Initialize(clv2)
		dragger.AddDragButtons
	End If
End Sub