Apache Adobe Flex TutorialTutoriaux Adobe Flex & AIR en Français

30août/112

AIR Mobile – Application TweetDeck (11) – Affichage d'un texte en fond lors du chargement des tweets

Lorsque les tweets sont chargés, les éléments de liste ne sont pas encore présents. L'espace est donc vide avec seulement la couleur de fond de la vue (blanc pour le moment). On va changer cela pour afficher un texte, qui correspond au titre de la colonne. Cela permet de remplir un peu l'écran et de faire patienter l'utilisateur.

Modification de la couleur de fond des listes / View

Ce que l'on voit quand on a pas encore la liste, c'est en fait la couleur de fond de la View. On va changer cela à l'aide d'une balise CSS à placer dans TweetDeck.mxml:

    s|View{
      contentBackgroundColor:#313031;
    }

Ajout d'un texte lors du chargement des tweets

Le comportement souhaité est assez simple. Lorsque l'on a des tweets, on affiche la liste, sinon on affiche un label. Le plus simple est d'empiler les 2 dans un Group et de contrôler leur visibilité.

On va créer un composant générique nommé "FeedList.mxml" qui hérite de Group dans le package "comps". Dans ce composant, on va mettre notre déclaration de List, notre Twitter service et notre CallResponder. Laissez Flash Builder faire les imports et les namespace à grand coup de Ctrl+Espace sur les classes:

<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
         xmlns:s="library://ns.adobe.com/flex/spark" xmlns:views="views.*"
         creationComplete="onCComplete(event)" xmlns:twitter="services.twitter.*">
  <fx:Script>
    <![CDATA[
      import mx.events.FlexEvent;
      protected function onCComplete(event:FlexEvent):void{
        search("Adobe");
      }
      protected function search(q:String):void{
        searchResult.token = twitter.search(q);
      }

    ]]>
  </fx:Script>
  <fx:Declarations>
    <s:CallResponder id="searchResult" />
    <twitter:Twitter id="twitter"/>
  </fx:Declarations>
  <s:List width="100%" height="100%" dataProvider="{searchResult.lastResult.results}"
          labelField="from_user">
    <s:itemRenderer>
      <fx:Component>
        <views:TweetRenderer iconField="profile_image_url"
                             labelField="from_user"
                             messageField="text">
        </views:TweetRenderer>
      </fx:Component>
    </s:itemRenderer>
  </s:List>
</s:Group>

Le code de notre vue principale est donc raccourci:

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:comps="comps.*"
        actionBarVisible="false">
  <s:layout>
    <s:VerticalLayout gap="0" />
  </s:layout>
  <comps:ColumnHeaderIndicator width="100%" height="40" />
  <comps:FeedList  width="100%" height="100%" />
  <comps:BottomBar width="100%" height="80" />
</s:View>

Il ne nous reste plus qu'à ajouter notre Label. En fait, on va ajouter 2 Labels légèrement décalés pour donner un effet d'ombre:

  </s:List>
  <s:Label id="labelDisplayShadow" verticalCenter="0" horizontalCenter="0" color="0x292C29"
           fontSize="40" visible="{searchResult.lastResult == null}"/>
  <s:Label id="labelDisplay" verticalCenter="2" horizontalCenter="0" color="0xBDBABD"
           fontSize="40" visible="{searchResult.lastResult == null}"/>
</s:Group>

Ici, on utilise un simple Binding pour détecter la présence ou non de résultat. Il ne nous reste plus qu'à afficher un texte. On va créer une variable à FeedList que l'on utilisera plus tard pour l'afficher comme tête de colonne:

[Bindable]
public var label:String = null;
...
<s:Label id="labelDisplayShadow" text="{label}" verticalCenter="0" horizontalCenter="0" color="0x292C29"
   fontSize="40" visible="{searchResult.lastResult == null}"/>
<s:Label id="labelDisplay" text="{label}" verticalCenter="2" horizontalCenter="0" color="0xBDBABD"
	fontSize="40" visible="{searchResult.lastResult == null}"/>

En oubliant pas bien sûr de donner un label à la FeedList dans notre vue principale:

<comps:FeedList  width="100%" height="100%" label="Home"/>

Et voilà la résultat:

Conclusion

Voilà une petite modification qui n'apporte pas grand chose mais qui nous a déjà permis de rassembler ce qui était nécessaire à la liste dans le composant FeedList. On va voir que ce composant va bien nous servir par la suite :)

