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

18août/114

AIR Mobile – Skinner un TextInput de façon programmatique

Tutorial Flex écrit par Arnaud Thorel (@athorel)
Publiez vous aussi sur flex-tutorial!

Depuis la version 4.0 de Flex, nous pouvons développer des skins en utilisant la nouvelle structure de composants spark.
Spark propose un système basé sur des composants logiques et des représentations graphiques que l’on appelle Skin. Dans un premier temps la plupart des skins étaient des représentations MXML où venaient se superposer des groupes de primitives de dessins (border, background, shadow, …), mais avec l’avènement du développement mobile, les enjeux de rapidité d’exécutions et de poids des fichiers rentrent en jeu.
C’est ainsi qu’Adobe a mis en place un ensemble de skins pour mobile dont la chaîne d’héritage est nettement moins longue que pour les skins sparks de base.

Dans ce tutorial nous allons voir comment intégrer un titre pour un composant TextInput (comme sur iOS), pour obtenir le rendu suivant, cet exemple pourra servir de base afin d’ajouter d’autres informations dans notre composant (Icon, Aide).

Comprendre l’affichage actuel d’un composant TextInput

Le composant TextInput possède 4 états :

  • Enable : Cet état est l’état éditable d’un TextInput
  • Disable : Cet état est l’état non éditable d’un TextInput
  • EnableWithPrompt : Cet état est l’état éditable d’un TextInput avec une valeur vide
  • DisableWithPrompt: Cet état est l’état non éditable d’un TextInput avec une valeur vide

La première étape consiste à hériter de la classe spark.skins.mobile.TextInputSkin pour obtenir une notre classe PromptTextInputSkin, et ensuite analyser le comportement de TextInpuitSkin.

Tout de suite on se rend compte du coté manichéen du composant TextInput, la valeur vient effacer le prompt, ce qui se traduit dans le code de cette manière.

override protected function commitCurrentState():void
{
    super.commitCurrentState();

    alpha = currentState.indexOf("disabled") == -1 ? 1 : 0.5;

    var showPrompt:Boolean = currentState.indexOf("WithPrompt") >= 0;

    if (showPrompt && !promptDisplay)
    {
        promptDisplay = createPromptDisplay();
        addChild(promptDisplay);
    }
    else if (!showPrompt && promptDisplay)
    {
        removeChild(promptDisplay);
        promptDisplay = null;
    }

    invalidateDisplayList();
}

Lors d’un changement d’état la fonction commitCurrentState on retrouve les éléments suivants :

  • On vérifie si nous sommes dans un état disabled, pour modifier l’alpha du composant.
  • On vérifie si nous sommes dans un état WithPrompt, si c’est le cas et que le prompt n’est pas déjà affiché, il est créé et ajouté, sinon il est retiré si il était déjà présent.
  • On conclut cette fonction par l’appel à invalidateDisplayList qui va permettre l’appel à updateDisplayList qui redessinera le composant

Redéfinir le comportement en fonction de l'état courant

Dans cette méthode on vient d’identifier la partie qui va nous poser problème, la première étape consiste donc à redéfinir cette méthode dans notre classe.

override protected function commitCurrentState():void
{
    alpha = currentState.indexOf("disabled") == -1 ? 1 : 0.5;
    invalidateDisplayList();
}

Nous avons ici de façon volontaire supprimé l’appel à la fonction super afin de ne pas avoir à exécuter le code qui ajoute ou supprime le promptDisplay. Il est généralement dangereux de faire ça car on coupe tout lien avec le code porté par l’héritage, mais dans notre cas on risque rien, car en remontant la chaîne d’héritage on se rend compte que la fonction commitCurrentState que nous venons de redéfinir est la première redéfinition d’une fonction vide.

Si on lance notre code maintenant nous n’obtenons pas encore le résultat voulu

Effectivement, nous avons retiré l’ajout du promptDisplay, nous allons donc gérer cette partie dans le constructeur de notre composant.

