Comment font-ils ? #3
Ce troisième tutoriel revient sur l'interface de PlayerPro de BlastOn LLC. Nous allons reproduire la liste des artistes et le fonctionnement des vues personnalisées de l'égaliseur. Nous allons aussi ajouter la fonction de glisser/déposer (drag & drop) à la barre d'onglets et déporter l'affichage du ScrollPanel.
Cet article suppose que vous connaissez déjà les bases du développement avec B4A et que vous avez lu les deux premiers tutoriels.
Deuxième partie: la liste des artistes
Nous avons vu dans le tutoriel précédent comment reproduire la mise en page générale de l'application. Je ne vais donc m'intéresser cette fois-ci qu'à la création de la liste des artistes. Elle présente une particularité: en cliquant sur un artiste, on peut faire apparaître la liste des albums en dessous. Dans l'API d'Android, cette vue est une ExpandableListView que nous pouvons reproduire sans problème avec la classe CheckList.
Dans le premier tutoriel, je vous avais montré comment fabriquer un masque d'item dans le designer et comment le charger dans l'application. Je ne vais pas utiliser cette méthode ici car j'ai besoin d'un maximum de rapidité. Il faut savoir que le chargement d'un fichier .bal avec LoadLayout est très lent. J'ai chronométré dans plusieurs projets les deux méthodes (chargement de vues créées dans le designer vs. création dans le code) et la différence varie de 5 à 7 suivant le projet. Dans le cas présent, mon appareil (un Huawei Honor monocore 1,4 Ghz) met 35 secondes pour construire 1000 entrées dans la liste des artistes avec la première méthode (chargement de vues créées dans le designer) et seulement 5 avec la deuxième (création dans le code). Je conseille donc d'éviter d'utiliser LoadLayout dans une boucle. Cela dit, vous pouvez quand même utiliser le designer pour faire du prototypage (la maquette visuelle vous aide à choisir les bonnes valeurs des propriétés des vues, notamment les propriétés de positionnement et de dimension).
Maquette pour l'item de la liste des artistes:
Avant de créer mes vues, je dois modifier mon gestionnaire d'évènements (voir le tutoriel précédent) pour appeler ma nouvelle fonction 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
Dans CreateArtistList, je commence par charger les images statiques en mémoire, c'est-à-dire les images qui ne sont pas propres à un item (l'image de fond, l'icône indiquant qu'une extension est possible et l'icône après extension). Cela m'évitera de lire des fichiers à chaque création d'item. Cela m'assure aussi qu'il n'y a qu'une seule copie en mémoire de ces images:
B4X:
bdDefaultBackground.Initialize(LoadBitmap(File.DirAssets, "bg_liste.png"))
bdCanExpandArrow.Initialize(LoadBitmap(File.DirAssets, "can_expand.png"))
bdExpandedArrow.Initialize(LoadBitmap(File.DirAssets, "expanded.png"))
Je crée ensuite le ScrollView qui héberge la liste et permet de la faire défiler:
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
Pour reproduire l'apparence personnalisée de la liste de PlayerPro, je dois gérer moi-même l'apparence de la liste et ne pas laisser cette gestion à la classe (par défaut, celle-ci ne gère pas les indicateurs d'extension). Je vais donc lui indiquer que je prends la main en définissant le gestionnaire des évènements OnPaint:
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
Désormais, à chaque fois qu'un item de la liste va changer d'état, je vais en être informé et je vais pouvoir modifier son apparence en conséquence. Je dois gérer au moins trois états:
0 (default): l'item n'est ni étendu, ni touché
Je n'oublie pas dans CreateArtistList de définir la couleur du séparateur entre les items:
B4X:
lstArtist.DividerColor = Colors.Black
Pour remplir ma liste, j'ai besoin d'une fonction de création d'item:
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
Ma fonction fait appel à une autre fonction, Ellipsize, qui ajoute des points de suspension si le nom de l'artiste est trop 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
Je remplis ma liste en faisant appel à CreateArtistItem. Exemple:
B4X:
Dim pnlArtist As Panel
pnlArtist = CreateArtistItem("Big Country", "big_country_square.jpg", 1)
lstArtist.AddCustomItem("BigCountry1", pnlArtist, 65dip)
B4X:
lstArtist.ResizePanel
Reste le cas des extensions. J'ai indiqué lors de l'initialisation de ma liste que je voulais gérer l'évènement OnClick. J'écris le gestionnaire:
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
L'extension est remplie avec la fonction AddAlbumItems qui ne sera pas détaillée ici. Cette fonction fait appel à une fonction CreateAlbumItem qui est très semblable à CreateArtistItem.
Résultat:
Troisième partie: les petits plus de l'interface
L'interface de PlayerPro a deux caractéristiques que nous allons reproduire: la possibilité de changer l'ordre des boutons de la barre d'onglets en faisant un clic long et l'affichage de la première lettre du nom de l'artiste quand on utilise la poignée de défilement rapide.
La classe ActionBar que nous avons utilisée pour les onglets propose une fonctionnalité de glisser/déposer (drag & drop). Il suffit de la mettre en oeuvre.
Je commence par modifier l'initialisation de mes boutons pour indiquer la prise en charge du clic long (il est inutile dans le cas présent d'avoir des gestionnaires personnalisés; tous les boutons peuvent avoir le même). Exemple:
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")
J'écris le gestionnaire du clic long:
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
Exemple de glisser/déposer:
Passons maintenant à la poignée de défilement rapide. On veut qu'elle affiche la première lettre de l'artiste dans un Label séparé. C'est assez simple avec la classe ScrollPanel. Je déclare cette classe et le Label dans 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
Problème: la première lettre s'affiche sur le ScrollPanel au lieu du Label et le Label reste affiché en permanence. Pour remédier à tout ça, j'ajoute dans 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à:
À noter: j'ai initialisé le ScrollPanel sans activer son cache. Ce n'est pas gênant ici car la liste est petite, mais si votre liste fait plus d'une centaine d'élements, je vous conseille vivement de l'activer. Inconvénient: il faudra appeler RefreshCache à chaque modification du contenu ou de l'ordre de la liste (ce qui, dans le cas présent, ne devrait pas arriver).
4e et dernière partie dans le message suivant...
Attachments
-
PlayerPro_0.jpg51.8 KB · Views: 4,456
-
PlayerPro_1.png10.4 KB · Views: 4,423
-
PlayerPro_3.jpg34.9 KB · Views: 4,419
-
PlayerPro_4.png21.1 KB · Views: 4,385
-
PlayerPro_5.jpg23.3 KB · Views: 4,426
-
PlayerPro_20.png70.5 KB · Views: 4,470
-
PlayerPro_21.png57.4 KB · Views: 4,430
-
PlayerPro_24.png71.7 KB · Views: 4,456