Android Tutorial How they do... #3

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

attachment.php


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:
attachment.php


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"))
These three BitmapDrawables have been declared in Globals.

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)
Then I create the list itself:
B4X:
lstArtist.Initialize(Me, svArtist, "", "lstArtist_Click", "", 1dip)
The ScrollView and the list have been declared in Globals:
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")
Then I create the event handler:
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
attachment.php
4 (pressed): the user just pressed the item
attachment.php
1 (extended): the item has been extended (we will see later how to fill the extension)
attachment.php

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
It is in this function that I avoid to use LoadLayout. All views are created and set in the code.

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)
I finish my filling with:
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
This handler creates an extension when the item has been clicked and removes the extension if the item already has one.
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:
attachment.php


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
And the function called after the button is dropped:
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
The class redraws the button background when the operation ends. So I have to reload the 9-patch with bright color (cf. the second tutorial) if the button is the selected tab.

Drag & drop in action:
attachment.php


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
I initialize my class in CreateArtistList:
B4X:
spFastScrollArtist.Initialize(svArtist, 60dip, 52dip, False) 'No cache
spFastScrollArtist.ReplaceBackground(spFastScrollArtist.LoadDrawable("scrollbar_handle_accelerated_anim2"))
I initialize the Label and I place it in the center of the screen:
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)
Then I request the display of the first letter for each item:
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")
Then I create two event handlers that change the visibility and content of the Label:
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à:
attachment.php


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:

Mrjoey

Active Member
Licensed User
Longtime User
Tnx man i will try this , im now focusing on the file list and i have a quick question , i ve created a list containing all artists using
Artist <> oldartist , and its working well , now i want to click on that artist and display all songs by this artist , is that a quick way to do that? My ideas are lost tnx
 

Mrjoey

Active Member
Licensed User
Longtime User
i think
AddAlbumItems(pnlAlbums, ID) is missing , i would like to know how to add albums from MB and then fill the songs from this album?
 

Mrjoey

Active Member
Licensed User
Longtime User
yes , anyway i will figure out how to implement my point in my app tnx for help man
 

Mrjoey

Active Member
Licensed User
Longtime User
B4X:
For i = 0 To (m.Size / 9) - 1
        Artist = m.Get("Artist" & i)
        If Artist <> DernArtist Then
            lstAudio.AddHeader(Artist)
            DernArtist = Artist
        End If
it adds a header for each artist , i would like to press on that header and fill a list of its items , without adding all the list in the loop , is that possible?
 

Informatix

Expert
Licensed User
Longtime User
B4X:
For i = 0 To (m.Size / 9) - 1
        Artist = m.Get("Artist" & i)
        If Artist <> DernArtist Then
            lstAudio.AddHeader(Artist)
            DernArtist = Artist
        End If
it adds a header for each artist , i would like to press on that header and fill a list of its items , without adding all the list in the loop , is that possible?
I'm not sure that I still have the code of this tutorial (I can't find it) but AddAlbumItems was probably populating the panel of items with views. The contents was read with the MediaBrowser library by filtering the result of GetMediaAudioList (if Artist = "the artist that I want" Then).
 

Mrjoey

Active Member
Licensed User
Longtime User
can u give me an idea of doing this loop?
B4X:
For i = 0 To (m.Size / 9) - 1
        Artist = m.Get("Artist" & i)
        If Artist <> DernArtist Then
            lstAudio.AddHeader(Artist)
            DernArtist = Artist
        End If
        lstAudio.AddItemNoChkbx(m.Get("ID" & i), m.Get("Title" & i), m.Get("Album" & i),Null)
    Next

this loop add 1 header for 1 artist each and below a list of songs from this artist.
so in the filled map i have all artist names , so if i want to request lets say artist3 and then fill all songs from this artist3 how it could be? i think to make another map but how to fill it the way i can request artists by names and gives me the songs of the requested artist?
 

Informatix

Expert
Licensed User
Longtime User
can u give me an idea of doing this loop?
B4X:
For i = 0 To (m.Size / 9) - 1
        Artist = m.Get("Artist" & i)
        If Artist <> DernArtist Then
            lstAudio.AddHeader(Artist)
            DernArtist = Artist
        End If
        lstAudio.AddItemNoChkbx(m.Get("ID" & i), m.Get("Title" & i), m.Get("Album" & i),Null)
    Next

this loop add 1 header for 1 artist each and below a list of songs from this artist.
so in the filled map i have all artist names , so if i want to request lets say artist3 and then fill all songs from this artist3 how it could be? i think to make another map but how to fill it the way i can request artists by names and gives me the songs of the requested artist?
If I understand you well, you have to filter the result:
If Artist = "the artist that I want" Then
 

Mrjoey

Active Member
Licensed User
Longtime User
If I understand you well, you have to filter the result:
If Artist = "the artist that I want" Then
yeah tnx it helped and worked great , tnx for u passion :)
anyway i have an outofmemory error and i started a new thread now called "got outofmemory error" i appreciate if u took a look at it since the calling handle includes one of urs wich is "createscaledbitmap" thank u in advance :)
 

Mrjoey

Active Member
Licensed User
Longtime User
these are buttons with statelist bitmaps , actually u have to pick up a graphic design sush as this , the design should be in psd format and u open it with photoshop and export each layer a s png files and transparency enabled , then u open the designer in B4A and design each button after u copy the png files onto ur project files called "Files" , if u want more help i can help u , i think mr Informatix has a small example about how to create statelist buttons if im not wrong programmaticaly.....
 

LucaMs

Expert
Licensed User
Longtime User
I think that it is better to start a new thread for this question.

Yes, I think so too, I did not want to sully this thread, but I did not know where ("Questions" is for B4A, this is more a question about graphic).


Thanks for help, Mrjoey. I'll try to do something.
I do not need to get exactly that interface. It is my technical curiosity, which can be useful to many people, I think.

Great tutorial, Informatix, thanks
 
Last edited:

Mrjoey

Active Member
Licensed User
Longtime User
hey Mr Informatix , i need some help with this line
B4X:
If spMode.SelectedIndex = 0 Then
        Dim Ratio As Float
        Ratio = (MaxAngle - MinAngle) / 100
        Activity.Title = Angle & "°  " & Round((Angle - MinAngle) / Ratio) & "/100"
    Else
im trying to change a number between 20 and 20000 so
B4X:
Round((Angle - MinAngle) / Ratio) *20000
is giving me a changing of 75 per step so when the angle is -135 its giving 0 and - 134 is giving 74
i tried to divide the ratio by 20000 but the same result , is there any equation to change number between 20 to 20000 with step = 1
im using rotary knob mode , tnx
 
Top