public class PromptTextInputSkin extends TextInputSkin
{
	public function PromptTextInputSkin(){
		super();
		promptDisplay = createPromptDisplay();
		this.addChild(promptDisplay);
	}

Maintenant le prompt est affiché continuellement, mais le texte vient se superposer par-dessus.

Mise en place des différents composants

Afin de remédier à ce problème nous allons redéfinir une fonction indispensable des skins, il s’agit de la fonction layoutContents, cette fonction permet la mise en place des éléments de la skin.

// Define gap beetween prompt and text
[Style(name="gap", type="Number", format="Number", inherit="yes")]

override protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void
{
	super.layoutContents(unscaledWidth, unscaledHeight);

	if(promptDisplay) {
		var leftMarginText:int = promptDisplay.x + promptDisplay.textWidth + getStyle('gap');
		var rightMarginText:int = getStyle('paddingRight');
		var textDisplaySize:int = hostComponent.width - leftMarginText - rightMarginText;

		//Replace la partie texte pour ne pas chevaucher le prompt
		setElementPosition(textDisplay, leftMarginText , textDisplay.y);
		//Redéfini la taille de la partie texte afin de prendre en compte les nouvelles marges
		setElementSize(textDisplay, textDisplaySize, textDisplay.height);
	}
}

Le gap est un Style ajouté, qui permettra dans notre CSS de définir l’espacement entre le prompt et le texte.

Afin de spécifier la marge qui doit être laissée à gauche du texte, nous calculons les éléments suivants : position x du prompt + taille du texte + le gap.

On récupère aussi la marge à laisser côté droit du texte, cela nous permettra de définir la nouvelle taille du texte.

La taille du texte est donc la taille du composant TextInput auquel on soustrait les marges de droites et de gauche.

On fait ensuite appel aux fonctions setElementPosition et setElementSize afin que le texte ne déborde pas du composant et ne sois plus superposé avec le prompt.

On se rapproche du résultat final, on remarque quand même quelques détails de style manquant :