Télécharger les sources

Vous pouvez télécharger le projet au format FXP tel qu'il est à la fin de cette partie:

Télécharger le projet au format FXP

Remplis sous: Adobe Air, List 2 Commentaires
29août/110

AIR Mobile – Application TweetDeck (10) – Corriger les espaces blancs entre les éléments d'interface (SolidColorStroke avec weight > 1)

Dans le dernier tutorial, on a terminé de styler la liste de tweet:

AIR Mobile – Application TweetDeck (9) – Finalisation des styles de l'itemRenderer

Voici le rendu actuel de l'application:

Même si vous n'avez pas une vision d'aigle, vous avez peut-être remarqué un petit padding:

Pourtant, on a tous nos éléments à 100% et notre VerticalLayout a un "gap" de 0. Tous les éléments devraient donc être collés les uns aux autres parfaitement. Si on reprend ce composant en le simplifiant, on voir que le problème ne vient pas du Layout mais bien du Rect et de son SolidColorStroke. Pour rappel, on a :

<s:Rect width="100%" height="100%">
    <s:fill>
      <s:LinearGradient rotation="90">
        <s:GradientEntry color="0x292C29" ratio="0" />
        <s:GradientEntry color="0x525152" ratio="0.2" />
        <s:GradientEntry color="0x313831" ratio="0.8" />
        <s:GradientEntry color="0x212021" ratio="1" />
      </s:LinearGradient>
    </s:fill>
    <s:stroke>
      <s:SolidColorStroke color="0" weight="2" />
    </s:stroke>
  </s:Rect>

En fait, si on modifie la propriété "weight", on voit que le décalage est différent. Pour une valeur de 4, il semble y avoir un offset de 2 pixels sur l'axe X. En fait, le comportement est le bon, seulement si on a une weight de 0 ou de 1.

Après quelques recherches, on trouve même un bug sur la JIRA Adobe de 2009:

SDK-19621 - Bounds of paths with mitered joints and a stroke weight of >1px are not correctly calculated.

Le bug a été "deferred", ce qui signifie qu'il sera traité dans une prochaine version. Et visiblement oublié par la même occasion.

Après quelques recherches Google, on tombe sur cet article:

Tired of Typing? No More left="0" right="0" top="0" bottom="0"

Même s'il porte sur la création de template de code dans Flash Builder, il y a une réponse intéressante dans les commentaires. C'est en fait la solution à notre bug. Pour pallier à ce bug, il ne faut pas utiliser un dimensionnement par pourcentage (width="100%") mais par contraintes (top="0", etc.).

On a donc :

  <s:Rect top="0" bottom="0" left="0" right="0">
    <s:fill>
      <s:LinearGradient rotation="90">

Et voilà le rendu, toujours avec notre contour de 2 pixels:

C'est parfait :) . N'oubliez pas de répéter la manipulation pour le composant BottomBar.mxml.

Télécharger les sources

Vous pouvez télécharger le projet au format FXP tel qu'il est à la fin de cette partie:

Télécharger le projet au format FXP

28août/110

AIR Mobile – Application TweetDeck (9) – Finalisation des styles de l'itemRenderer

Dans le dernier tutorial, on a vu comment créer un itemRenderer pour les Tweet. Avec quelques hacks pour combler un bug de Flex mais cela fonctionnait quand même:

AIR Mobile – Application TweetDeck (8) – Création d'un itemRenderer pour l'objet Tweet

Avant de continuer, on va essayer de faire pour que notre itemRenderer ait la tête de celui de TweetDeck:

Tout d'abord, on va changer la couleur du fond. Pour cela, on va utiliser une propriété CSS d'IconItemRenderer:"alternatingItemColors". Dans notre cas, les couleurs sont toujours les mêmes, il nous suffit donc de donner une couleur.

Dans le code de l'application principale "TweetDeck.mxml", rajoutez un tag "Style":

<?xml version="1.0" encoding="utf-8"?>
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                            xmlns:s="library://ns.adobe.com/flex/spark" firstView="views.TweetDeckHomeView">
  <fx:Style>
    @namespace s "library://ns.adobe.com/flex/spark";
    s|IconItemRenderer{
      alternatingItemColors:#565556;
    }
  </fx:Style>
</s:ViewNavigatorApplication>

Le rendu:

