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.

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

18juil/100

Flex 4 – Le lien "Données" entre Skin et Composant

Grâce à l’utilisation des States, notre composant a un nouvel aspect graphique. Cependant, le label du bouton est toujours « Bouton ». En effet, celui-ci est codé en dur dans notre fichier MyButtonSkin. Le label que l’on va donner au bouton n’est pas une propriété de la Skin, mais une propriété du composant Button. Pourtant, notre composant Label se trouve dans le Skin, et c’est lui qui doit recevoir ce texte.
C’est là qu’intervient le contrat de type "Données" entre le composant Button et la Skin. La Skin va ainsi indiquer sur quel composant hôte elle peut se greffer. Cela se fait par l’ajout d’une méta-information sur le composant : [HostComponent("chemin vers la classe concernée")].
Depuis la Skin, on pourra alors accéder à ce composant hôte par la propriété « hostComponent ». Pour lier la propriété « label » du composant Button et la propriété « text » du composant Label de la Skin, on va faire un simple DataBinding. Voici les éléments à rajouter dans MyButtonSkin.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" alpha.disabled=".5">

  <fx:Metadata>
    [HostComponent("spark.components.Button")]
  </fx:Metadata>

  ...

  <s:Label text="{hostComponent.label}" color="0x131313"
           textAlign="center"
           verticalAlign="middle"
           horizontalCenter="0" verticalCenter="1"
           left="12" right="12" top="6" bottom="6"
           />
</s:Skin>

Vous pouvez maintenant préciser n’importe quel label, en donnant simplement une valeur à la propriété « label » de votre Button:
spark-3

Votre composant se comporte maintenant comme vous le souhaitiez, exactement comme le Button Spark de base. Le composant Button de base est d’ailleurs fait de la même manière, avec de base, la classe spark.skins.spark.ButtonSkin.

Vous pouvez si le souhaitez aller voir comment est crée la Skin de base des Button Spark. Pour cela, il vous suffit d’ouvrir la classe ButtonSkin dans Flash Builder. Dans le menu de Flash Builder 4, allez dans Navigate > Open Type… puis tapez « ButtonSkin » dans le champ de recherche.

17juil/100

Flex 4 – Utilisation de States dans une Skin

Tout d’abord, on va voir l’aspect State. Un composant pouvant être dans plusieurs états différents, il est normal que la Skin suive, et propose plusieurs états. Tout composant héritant de SkinnableComponent a au moins un état, celui de base.
Un composant peut définir des états supplémentaires à l’aide de la balise MetaData [SkinStates]. Voici par exemple les States déclarés dans la classe Button :

  • [SkinState("up")]
  • [SkinState("over")]
  • [SkinState("down")]
  • [SkinState("disabled")]

Pour un bouton, on a quatre States : up, over, down, disabled. On va modifier notre fichier Skin pour avoir un rendu différent dans chaque State. Comme on l’a vu plus haut, on déclare dans une balise « s:states », la liste des States utilisés par le composant :

<s:states>
 <s:State name="up" />
 <s:State name="over" />
 <s:State name="down" />
 <s:State name="disabled" />
 </s:states>

Grâce à cette déclaration, la Skin va respecter sa partie du contrat Skin-Composant. Vous pouvez utiliser la nouvelle notation Flex 4 « pointée ». Ainsi, alpha.disabled=".5" indique que l’opacité sera de 0.5 dans l’état nommé « disabled ». Pour donner un effet graphique plus agréable, on va aussi rajouter un léger dégradé.

Voici donc la Skin modifiée pour pouvoir avoir un aspect différent suivant la Skin :

<?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" alpha.disabled=".5">

  <s:states>
    <s:State name="up" />
    <s:State name="over" />
    <s:State name="down" />
    <s:State name="disabled" />
  </s:states>

  <s:Rect radiusX="4" radiusY="4" top="0" right="0" bottom="0"
          left="0" includeIn="down">
    <s:fill>
      <s:SolidColor color="0"/>
    </s:fill>
    <s:filters>
      <s:DropShadowFilter knockout="true" blurX="5" blurY="5"
                          alpha="0.32" distance="2" />
    </s:filters>
  </s:Rect>

  <s:Rect id="rect" radiusX="4" radiusY="4" top="0"
          right="0" bottom="0" left="0">
    <s:fill>
      <s:SolidColor color="0x0099FF" color.over="0x0066FF"
                    color.down="0x0000CC"/>
    </s:fill>
    <s:stroke>
      <s:SolidColorStroke color="0x222222" weight="2"/>
    </s:stroke>
  </s:Rect>

  <s:Rect radiusX="4" radiusY="4" top="2" right="2" left="2"
          height="50%">
    <s:fill>
      <s:LinearGradient rotation="90">
        <s:GradientEntry color="0xFFFFFF" alpha=".5"/>
        <s:GradientEntry color="0xFFFFFF" alpha=".1"/>
      </s:LinearGradient>
    </s:fill>
  </s:Rect>

  <s:Label text="Bouton" color="0x222222"
           textAlign="center" verticalAlign="middle"
           horizontalCenter="0" verticalCenter="1"
           left="12" right="12" top="6" bottom="6"
           />
</s:Skin>

Et voici le résultat, dans les différents états :
spark-2

<?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" alpha.disabled=".5">

<s:states>
<s:State name="up" />
<s:State name="over" />
<s:State name="down" />
<s:State name="disabled" />
</s:states>

<s:Rect radiusX="4" radiusY="4" top="0" right="0" bottom="0"
left="0" includeIn="down">
<s:fill>
<s:SolidColor color="0"/>
</s:fill>
<s:filters>
<s:DropShadowFilter knockout="true" blurX="5" blurY="5"
alpha="0.32" distance="2" />
</s:filters>
</s:Rect>

<s:Rect id="rect" radiusX="4" radiusY="4" top="0"
right="0" bottom="0" left="0">
<s:fill>
<s:SolidColor color="0x0099FF" color.over="0x0066FF"
color.down="0x0000CC"/>
</s:fill>
<s:stroke>
<s:SolidColorStroke color="0×222222" weight="2"/>
</s:stroke>
</s:Rect>

<s:Rect radiusX="4" radiusY="4" top="2" right="2" left="2"
height="50%">
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="0xFFFFFF" alpha=".5"/>
<s:GradientEntry color="0xFFFFFF" alpha=".1"/>
</s:LinearGradient>
</s:fill>
</s:Rect>

<s:Label text="Bouton" color="0×222222"
textAlign="center" verticalAlign="middle"
horizontalCenter="0" verticalCenter="1"
left="12" right="12" top="6" bottom="6"
/>
</s:Skin>
Et voici le résultat, dans les différents états :