  • Le prompt n’a pas le format voulu (gras, noir)
  • Le texte n’a pas l’alignement voulu.

Finir avec style

Pour remédier à ce problème nous utilisons donc la dernière méthode qui nous permet de jouer sur les propriétés du composant, il s’agit de la méthode commitProperties.

// Define color of the prompt text
[Style(name="promptColor", type="uint", format="Color", inherit="no")]

// Define font Weigth of the prompt text
[Style(name="promptFontWeight", type="String", format="String", inherit="no")]

override protected function commitProperties():void
{
	super.commitProperties();
	promptDisplay.setStyle('color', getStyle('promptColor'));
	promptDisplay.setStyle('fontWeight', getStyle('promptFontWeight'));
	promptDisplay.setStyle('textAlign', 'left');
}

On ajoute ici deux style pour la mise en forme du prompt, on force par défaut le promptDisplay à gauche sinon il est dépendant de la mise en forme textAlign et dans cet exemple nous n’avons pas prévu un affichage à droite du prompt.
On termine donc par une petite touche de CSS et le tour est joué

skins|PromptTextInputSkin {
	gap : 10px;
	promptColor : black;
	promptFontWeight : bold;
	textAlign : right;
}

Le résultat correspond à nos attentes, voici une base de travail pour ajouter une image comme dans cet exemple développé par nos amis de people in action inclus dans la lib e-skimo.

 

 

Remplis sous: Adobe Air, Skin 4 Commentaires
23juin/111

AIR Mobile – Application Pokémon (16) – Réaliser une Skin de bouton générique

Les applications mobiles ne dérogent pas à la règle. Pour avoir une bonne application, il faut qu'elle soit fonctionnelle et agréable à l'oeil. Pour l'aspect fonctionnel, c'est souvent votre job en tant que développeur. Quant à l'aspect graphisme, c'est souvent réservé aux graphistes qui vous fournissent des maquettes. Mais on a pas toujours un graphiste sous la main, encore plus si vous faîtes une application perso avec des Pokémon dedans.

Heureusement, le look par défaut des composants Flex peut parfois être suffisant. Mais vous ne voulez pas que votre application ressemble à toutes les autres, il va donc falloir passer par l'étape du Skinning. Pas de panique, même si vous n'avez pas la fibre artistique, c'est très simple. Pour cela, pas besoin (on pourrait) de sortir des outils comme Photoshop ou Illustrator, on va tout faire dans le code.

Le résultat attendu

Si vous avez suivi tous les tutoriaux dans l'ordre, vous n'avez pas pu manquer le premier, celui sur l'étape de prototypage dans Balsamiq:

AIR Mobile – Application Pokémon (1) – Conception et maquettage

On avait définit le bouton comme ceci:

maq

Plutôt simple, un texte et un icône à droite. D'ailleurs, le bouton "Evolution de …" y ressemble étrangement, on va pouvoir mutualiser les développements.

Utilisation des propriétés de Button par défaut

Il existe déjà une propriété "icon" sur button. Essayons donc le code suivant:

...
<s:Button id="soundButton" label="Ecouter le cri de {_p.nom}" click="onSoundClick(event)"
	icon="@Embed(source='/assets/sound_high.png')"/>
	...

Résultat:

icon

L'icône est volontairement blanc car dans notre application, on a plutôt fait du "texte blanc sur fond noir" et pas l'inverse. Par la suite on changera ces couleurs.

Par défaut, l'icône est placé à gauche. Lors de la frappe dans Flash Builder, vous avez peut-être remarqué une autre propriété "iconPlacement":

iconp

Cela semble parfait, choisissons "right":

right

C'est mieux. Mais notre bouton ne prend pas toute la largeur et fait un peu tâche. On va lui donner une width="100%":

wid

Hum, ce n'est pas vraiment le résultat espéré. Tous les éléments sont centrés par défaut alors que l'on voudrait avoir une partie à gauche et l'autre à droite. Pour nos besoins, les propriétés par défaut sont presque suffisantes mais pas vraiment. On peut faire beaucoup avec les propriétés par défaut mais on ne peut pas tout faire. Pour faire exactement ce que l'on veut, il faut faire une Skin.

Skin par défaut de Button : ButtonSkin

Je le dis souvent (peut-être pas assez) mais la meilleure manière d'apprendre le Flex est de s'inspirer des meilleurs exemples, de leurs techniques. Et ou trouve-t-on les meilleurs exemples? Si vous vous dîtes sur flex-tutorial.fr, cela me fait plaisir mais pas vraiment ^^.

Vous avez déjà tout ce qu'il faut sous la main, dans le code du SDK Flex. Flex est open source, profitez-en! Pour avoir passé pas mal de temps à fouiner dans les classes du framework, je peux vous assurez que l'on y trouve souvent la réponse à ses questions.

Pour commencer, allons voir le fichier CSS qui associé au thème mobile par défaut. Celui-ci se trouve sur votre disque, sous Windows il est ici:

C:\Program Files (x86)\Adobe\Adobe Flash Builder 4.5\sdks\4.5.0\frameworks\projects\mobiletheme\defaults.css

Pour un système Mac, c'est à peu près pareil sauf qu'il faut remplacer le début par Application (enfin vous savez vous servir de votre Mac). Dans ce fichier, on va chercher "Button", pour voir les styles et skins associés par défaut au composant Button. On trouve rapidement:

Button{
    fontWeight: "bold";
    skinClass: ClassReference("spark.skins.mobile.ButtonSkin");
}

On apprend déjà que la skin par défaut de tous les Button est spark.skins.mobile.ButtonSkin. Bon avec un peu de bouteille, vous auriez pu deviner cela, il est assez conventionnel en Flex de rajouter "Skin" à la suite du nom du composant pour symboliser la skin par défaut.

Retournons dans Flash Builder. Utilisez le raccourci Ctrl+T qui va vous permettre de consulter n'importe quelle classe du framework Flex. Tapez donc ButtonSkin puis Entrée pour consulter le code de cette classe. Prenez bien celle qui est dans le package "mobile", sinon vous tomberez sur la skin de base du thème classique (non-mobile):

type

Prenez 2 minutes pour bien comprendre comment fonctionne cette classe.

Analyse rapide du code de la classe ButtonSkin

Première partie, le constructeur. On y effectue un traitement suivant les DPI de l'application (densité de pixels). Suivant la classe DPI de l'appareil, on assigne différentes valeurs à upBorderSkin et downBorderSkin et différentes valeurs de paddings:

switch (applicationDPI){
	case DPIClassification.DPI_320:{
		upBorderSkin = spark.skins.mobile320.assets.Button_up;
		downBorderSkin = spark.skins.mobile320.assets.Button_down;

		layoutGap = 10;
		layoutCornerEllipseSize = 20;
		layoutPaddingLeft = 20;
		layoutPaddingRight = 20;
		layoutPaddingTop = 20;
		layoutPaddingBottom = 20;
		layoutBorderSize = 2;
		measuredDefaultWidth = 64;
		measuredDefaultHeight = 86;

		break;
	}
	...

On reviendra plus tard dans ce fil rouge sur ces problématiques de DPI.

5mai/1112

AIR Mobile – Les composants et le thème Mobile Flex 4.5

L'avantage de pouvoir utiliser les composants Flex par rapport à une application pure AS3 est de simplifier le développement en vous donnant toute une bibliothèque de composants prêts à l'emploi. Certains composants sont de nouveaux composants, d'autres ont simplement une autre "Skin" optimisée pour le rendu mobile (plus gros, système de cache, etc.). Bien sûr, vous pouvez toujours faire vos propres composants mais on va voir ceux qui sortent directement du four. Dans ce billet, on va découvrir ces composants et leurs looks pour vous donner une idée.

Notez que les itemRenderer seront couverts dans un autre billet sur flex-tutorial.

Bouton

Ce n'est pas un nouveau composant, seulement le composant s:Button avec un look différent lorsque vous l'utilisez dans un projet de type Mobile:

buttons

Notez que le composant Spark Button a retrouvé sa propriété "icon" vous permettant d'ajouter un icône directement avec la Skin de base (il fallait faire sa propre Skin auparavant).

25juil/100

Flex 4 – Création d’une Skin pour le composant HSlider

Prenons tout d’abord le composant HSlider de base de Flex 4 :

spark-4

Comme on l’a vu dans l'article précédent, dans la déclaration des SkinPart, on a "thumb" et "track" qui sont des boutons. On va laisser de côté, pour cet exemple, la Skin du DataTip. Pour créer une Skin, on va donc créer deux Skins de Button, un peu comme on l’a fait dans l’exemple précédent.
Pour la "track", on va simplement créer un rectangle arrondi avec un remplissage et une ombre portée. Ce fichier s’appellera MyTrackSkin.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx">
  <s:states>
    <s:State name="up" />
    <s:State name="over" />
    <s:State name="down" />
    <s:State name="disabled" />
  </s:states>
  <s:Rect radiusX="2" radiusY="2" top="0" right="0" bottom="0"
          left="0" height="3">
    <s:fill>
      <s:SolidColor color="0xFF3300"/>
    </s:fill>
    <s:stroke>
      <s:SolidColorStroke color="0x3E3E3E" weight="1" />
    </s:stroke>
    <s:filters>
      <s:DropShadowFilter blurX="5" blurY="5"
                          alpha="0.32" distance="2" />
    </s:filters>
  </s:Rect>
</s:Skin>

Pour la "thumb", on va simplement créer une ellipse avec un remplissage. Ce fichier s’appellera MyThumbSkin:

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx">
  <s:states>
    <s:State name="up" />
    <s:State name="over" />
    <s:State name="down" />
    <s:State name="disabled" />
  </s:states>
  <s:Ellipse width="12" height="12">
    <s:fill>
      <s:SolidColor color="0xFF33FF"/>
    </s:fill>
    <s:stroke>
      <s:SolidColorStroke color="0x3E3E3E" weight="1" />
    </s:stroke>
  </s:Ellipse>
</s:Skin>

Il ne nous reste plus qu’à créer la Skin du Slider à proprement parler. Celle-ci sera constituée de nos deux composants. Notez leurs propriétés « id », liées aux SkinPart que l’on a décrit plus haut :

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx">
  <fx:Metadata>
    [HostComponent("spark.components.HSlider")]
  </fx:Metadata>
  <s:states>
    <s:State name="normal" />
    <s:State name="disabled" />
  </s:states>

  <s:Button id="track" left="3" right="3" top="3" bottom="3" skinClass="MyTrackSkin"/>
  <s:Button id="thumb" top="0" bottom="0" width="12" height="5" skinClass="MyThumbSkin" />
</s:Skin>

Il ne nous reste plus qu’à donner notre Skin à notre composant dans l’application Flex principale :

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark">
  <s:layout>
    <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
  </s:layout>
  <s:HSlider skinClass="MySliderSkin" width="150"/>
</s:Application>

Voici le résultat de notre Skin très simpliste :

spark-5

25juil/100

Flex 4 – Le lien "Composants" entre Skin et Composant

Jusqu’ici, on a vu deux types de contrat entre le composant et sa Skin : States et Données. Le dernier est le lien "Composant". Il faut tout d’abord comprendre qu’un composant peut-être défini par un ensemble de SkinPart (morceaux de Skin). Par exemple, pour une barre de défilement, on a :

  • Le bouton ascendant
  • Le bouton descendant
  • Le curseur
  • La zone dans laquelle le curseur va se déplacer (« track »)

Dans le cas du Button, il n’y a qu’une SkinPart : le label. C’est la seule partie dont le Button a besoin. C’est par ces SkinPart que l’on va établir le contrat "Composants".

Définition des SkinPart dans les composants Flex 4

Ces SkinPart sont aussi définies par annotation, juste au dessus des variables concernées. Par exemple, dans la classe spark.components.supportClasses.ButtonBase, on peut trouver :

[SkinPart(required="false")]

    /**
     *  A skin part that defines the label of the button.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var labelDisplay:TextBase;

Dans cet exemple, la SkinPart est définie comme non requise. Cela nous a permis de pouvoir créer la Skin sans même connaître cette notion de SkinPart. Cela pourrait ainsi nous permettre de réaliser un bouton complètement graphique, sans texte.
Pour remplir le contrat, la Skin doit déclarer un composant du type TextBase (dont Label hérite) avec comme identifiant "labelDisplay". Dans ce cas-là, le composant hôte (le Button) va reconnaître la SkinPart et lui donner automatiquement la propriété "text".

On aura donc dans MyButtonSkin :

<s:Label id="labelDisplay" color="0x131313" textAlign="center"
           verticalAlign="middle"
           horizontalCenter="0" verticalCenter="1"
           left="12" right="12" top="6" bottom="6"
           />

On a plus de Data Binding entre le Label et le "hostComponent". Au lieu de cela, on lui donne comme "id", labelDisplay. Le composant Button fera ensuite le travail pour vous et va injecter sa propriété "label" dans le composant "labelDisplay".
Ce lien "Composant" va bien plus loin, en vous permettant de lier les comportements des composants à la Skin. Par exemple, pour le composant Slider, on a trois SkinParts (tous facultatifs) :

  • Le curseur
  • La zone dans laquelle le curseur va se déplacer (« track »)
  • Le DataTip (tooltip indiquant la valeur sélectionnée)

Voici les déclarations dans les classes TrackBase et SliderBase, dont les Slider héritent :

[SkinPart(required="false")]
public var thumb:Button;
[SkinPart(required="false")]
public var track:Button;
[SkinPart(required="false", type="mx.core.IDataRenderer")]
public var dataTip:IFactory;

Contrairement au bouton, dans lequel la zone interactive se résume au bouton tout entier, ici on a trois composants distincts, qui réagissent différemment aux actions utilisateur. Le composant « curseur » peut ainsi être déplacé par le clic de l’utilisateur.

Dans le cas du Slider, l’avantage de la liaison composant n’est pas de récupérer de la donnée, mais bien de pouvoir utiliser les évènements qui vont être ajoutés sur ces composants. Cela vous permet de profiter de la classe Slider de base, en modifiant uniquement son style graphique, et pas son comportement.

Pour bien illustrer ce comportement, on va créer une Skin de Slider.

Remplis sous: Flex 4, Skin Aucun commentaire