Pour la couleur du texte de la deuxième ligne, on va utiliser le style CSS "color" qui s'applique au labelDisplay (dans notre cas, en bas):

    s|IconItemRenderer{
      alternatingItemColors:#565556;
      color:#B5B6B5;
    }

Maintenant, il nous faut afficher le texte en blanc et pas en noir. En temps normal, il suffirait de rajouter des styles sur IconItemRenderer mais avec notre hack pour l'affichage du texte HTML, on ne peut plus. On va donc indiquer cela dans le contenu HTML. Dans la méthode formatMessage de TweetRenderer.as, remplacez donc la dernière ligne par:

return "<font size=\"" + fontSize + "\" face=\"Arial\" color=\"#FFFFFF\">" + value.replace(urlPattern, "<a href='$1$2$3'>$1$2$3</a>") + "</font>";

Rendu:

Maintenant, les liens doivent aussi être en #B5B6B5. Cela se passe dans la construction de notre StyleSheet dans TweetRenderer.as:

var styles:String = "a {color: #B5B6B5;}";

Rendu:

Maintenant, un petit problème d'ordre esthétique, lorsque les images (icônes) sont en train de charger, elles ne prennent aucune place.

24août/110

AIR Mobile – Créer une liste avec groupement (type liste de contacts iOS / Android)

Le framework Flex propose en standard, un composant List, qui réagit aux évènements touch pour une utilisation sur mobile. A cette liste, vous pouvez coupler un renderer comme par exemple un renderer de base de Flex, IconItemRenderer qui vous permet d'afficher une image + 2 lignes de texte pour chaque élément.

Mais il y a un autre type de rendu assez populaire sur mobile, qui est de créer une liste à "sections". Par exemple la liste de contacts sous Android ou iOS. Les contacts sont arrangés par ordre alphabétique et rassemblés par la première lettre de leur prénom:

Les sections ne sont pas cliquables, elles servent juste de décoration. Elles ont le plus souvent une taille différente de celle des items. Je ne suis pas un cador du développement natif mais je pense qu'il y a un composant tout prêt pour ce genre d'utilisation. En Flex, il vous faudra le faire vous-même.

Mais comme d'habitude, certains se sont posé la question avant vous (désolé) et on trouvé une solution. Voici quelques liens qui vous seront utiles:

http://corlan.org/2011/07/04/creating-flex-mobile-section-lists/

http://corlan.org/2011/07/11/creating-flex-mobile-lists-part-ii-using-virtualization/

http://www.tink.ws/blog/flex-4-excludeselectionlist/

http://code.google.com/p/tink/source/browse/trunk/flex4.5/spark/src/ws/tink/spark/controls/ExcludeSelectionList.as

http://flexponential.com/2011/06/27/extending-labelitemrenderer-to-look-like-itunes-on-the-ipad/

Voilà, je vous ai bien préparé le travail mais on va aller plus loin en regardant les techniques utilisées.

La technique générale

Pour intégrer ces sections, et que le rendu de la liste soit cohérent, on intègre ces sections directement dans le dataProvider de la liste. Au milieu de vos objets Contact, vous aurez donc d'autres items qui sont là pour la déco. Il faut ensuite déterminer dans votre liste quels sont ces éléments et leur donner un rendu différent.

Le composant ExcluseSelectionList

Le (très bon) blogger de tink.ws propose une implémentation de List.as (par héritage) qui permet d'indiquer au composant quels éléments sont "non-sélectionnables". Vous disposez donc de plusieurs propriétés pour définir cette restriction:

  • excludeType / excludeTypes : Permet de donner une liste de type AS3 à exclure (ex: la classe Header)
  • excludeIndex / excludeIndices : Permet de donner une liste d'index à exclure (ex 1, 10, 30)
  • excludeFunction : Permet de passer une fonction qui va renvoyer un Boolean pour indiquer s'il faut exclure ou pas l'élément. Un peu comme une filterFunction.

Ce composant va se révéler très pratique mais on peut voir que l'itemRenderer créé par flexponential.com contient un petit "trick" qui permet de se passer de cette restriction. En effet, il se contente de ne pas passer l'itemRenderer en état "sélectionné" s'il est sur un header. Un couplage des 2 solutions semble le meilleur compromis.

Utilisation des itemRenderer

C'est la solution la plus naturelle et la plus élégante d'un point de vue code. Suivant l'item passé à l'itemRenderer, on va modifier le rendu.On peut faire ça facilement en MXML mais il vaut mieux le faire en AS3 pour avoir de meilleurs perfs sur mobile.

L'exemple de flexponential explique bien la technique:

http://flexponential.com/2011/06/27/extending-labelitemrenderer-to-look-like-itunes-on-the-ipad/

Ici, on utilise bien la propriété itemRenderer et pas une itemRendererFunction. C'est encore mieux car l'utilisation d'une itemRendererFunction empêche la ré-utilisation des renderers et devient donc moins performante en RAM. Dans l'exemple, le composant MusicLabelItemRenderer est taillé pour cette application puisqu'il connait le type d'item qu'il va afficher. Il peut donc facilement faire la distinction entre un item et un header. Très pratique si vous connaissez votre liste, moins si vous voulez être générique.

En l’occurrence, le renderer est assez complexe puisqu'il permet d'afficher les éléments de manière horizontale avec une ligne séparatrice.

La problématiques des layouts

Voici un problème que je n'avais même pas envisagé pendant la rédaction de cet article. De base, une List utilise un "layout" qui est un VerticalLayout. On obtient donc une liste verticale. Mais sur de plus grands écrans, vous voudrez peut-être utiliser un layout qui vous permet d'afficher plusieurs éléments par ligne, comme un TileLayout:

Si vous modifier votre itemRenderer, vous n'aurez pas un header sur une ligne complète, juste sur la taille d'un item. Le résultat sera donc mauvais. Les billets de corlan.org peuvent vous permettre de résoudre cette problématique en faisant en sorte que votre header prenne toute la largeur. C'est un cas assez exceptionnel mais au moins vous êtes prévenus :) .

Conclusion

Avec ces différents articles, vous avez assez d'informations pour pouvoir créer votre liste. Certaines subtilités comme le layout ou la manière par laquelle on rend les éléments non-sélectionnables sont optionnels, essayez de faire au plus simple.

Dans un futur tutorial, je vais peut-être partager un exemple complet de liste avec groupement avec en bonus le composant de scroll rapide comme sur Android. Pour ceux qui ne voient pas, c'est une liste alphabétique qui vient au dessus de la liste, permettant d'aller directement à la bonne lettre. Très pratique quand on a des dizaines / centaines d'éléments :) .

1juil/112

AIR Mobile – Restreindre une List à un scrolling horizontal uniquement

Lorsque vous compilez une application Flex 4.5.1 qui utilise le thème Mobile, celle-ci se comporte différemment, notamment pour réagir aux évènements "touch". Les List se comportent elles aussi différemment, dans leur comportement lors du scrolling. Celui-ci a des effets "bounce and pull" qui font comme si la liste rebondissait lorsqu'elle touche une extrémité.

Le rendu est plutôt sympa et cela ressemble au rendu des listes des applications natives Android et iOS.

Le problème

Souvent dans les exemples des Twitter-Like, on voit des listes verticales. Pour celles-là, pas de problème, l'effet est le bon. Au lieu de donner un VerticalLayout à votre List, donnez lui maintenant un HorizontalLayout.

Testez votre application et vous allez avoir un comportement assez étrange. Vous aurez bien un scrolling horizontal mais la liste va reproduire les effets bounce and pull sur l'axe vertical. Il vous sera donc très difficile de faire un déplacement parfaitement horizontal.

En soi, ce n'est pas vraiment un bug, dans ce billet de la JIRA Adobe, un des ingénieurs du SDK explique que c'était le comportement escompté mais qu'il aurait du être mieux documenté (même si honnêtement, pour ce genre de problème, je n'irai pas voir la doc):

A horizontal mobile list does not have bounce/pull effects on its right and left boundaries when the content is smaller than the size

La solution

La solution est en fait plutôt simple. Il suffit de donner une verticalScrollPolicy à "off" à la liste pour ne pas avoir l'effet. Petit exemple:

<s:List id="colorList" dataProvider="{colorsArray}" width="100%" itemRenderer="ColorRenderer" y="40" selectedIndex="0" verticalScrollPolicy="off">
  <s:layout>
    <s:HorizontalLayout  />
  </s:layout>
</s:List>

Voilà, c'est en fait plutôt simple mais j'ai eu ce problème dans un de mes projets et cela m'a bien pris 30 minutes. Merci à ce billet pour m'avoir guidé vers la bonne solution:

Getting scrolling right in a mobile horizontal list

Remplis sous: Adobe Air, List 2 Commentaires