Xamarin par l’exemple : “Metsys Music Search” 3/3

Il y a plus de 2 ans, Microsoft annonçait le rachat de Xamarin. Aujourd’hui, le résultat de cette alliance aboutit à une énorme gamme d’outils rendant le développement “Cross-Platform” plus rapide et plus facile pour les développeurs.

La partie 1 se trouve ici : Xamarin par l’exemple : “Metsys Music Search” 1/3
La partie 2 se trouve ici : Xamarin par l’exemple : “Metsys Music Search” 2/3

8. Mise en place de la liste des albums :

8.1 Ajout des ressources :

Afin de finaliser le projet, nous allons rajouter les ressources manquantes qui permettront un rendu plus graphique des listes d’artistes et albums. Rajoutez les éléments ci-dessous dans le répertoire “Resources” du projet “Metsys.Music.Search” :

calendar.png, disc.png, httppng.png, follow.png

(ne pas oublier de mettre “Ressource incorporée” dans “Action de génération”).

8.2 Création de la vue “TrackView” :

Comme pour la vue “ArtistView”, ajoutez un nouvel élément de type Xamarin.Forms et nommez le “TrackView.xaml”. Remplacez le code XAML existant par celui-ci dessous :

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:extension="clr-namespace:Metsys.Music.Search.Extensions"
             xmlns:converter="clr-namespace:Metsys.Music.Search.Converter"
             xmlns:controls="clr-namespace:Metsys.Music.Search.Controls"
             x:Class="Metsys.Music.Search.Views.ArtistView"
             NavigationPage.HasNavigationBar="False">

    <ContentPage.Resources>
        <ResourceDictionary>
            <converter:SelectedItemImageConverter x:Key="SelectedItemImageConverter" />
            <converter:SelectedItemBooleanConverter x:Key="SelectedItemBooleanConverter" />
            <converter:InvertSelectedItemBooleanConverter x:Key="InvertSelectedItemBooleanConverter" />
            <converter:InvertBooleanConverter x:Key="InvertBooleanConverter" />
            <converter:BooleanToOpacityConverter x:Key="BooleanToOpacityConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid>

        <Grid IsEnabled="{Binding Source={x:Reference indicator}, Path=IsRunning, Converter={StaticResource InvertBooleanConverter}}"
              Opacity="{Binding Source={x:Reference indicator}, Path=IsRunning, Converter={StaticResource BooleanToOpacityConverter}}">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition Height="2*"/>
            </Grid.RowDefinitions>

            <!--Background Image and navigation-->
            <Image Source="{extension:ImageResource Source=Metsys.Music.Search.Resources.fondbanner.png}" 
               IsVisible="{Binding Source={x:Reference lst}, Path=SelectedItem, Converter={StaticResource InvertSelectedItemBooleanConverter}}"
               Aspect="AspectFill" />
            <Image Source="{Binding Source={x:Reference lst}, Path=SelectedItem, Converter={StaticResource SelectedItemImageConverter}}"                
               IsVisible="{Binding Source={x:Reference lst}, Path=SelectedItem, Converter={StaticResource SelectedItemBooleanConverter}}"
               Aspect="AspectFill" Opacity=".3" Grid.RowSpan="3"/>

            <controls:ButtonImage Icon="{extension:ImageResource Source=Metsys.Music.Search.Resources.backbutton.png}" Label="Back"
                    HeightDezired="32" WidthDezired="32" Margin="30" VerticalOptions="Start">
                <controls:ButtonImage.IsVisible>
                    <OnPlatform x:TypeArguments="x:Boolean">
                        <On Platform="iOS, Android" Value="true" />
                        <On Platform="UWP" Value="false" />
                    </OnPlatform>
                </controls:ButtonImage.IsVisible>
                <controls:ButtonImage.GestureRecognizers>
                    <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped_1" />
                </controls:ButtonImage.GestureRecognizers>
            </controls:ButtonImage>

            <!-- List with artists-->
            <ListView x:Name="lst" ItemsSource="{Binding ListeArtists}" Grid.Row="1" Grid.RowSpan="3"
                      VerticalOptions="FillAndExpand" SeparatorVisibility="None"
                      SelectionMode="Single" 
                      HorizontalOptions="FillAndExpand" Margin="0,-40,0,20" 
                      BackgroundColor="Transparent" RowHeight="170"
                      ItemSelected="lst_ItemSelected">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Frame BackgroundColor="White" Margin="10" 
                               BorderColor="#FFC7ADE5"
                               HasShadow="True">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition />
                                        <ColumnDefinition Width="2*"/>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition />
                                        <RowDefinition />
                                        <RowDefinition />
                                        <RowDefinition Height="auto"/>
                                    </Grid.RowDefinitions>

                                    <Image Source="{Binding picture_medium}" Grid.RowSpan="3" Aspect="AspectFit"/>

                                    <Label Text="{Binding name}" LineBreakMode="TailTruncation" Grid.Column="1" FontAttributes="Bold"
                                       TextColor="#FF783CBD"  HorizontalOptions="StartAndExpand" VerticalOptions="Start">
                                        <Label.FontSize>
                                            <OnPlatform x:TypeArguments="x:Double">
                                                <On Platform="iOS, Android" Value="17" />
                                                <On Platform="UWP" Value="18" />
                                            </OnPlatform>
                                        </Label.FontSize>
                                    </Label>

                                    <Grid Grid.Row="1" Grid.Column="1">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="auto"/>
                                            <ColumnDefinition Width="2*"/>
                                            <ColumnDefinition Width="auto"/>
                                            <ColumnDefinition/>
                                        </Grid.ColumnDefinitions>

                                        <Image  Source="{extension:ImageResource Source=Metsys.Music.Search.Resources.follow.png}" 
                                        HorizontalOptions="Start" VerticalOptions="CenterAndExpand" Margin="5"/>
                                        <Label Grid.Column="1" Text="{Binding nb_fan}"  VerticalOptions="CenterAndExpand"/>

                                        <Image Grid.Column="2" Source="{extension:ImageResource Source=Metsys.Music.Search.Resources.disc.png}" 
                                        HorizontalOptions="Start" VerticalOptions="CenterAndExpand" Margin="5"/>
                                        <Label Grid.Column="3" Text="{Binding nb_album}" VerticalOptions="CenterAndExpand"/>

                                    </Grid>


                                    <Label Grid.Column="1" Grid.Row="2" HorizontalOptions="StartAndExpand">
                                        <Label.FormattedText>
                                            <FormattedString>
                                                <Span Text="View all albums and tracks" TextColor="#FFBC9C16" FontSize="14">
                                                    <Span.GestureRecognizers>
                                                        <TapGestureRecognizer BindingContext="{Binding id}"/>
                                                    </Span.GestureRecognizers>
                                                </Span>
                                            </FormattedString>
                                        </Label.FormattedText>
                                    </Label>

                                    <Label Grid.Row="3" Grid.ColumnSpan="2">
                                        <Label.FormattedText>
                                            <FormattedString>
                                                <Span Text="{Binding link}" TextColor="#FFBC9C16" FontSize="12">
                                                    <Span.GestureRecognizers>
                                                        <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
                                                    </Span.GestureRecognizers>
                                                </Span>
                                            </FormattedString>
                                        </Label.FormattedText>
                                    </Label>


                                </Grid>
                                <Frame.CornerRadius>
                                    <OnPlatform x:TypeArguments="x:Single">
                                        <On Platform="iOS, Android" Value="7" />
                                    </OnPlatform>
                                </Frame.CornerRadius>
                            </Frame>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>               
            </ListView>

        </Grid>

        <ActivityIndicator x:Name="indicator" IsRunning="False" Color="#FF783CBD" 
                           IsVisible="False" VerticalOptions="Center" HorizontalOptions="Center">
            <ActivityIndicator.HeightRequest>
                <OnPlatform x:TypeArguments="x:Double">
                    <On Platform="UWP" Value="250" />
                    <On Platform="Android" Value="70" />
                    <On Platform="iOS" Value="200" />
                </OnPlatform>
            </ActivityIndicator.HeightRequest>
            <ActivityIndicator.WidthRequest>
                <OnPlatform x:TypeArguments="x:Double">
                    <On Platform="UWP" Value="250" />
                    <On Platform="Android" Value="70" />
                    <On Platform="iOS" Value="200" />
                </OnPlatform>
            </ActivityIndicator.WidthRequest>
        </ActivityIndicator>

    </Grid>

</ContentPage>

Rajoutez dans le “code-behind” les deux méthodes ci-dessous :

using System;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Metsys.Music.Search.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
	public partial class TrackView : ContentPage
	{
		public TrackView ()
		{
			InitializeComponent ();
		}

        private void TapGestureRecognizer_Tapped_1(object sender, EventArgs e)
        {            
            Navigation.PopAsync();
        }
    }
}

8.3 Selection et Navigation :

Il ne reste plus qu’à initialiser la sélection de l’artiste. L’application effectuera, via le service web, une recherche de ses albums et naviguera vers la vue “TrackView” qui affichera les résultats obtenus.

Ouvrez le fichier “ArtistView.xaml” et rajoutez les éléments surlignés ci-dessous :

                                        <Label Grid.Column="3" Text="{Binding nb_album}" VerticalOptions="CenterAndExpand"/>

                                    </Grid>


                                    <Label Grid.Column="1" Grid.Row="2" HorizontalOptions="StartAndExpand">
                                        <Label.FormattedText>
                                            <FormattedString>
                                                <Span Text="View all albums and tracks" TextColor="#FFBC9C16" FontSize="14">
                                                    <Span.GestureRecognizers>
                                                        <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped_3" BindingContext="{Binding id}"/>
                                                    </Span.GestureRecognizers>
                                                </Span>
                                            </FormattedString>
                                        </Label.FormattedText>
                                    </Label>

                                    <Label Grid.Row="3" Grid.ColumnSpan="2">
                                        <Label.FormattedText>
                                            <FormattedString>
                                                <Span Text="{Binding link}" TextColor="#FFBC9C16" FontSize="12">
                                                    <Span.GestureRecognizers>

Rajoutez dans le “code-behind” les trois méthodes ci-dessous :

        private async Task<List<AlbumInfo>> GetListAlbums(string id)
        {
            try
            {
                if (!String.IsNullOrEmpty(id))
                {
                    List<AlbumInfo> albums = await Core.GetAlbums(id);
                    return albums;
                }

                return null;
            }
            catch
            {
                return null;
            }
        }

        private void SetBusy(bool OnOff)
        {
            this.indicator.IsRunning = OnOff;
            this.indicator.IsVisible = OnOff;
        }

        private async void TapGestureRecognizer_Tapped_3(object sender, EventArgs e)
        {

            ArtistInfo artist = ((ArtistInfo)((Label)sender).FormattedText.BindingContext);

            if (artist.Equals(artistSelected))
            {
                SetBusy(true);

                var albums = await GetListAlbums(artist.id);

                if (albums != null)
                {
                    TrackView playPage = new TrackView();
                    playPage.BindingContext = new TrackViewModel() { ListeAlbums = albums, artistName = artist.name, artistPicture = artist.picture_medium };

                    await Navigation.PushAsync(playPage, true);
                    SetBusy(false);
                }
            }
            else
            {
                this.lst.SelectedItem = artist;
            }
        }

8.4 Tests et comparaison sur les appareils :

Exécutez l’application sur les trois plateformes (iOS, Android et Windows10) :

      

9. Harmonisation de la vue “TrackView” :

Comme pour la vue “ArtistView”, nous allons harmoniser l’image en entête pour l’afficher sous forme de bulle. Pour cela, nous allons installer un plugin qui nous permettra de personnaliser le contrôle “image”. Sélectionnez la solution et ouvrez le gestionnaire de Nugets : rajoutez “Xam.Plugins.Forms.ImageCircle” à tous les projets :

Une fois le composant installé, il ne reste plus qu’a changer l’image comme ci-dessous :

            <ci:CircleImage Source="{Binding artistPicture}" Aspect="AspectFill" HorizontalOptions="End" VerticalOptions="Start" Margin="0,20,20,0">                
                <ci:CircleImage.IsVisible>
                    <OnPlatform x:TypeArguments="x:Boolean">
                        <On Platform="iOS, Android" Value="true" />
                        <On Platform="UWP" Value="false" />
                    </OnPlatform>
                </ci:CircleImage.IsVisible>
            </ci:CircleImage>

            <ci:CircleImage Source="{Binding artistPicture}" Aspect="AspectFill" HorizontalOptions="End" VerticalOptions="Start" Margin="0,20,20,0">
                <ci:CircleImage.HeightRequest>
                    <OnPlatform x:TypeArguments="x:Double">                        
                        <On Platform="UWP" Value="100" />
                    </OnPlatform>
                </ci:CircleImage.HeightRequest>
                <ci:CircleImage.WidthRequest>
                    <OnPlatform x:TypeArguments="x:Double">
                        <On Platform="UWP" Value="100" />
                    </OnPlatform>
                </ci:CircleImage.WidthRequest>
                <ci:CircleImage.IsVisible>
                    <OnPlatform x:TypeArguments="x:Boolean">
                        <On Platform="iOS, Android" Value="false" />
                        <On Platform="UWP" Value="true" />
                    </OnPlatform>
                </ci:CircleImage.IsVisible>
            </ci:CircleImage>

Vous remarquerez que nous changeons la taille pour la partie Windows10 afin d’avoir l’image plus grande que sur des périphériques de type téléphone.

Il ne reste plus qu’a initialiser pour chaque plateforme le composant en rajoutant “ImageCircleRenderer.Init();” après chaque instantiation de “Xamarin.Forms” comme ci-dessous :

  • UWP : App.xaml.cs -> Xamarin.Forms.Forms.Init(e);
  • iOS : AppDelegate.cs -> global::Xamarin.Forms.Forms.Init();
  • Android : MainActivity.cs -> global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

Voici le résultat obtenu sur les 3 plateformes (Windows10, iOS et Android) :

      

10. Un peu de musique pour terminer :

Nous allons terminer en jouant un extrait de l’album. Pour cela nous allons utiliser un plugin comme pour la partie précédente :

Dans le fichier “TrackView.xaml.cs”, décommentez la ligne comme ci-dessous :

<Label Grid.Column="1" Grid.Row="2" HorizontalOptions="StartAndExpand">
                                        <Label.FormattedText>
                                            <FormattedString>
                                                <Span Text="Play sample" TextColor="#FFBC9C16" FontSize="14">
                                                    <Span.GestureRecognizers>
                                                        <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped_3" BindingContext="{Binding link}"/>
                                                    </Span.GestureRecognizers>
                                                </Span>
                                            </FormattedString>
                                        </Label.FormattedText>
                                    </Label>

Et rajoutez le “code-behind” ci-dessous :

        protected override bool OnBackButtonPressed()
        {
            var player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
            if (player.IsPlaying) player.Stop();
            
            return base.OnBackButtonPressed();
        }

        private void TapGestureRecognizer_Tapped_1(object sender, EventArgs e)
        {            
            var player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
            if (player.IsPlaying) player.Stop();
            Navigation.PopAsync();
        }
        
        private async void TapGestureRecognizer_Tapped_3(object sender, EventArgs e)
        {
            AlbumInfo album = ((Label)sender).FormattedText.BindingContext as AlbumInfo;

            if (album !=null)
            {
                SetBusy(true);

                var sample = await GetSample(album.id);

                if (!string.IsNullOrEmpty(sample))
                {

                    var audioStream = await DataService.getStreamFromService(sample);

                    var player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
                    if (player.IsPlaying) player.Stop();
                    player.Load(audioStream);
                    player.Play();
                    
                    SetBusy(false);
                }
            }            
        }

        private async Task<string> GetSample(string id)
        {
            try
            {
                if (!String.IsNullOrEmpty(id))
                {
                    string sample = await Core.GetSample(id);
                    return sample;
                }

                return string.Empty;
            }
            catch
            {
                return string.Empty;
            }
        }

        private void SetBusy(bool OnOff)
        {
            this.indicator.IsRunning = OnOff;
            this.indicator.IsVisible = OnOff;
        }

N’oubliez pas de rajouter l’assembly “using System.Threading.Tasks;”.

Et voilà, en cliquant sur “Play sample”, le service web ira récupérer un extrait de 30 secondes et le jouera.

11. Conclusion :

Nous avons créer un application multiplateforme en utilisant un seul code et une seule source d’interface utilisateur pour les 3 appareils Windows10, iOS et Android. Ensuite nous avons pu personnaliser les composants natifs suivant le type d’appareil et personnaliser le rendu graphique. Et enfin nous avons vu l’utilisation de package Nuget (il en existe des milliers) permettant l’implémentation et l’utilisation des capacités des appareils.

Et voici le résultat pour l’ensemble des appareils :

 

A propos de l'auteur

Je suis Architecte Technique et passionné par le développement mobile et la réalité augmentée. Je baigne dans l’univers Microsoft depuis maintenant une vingtaine d’année.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.