How do they... ? #3
<Version française/french version>
This third tutorial returns to the interface of PlayerPro of BlastOn LLC. We are going to reproduce the artists list and the operation of the customized views of the equalizer. We are also going to add the drag & drop functionality to the tabs bar and deport the ScrollPanel display.
This article assumes that you already know the bases of the development with B4A and that you have read the first two tutorials.
Second part: the artists list
We saw in the previous tutorial how to reproduce the general layout of the application. I'm going to focus this time on the creation of the artists list. It has a noticeable feature: by clicking on the artist, you can expand a list of albums below. In the Android API, this view is a ExpandableListView that we can reproduce easily with the class CheckList.
In the first tutorial, I showed you how to make a mask for an item in the designer and how to load it into the application. I am not going to use this method here because I need maximum speed. You should know that loading a .bal file with LoadLayout is very slow. I timed both methods (loading views created in the designer vs. creation in the code) in several projects and the difference varies from 5 to 7 depending on the project. In this case, my device (a Huawei Honor with a single core 1.4 Ghz) takes 35 seconds to build 1000 entries in the artists list with the first method (loading views created in the designer) and only 5 with the second (creation in the code). So, I advise to avoid using LoadLayout in a loop. That said, you can still use the designer to make models (the visual model helps you to choose the right values for the view properties, in particular Left, Top, Width and Height).
Model for an item of the artists list:
Before creating the views, I have to modify the events handler (cf. previous tutorial) to call my new function CreateArtistList:
B4X:
Sub Artists_Click(ActionBar As ClsActionBar, Btn As View)
If LastBtn <> Btn Then
TabChange(Btn, "tab_artists_defaut.png", "tab_artists_select.png")
CreateArtistList
End If
End Sub
In CreateArtistList, I start by loading the static images in memory, that is to say, the images that are not specific to an item (the background image, the icon indicating that an extension is possible and the icon after extension). This will avoid having to read files for each created item. This ensures also there is only one copy of these images in memory:
B4X:
bdDefaultBackground.Initialize(LoadBitmap(File.DirAssets, "bg_liste.png"))
bdCanExpandArrow.Initialize(LoadBitmap(File.DirAssets, "can_expand.png"))
bdExpandedArrow.Initialize(LoadBitmap(File.DirAssets, "expanded.png"))
Next, I create the ScrollView holding the list and allowing to scroll it:
B4X:
svArtist.Initialize2(0, "svArtist")
pnlContent.AddView(svArtist, 0, 0, pnlContent.Width, pnlContent.Height)
B4X:
lstArtist.Initialize(Me, svArtist, "", "lstArtist_Click", "", 1dip)
B4X:
Dim svArtist As ScrollView
Dim lstArtist As ClsCheckList
To reproduce the custom appearance of the PlayerPro list, I cannot leave the appearance management to the class (by default, it does not support the extension indicators). I let it know that I take control by setting the OnPaint listener:
B4X:
lstArtist.SetOnPaintListener("PaintArtist")
B4X:
Sub PaintArtist(pnlItem As Panel, State As Int)
Select State
Case 1 ' Extended
pnlItem.Color = Colors.RGB(120, 190, 30) 'Green
Dim ivExpandState As ImageView
ivExpandState = pnlItem.GetView(3)
ivExpandState.Background = bdExpandedArrow
Case 4 ' Pressed
pnlItem.Color = Colors.RGB(120, 190, 30) 'Green
Case Else
pnlItem.Background = bdDefaultBackground
Dim ivExpandState As ImageView
ivExpandState = pnlItem.GetView(3)
ivExpandState.Background = bdCanExpandArrow
End Select
End Sub
From now on, whenever an item in the list will change state, I will be informed and I can change its appearance accordingly. I have to manage at least three states:
0 (default): the item is not pressed or extended
I don't forget in CreateArtistList to set the color of the divider separating items:
B4X:
lstArtist.DividerColor = Colors.Black
To fill my list, I need a function creating items:
B4X:
Sub CreateArtistItem(Artist As String, PicFile As String, NbOfAlbums As Int) As Panel
Dim pnl As Panel
pnl.Initialize("")
pnl.Background = bdDefaultBackground
Dim ivPhoto As ImageView
ivPhoto.Initialize("")
ivPhoto.Gravity = Gravity.FILL
ivPhoto.Bitmap = LoadBitmap(strPicDir, PicFile)
pnl.AddView(ivPhoto, 0, 0, 65dip, 65dip)
Dim lblArtistName As Label
lblArtistName.Initialize("")
lblArtistName.TextColor = Colors.White
lblArtistName.TextSize = 18
lblArtistName.Typeface = Typeface.DEFAULT_BOLD
lblArtistName.Gravity = Gravity.TOP
lblArtistName.Text = Artist
Ellipsize(lblArtistName)
AddShadow(lblArtistName, Colors.Black)
pnl.AddView(lblArtistName, 75dip, 10dip, 100%x - 110dip, 25dip)
Dim lblNbOfAlbums As Label
lblNbOfAlbums.Initialize("")
lblNbOfAlbums.TextColor = Colors.RGB(220, 220, 220)
lblNbOfAlbums.TextSize = 14
lblNbOfAlbums.Gravity = Gravity.BOTTOM
If NbOfAlbums > 1 Then
lblNbOfAlbums.Text = NbOfAlbums & " albums"
Else
lblNbOfAlbums.Text = "1 album"
End If
AddShadow(lblNbOfAlbums, Colors.Black)
pnl.AddView(lblNbOfAlbums, 75dip, 34dip, 100%x - 110dip, 20dip)
Dim ivExpandState As ImageView
ivExpandState.Initialize("")
ivExpandState.Gravity = Gravity.FILL
ivExpandState.Background = bdCanExpandArrow
pnl.AddView(ivExpandState, 100%x - 36dip, 10dip, 32dip, 32dip)
Return pnl
End Sub
My function calls another function, Ellipsize, which adds an ellipsis if the artist name is too long:
B4X:
Sub Ellipsize(lbl As Label)
Dim r As Reflector
r.Target = lbl
r.RunMethod2("setLines", 1, "java.lang.int")
r.RunMethod2("setHorizontallyScrolling", True, "java.lang.boolean")
r.RunMethod2("setEllipsize", "END", "android.text.TextUtils$TruncateAt")
End Sub
I fill my list using CreateArtistItem. Example:
B4X:
Dim pnlArtist As Panel
pnlArtist = CreateArtistItem("Big Country", "big_country_square.jpg", 1)
lstArtist.AddCustomItem("BigCountry1", pnlArtist, 65dip)
B4X:
lstArtist.ResizePanel
What remains is the case of extensions. I indicated while I initialized my list that I wanted to handle the OnClick event. I write the handler:
B4X:
Sub lstArtist_Click(pnlItem As Panel, ID As Object)
If lstArtist.HasExtraContent AND lstArtist.ExtendedItemID = ID Then
lstArtist.CollapseItem
Else
Dim pnlAlbums As Panel
pnlAlbums.Initialize("")
Dim NbOfAlbums As Int
NbOfAlbums = AddAlbumItems(pnlAlbums, ID)
lstArtist.ExtendItem(ID, pnlAlbums, 65dip * NbOfAlbums)
End If
End Sub
The extension is filled with AddAlbumItems which will not be detailed here. This function uses a CreateAlbumItem function which is very similar to CreateArtistItem.
Result:
Third part: the little extras
The PlayerPro interface has two characteristics that we are going to reproduce: the ability to change the order of buttons on the tab bar by a long click and the display of the first letter of the artist name when using the fast scroll handle.
The ActionBar class that I used for the tabs has a drag & drop feature. I just have to implement it.
I start by changing the initialization of my buttons to indicate that I want to handle long clicks (no need in this case to have custom handlers, all buttons can have the same). Example:
B4X:
aBtn(0) = abTabs.AddButton(LoadBitmap(File.DirAssets, "tab_artists_defaut.png"), "", 2, 1, "Artists_Click", "Btn_LongClick")
aBtn(1) = abTabs.AddButton(LoadBitmap(File.DirAssets, "tab_albums_defaut.png"), "", 2, 2, "Albums_Click", "Btn_LongClick")
I write the long click handler:
B4X:
Sub Btn_LongClick(ActionBar As ClsActionBar, Btn As View)
If LastBtn <> Btn Then
'Replaces the transparent background by a solid one
Btn.Background = btnBackground
End If
abTabs.StartDragAndDrop(Btn, hsvTabBar, "Btn_AfterDrop")
End Sub
B4X:
Sub Btn_AfterDrop(ActionBar As ClsActionBar, Btn As View)
If LastBtn = Btn Then
'Selected btn: redraws the nine-patch erased after the drag&drop
Btn.Background = LoadNinePatchDrawable("bg_select")
End If
End Sub
Drag & drop in action:
Now, let's see the fast scroll handle. We want it to display the first letter of the artist name in a separate label. That is quite easy with the ScrollPanel class. I declare this class and the Label in Globals:
B4X:
Dim spFastScrollArtist As ClsScrollPanel
Dim lblFirstLetter As Label
B4X:
spFastScrollArtist.Initialize(svArtist, 60dip, 52dip, False) 'No cache
spFastScrollArtist.ReplaceBackground(spFastScrollArtist.LoadDrawable("scrollbar_handle_accelerated_anim2"))
B4X:
lblFirstLetter.Initialize("")
lblFirstLetter.Background = spFastScrollArtist.LoadDrawable("toast_frame")
lblFirstLetter.Gravity = Gravity.CENTER_HORIZONTAL + Gravity.CENTER_VERTICAL
lblFirstLetter.TextSize = 20
lblFirstLetter.TextColor = Colors.White
lblFirstLetter.Typeface = Typeface.DEFAULT_BOLD
pnlContent.AddView(lblFirstLetter, (lstArtist.getWidth - 70dip) / 2, (lstArtist.getHeight - 70dip) / 2, 70dip, 70dip)
B4X:
Sub svArtist_ScrollChanged(Position As Int)
spFastScrollArtist.DisplayFirstChar(Position)
End Sub
Problem: the first letter is displayed on the ScrollPanel instead of the Label and the Label is displayed permanently. To remedy that, I add in CreateArtistList:
B4X:
spFastScrollArtist.GetLabel.Visible = False
lblFirstLetter.Visible = False 'Initially, the Label is hidden
spFastScrollArtist.SetOnShowHideEvent(Me, "spArtist_ShowHide")
spFastScrollArtist.SetOnTextUpdatedEvent(Me, "spArtist_TextUpdated")
B4X:
Sub spArtist_ShowHide(SenderSP As Object, Visible As Boolean)
If Visible = False Then
lblFirstLetter.Visible = False
Else
lblFirstLetter.Visible = spFastScrollArtist.ScrollPanelMovedByUser
End If
End Sub
Sub spArtist_TextUpdated(SenderSP As Object, Text As String)
lblFirstLetter.Text = Text
End Sub
Et voilà:
Note: I initialized the ScrollPanel without activating its cache. This is not a problem here because the list is small, but if your list contains more than a hundred of items, I urge you to turn it on. Disadvantage: you will have to call RefreshCache each time you change the content or order of the list (which in this case should not happen).
Fourth and last part in the next post...
Last edited: