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:
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:
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:
svArtist.Initialize2(0, "svArtist") pnlContent.AddView(svArtist, 0, 0, pnlContent.Width, pnlContent.Height)
lstArtist.Initialize(Me, svArtist, "", "lstArtist_Click", "", 1dip)
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:
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
4 (pressed): the user just pressed the item
1 (extended): the item has been extended (we will see later how to fill the extension)
I don't forget in CreateArtistList to set the color of the divider separating items:
lstArtist.DividerColor = Colors.Black
To fill my list, I need a function creating items:
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:
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:
Dim pnlArtist As Panel pnlArtist = CreateArtistItem("Big Country", "big_country_square.jpg", 1) lstArtist.AddCustomItem("BigCountry1", pnlArtist, 65dip)
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:
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.
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:
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:
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
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:
Dim spFastScrollArtist As ClsScrollPanel Dim lblFirstLetter As Label
spFastScrollArtist.Initialize(svArtist, 60dip, 52dip, False) 'No cache spFastScrollArtist.ReplaceBackground(spFastScrollArtist.LoadDrawable("scrollbar_handle_accelerated_anim2"))
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)
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:
spFastScrollArtist.GetLabel.Visible = False lblFirstLetter.Visible = False 'Initially, the Label is hidden spFastScrollArtist.SetOnShowHideEvent(Me, "spArtist_ShowHide") spFastScrollArtist.SetOnTextUpdatedEvent(Me, "spArtist_TextUpdated")
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
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...