aller plus loin dans le dÉveloppement … · 2 fragmenter vos projets...

59
ALLER PLUS LOIN DANS LE DÉVELOPPEMENT ANDROID Andr0 29 octobre 2015

Upload: trinhdat

Post on 13-Sep-2018

213 views

Category:

Documents


0 download

TRANSCRIPT

ALLER PLUS LOIN DANS LEDÉVELOPPEMENT ANDROID

Andr0

29 octobre 2015

Table des matières

1 Introduction 5

2 Fragmenter vos projets 72.1 Fragment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.1.1 Introduction aux fragments . . . . . . . . . . . . . . . . . . . . . . . . . 72.1.2 Utiliser des fragments fixes . . . . . . . . . . . . . . . . . . . . . . . . . 102.1.3 Utiliser des fragments dynamiques . . . . . . . . . . . . . . . . . . . . . . 142.1.4 Attacher un bundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.1.5 En résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

2.2 ListFragment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.2.1 Utilisation simple des listes . . . . . . . . . . . . . . . . . . . . . . . . . 212.2.2 Intégrer une vue personnalisée . . . . . . . . . . . . . . . . . . . . . . . . 232.2.3 Vers des listes dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . 272.2.4 En résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

2.3 Interfaces dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312.3.1 Concevoir une interface dynamique . . . . . . . . . . . . . . . . . . . . . 312.3.2 Initialiser l’activité hôte . . . . . . . . . . . . . . . . . . . . . . . . . . . 332.3.3 Communication entre les fragments . . . . . . . . . . . . . . . . . . . . . 352.3.4 En résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

2.4 PreferenceFragment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382.4.1 Définir ses préférences . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382.4.2 Intégrer ses paramètres dans une activité . . . . . . . . . . . . . . . . . . 402.4.3 Affiner ses paramètres avec les en-têtes . . . . . . . . . . . . . . . . . . . 412.4.4 Lire les préférences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482.4.5 En résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

2.5 DialogFragment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502.5.1 Créer un DialogFragment . . . . . . . . . . . . . . . . . . . . . . . . . . 502.5.2 Créer un AlertDialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512.5.3 Afficher une boite de dialogue . . . . . . . . . . . . . . . . . . . . . . . . 532.5.4 En résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

3 Conclusion 593.1 Remerciements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

3

1 Introduction

Le développement mobile est devenu incontournable pour les entreprises. Elles se doivent d’of-frir des solutionsmobiles pour leurs clients ou une vitrinemobile pour elles-mêmes. Le dévelop-pement Android est d’autant plus important puisqu’il occupe une majeure partie du marché.

Ce tutoriel n’abordera pas de notions complexes mais les bases sont des pré-requis. Celles-cipeuvent être acquises dans ce tutoriel Android. Ce dernier aborde l’utilisation des composantsbasiques du développement, nécessaire pour commencer. Dans le tutoriel ci-présent, un pointd’honneur est placé sur des notions utilisées en entreprise et couvrant une majeure partie desversions d’Android.

En effet, au vue de la diversité des constructeurs et des terminaux qu’ils confectionnent, Googlea du mal à migrer tous les smartphones vers la dernière version du système. C’est pourquoi, ladiversité est importante et il a fallu que Google puisse offrir une solution aux développeurs pourpouvoir maintenir et développer efficacement sur Android.

Figure 1.1 – Diagramme de la répartition des versions daté du 3 Juin 2013

Il sera enseigné le développement pour les nouveaux systèmes tout en restant compatible le pluspossible vers les anciens systèmes (jusqu’à Froyo en général). Ces notions sont, pour certaines,difficiles à acquérir tant les ouvrages sont peu nombreux sur les nouvelles fonctionnalités inté-grées au système.

L’objectif est d’aborder des notions avancées mais intéressantes sur le développement Androiden restant accessible aux débutants. Tout ce qui figurera sera consciencieusement expliqué afin devous permettre d’en comprendre les concepts et de pouvoir les mettre en pratique directement,à travers des travaux pratiques sérieux.

5

1 Introduction

Pour parvenir à atteindre cet objectif, une série de choses sont abordées :

— L’utilisation de fragments pour confectionner des interfaces souples et avancées.— Les derniers composants ajoutés dans les dernières versions d’Android.— La mise en place d’un serveur pour effectuer des requêtes HTTP tout en gardant à l’esprit

le cycle de vie de l’application et la batterie de l’utilisateur final.

[[information]] | Tous les codes sources de ce tutoriel sont disponibles sur ce projet GitHub afinque vous puissiez consulter des exemples fonctionnels directement exécutables sur un terminalou un émulateur.

6

2 Fragmenter vos projets

Depuis Android 3.x, les fragments est apparus dans le développement d’applications. Ils sontmaintenant utilisés pour le développement de toutes les applications (dumoins sérieuses) sur leGoogle Play Store. Ils permettent d’isoler des portions d’interfaces qui sont gérées par des classeshéritant de Fragment plutôt que de Activity.

Les fragments ressemblent aux activités, que cela soit dans son cycle de vie que dans les diffé-rentes sous-classes qu’il peuvent créer. Ils se composent du type de base, des listes, des boitesde dialogue et des écrans conçus pour paramétrer son application. Leurs utilisations seront expli-quées via des exemples simples et claies pour présenter leurs fonctionnements.

2.1 Fragment

Tout comme les activités, il existe un fragment«générique». Il peut inclure des listes, des écransde préférences et toutes les vues du framework Android pour constituer des portions d’interfaces.Par la suite, elles pourront être réutilisées plusieurs fois dans les écrans de l’application finale.Pour expliquer en profondeur tous ces concepts, une explication détaillée des fragments est don-née dans la suite de ce chapitre.

2.1.1 Introduction aux fragments

2.1.1.1 Qu’est ce qu’un fragment ?

Les fragments ne sont pas simples à comprendre. Cette notion est souvent confuse pour les débu-tants et pas toujours bien comprise par les développeurs expérimentés. En effet, les possibilitésoffertes sont nombreuses et intéressantes. Les fragments sont à mi-chemin entre les activitéset les vues. Il ne s’agit ni de l’un, ni de l’autre mais ils sont étroitement liés avec ces concepts.Les fragments permettent de définir des morceaux d’interfaces, un ensemble de vues, qu’il estpossible de contrôler et de réutiliser sur plusieurs écrans.

En temps normal, une application est destinée à être exécutée aussi bien sur les smartphones quesur les tablettes, et peu à peu sur les télévisions connectées. La taille des écrans devient de plusen plus grande. Il faut pouvoir exploiter l’espace efficacement sur chacune de ces plates-formeset, si possible, à partir d’un seul projet. Ce dernier point sera expliqué bien plus tard dans ce coursau vue de sa complexité.

Par exemple, le propriétaire d’un blog pourrait légitimement vouloir développer sa propre appli-cation pour offrir une alternative mobile à ses lecteurs. Dans un blog simpliste, il voudrait per-mettre une consultation de ses articles et des commentaires associés. Une version smartphoneconsisterait dans l’affichage de trois écrans : une liste des articles du blog, l’article courant que

7

2 Fragmenter vos projets

le lecteur consulte et les commentaires associés. Sur une tablette en mode paysage, il est pos-sible de restreindre les écrans à un seul en affichant la liste des articles à gauche et l’article et sescommentaire à droite.

Ce genre de chose est possible grâce aux fragments !

2.1.1.2 Le cycle de vie des fragments

Les activités sont régulées par un cycle de vie. Les fragments possèdent des similitudes sur cepoint avec les activités. Ils définissent une interface qu’ils contrôlent mais ils ne sont pas as-sociés à un écran. Ils ne font que s’y attacher. C’est la raison pour laquelle leurs cycles de viesont semblables mais pas identiques. Ils possèdent les méthodes callback d’une activité, à savoironCreate, onStart, onResume, onPause, onStop et onDestroy. Ces méthodes ont le mêmeobjectif que dans une activité. Elles ne seront pas (ou peu) expliquées. La documentation donnedes informations à ce sujet en cas d’oubli.

Plusieurs nouvelles méthodes ont été rajoutées à travers ce cycle de vie. Elles sont liées à l’acti-vité hôte en cours d’exécution. Il ne faut pas perdre à l’esprit qu’un fragment n’est qu’attaché àune activité. Les méthodes supplémentaires sont liées à cette activité. La documentation met àdisposition un schéma pour donner le cycle de vie.

Les méthodes rajoutées sont onAttach, onCreateView, onActivityCreated, onDestroy-View et onDetach. Les noms de ces méthodes sont assez explicites. Deux sont destinées à laliaison avec l’activité hôte, deux autres à la création de la vue qui compose le fragment et ladernière après la création de l’activité hôte. Elles seront expliquées plus en détail dans la suitede ce chapitre ainsi que dans les suivants de cette partie.

2.1.1.3 La bibliothèque de compatibilité

Le fragment est une nouveauté depuis Android 3.x. A partir de cette version, il est possible de lesplacer librement aux endroits voulus pour rendre les applications plus souples et les architecturerconvenablement. Cependant, la version 2.3 d’Android est encore largement utilisée, surtout dansles smartphones d’entrée de gammemais ils ne peuvent pas utiliser les fragments.

Pourquoi ne pas avoir intégré les fragments dès le début ? Google n’a pas pu voir assez loin surle long terme. Au début, Android était dédié uniquement aux smartphones. Les écrans avaientdes tailles différentes en fonction du constructeur, voire dumodèle, mais lemarché des tablettesétait presque inexistant. Les tailles ne dépassaient pas le 4 pouces.

Une fois que les tablettes ont commencées à être commercialisées en grande quantité, Google aété contraint d’offrir une alternative pour ce type d’appareil. La version 3.x d’Android était des-tinée à résoudre le problème mais ce n’est qu’à partir de la version 4 qu’un tournant a pu êtrenoté. Le plus grand défaut de la troisième version du système était sa philosophie. Même s’ilétait meilleur que les versions précédentes, il n’était pas prévu pour les smartphones.

[[information]] | Aujourd’hui, de plus en plus de télévisions connectées sur Android sont com-mercialisées. Il est tout à fait possible de développer pour ce genre d’appareil mais les bonnespratiques d’ergonomie sont encore un peu vagues. C’est pourquoi, aucun exemple concret ne seramontré mais les techniques apprises dans ce tutoriel le permettront.

Dans le but de ne pas devoir créer un projet pour chaque version d’Android, Google a développéune bibliothèque de compatibilité qu’il est possible d’utiliser dans n’importe quel projet. Cette

8

2.1 Fragment

Figure 2.1 – Cycle de vie des fragments disponible à partir du site des développeurs Android

9

2 Fragmenter vos projets

bibliothèque donne accès à presque toutes les nouvelles notions qu’apporte la version 3.x maisaussi la 4.x qui apporte son lot de nouveautés (elles seront expliquées dans la seconde partie).

Grâce à cette bibliothèque, les applications développées toucheront plus de 90% du marché desappareils. Raison pour laquelle toutes les notions apprises seront basées sur son utilisation dèsque possible. Il est également conseillé de développer avec ce projet même si l’application finalen’est pas destinée à être compatible avec les anciennes versons. Personne ne sait de quoi est faitdemain.

2.1.2 Utiliser des fragments fixes

!(http ://www.youtube.com/watch ?v=Z_QKuWCses4)

La théorie maintenant terminée, la pratique permet d’en comprendre réellement le fonctionne-ment. Il existe deux façons d’intégrer des fragments à une activité : demanière fixe en spécifiantles fragments dans le fichier XML d’affichage de l’écran voulu et dynamiquement avec l’aide desoutils mis à la disposition des développeurs dans le framework Android.

2.1.2.1 Création d’un fragment

La création d’un fragment est similaire à la création d’une activité. La portion d’interface se dé-clare dans un fichier XML d’affichage. Il est désérialisé dans une classe qui étend la classe Frag-ment et non plus Activity. Cependant, même si les fragments peuvent redéfinir la méthodepublic void onCreate (Bundle savedInstanceState), y appeler la méthode setContent-View(int resource) ne fonctionnera pas pour la désérialisation.

Il est possible de redéfinir la méthode suivante dans son cycle de vie, public View onCreate-View (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceS-tate). Elle donne accès en premier paramètre à un « inflater » qui permet la désérialisation dufichier XML d’affichage.

La bibliothèque de compatibilité a été présentée. Son utilisation sera constante dans les exemplesillustrant le tutoriel. Il existe une chose importante à savoir sur son utilisation : l’environne-ment de travail, peu importe lequel, proposera d’importer deux paquetages pour avoir accès àFragment et aux autres classes présentées dans ce tutoriel. Pour les fragments, il proposera an-droid.app.Fragment et android.support.v4.app.Fragment.

Toutes les classes figurant dans les paquetages android.support.v4 possèdent un équivalentlimité à une certaine API (à partir de la version 3.x d’Android jusqu’aux versions récentes). Quantà la bibliothèque de compatibilité, elle apporte une compatibilité à partir de la version 2.x. L’uti-lisation des classes de cette bibliothèque diffère peu de l’originale mais il y a certaines subtilitésà connaître. Le tutoriel enseignera son utilisation plutôt que les originaux pour deux raisons :

— Il est plus simple de passer de l’utilisation de la librairie de compatibilité vers les originauxque l’inverse ;

— Les originaux restreignent la portée de l’application finale au minimum à la version 3.xd’Android, voire plus comme les notifications avancées qui ne sont compatibles qu’à partirde la version 4.1.

[[attention]] | Les raisons pour utiliser la bibliothèque de compatibilité sont intéressantesmais ily a une chose essentielle à savoir : il n’est pas possible de mélanger les originaux avec les classes

10

2.1 Fragment

du support. C’est une erreur commune chez les développeurs. Google a tenté de rendre l’utilisa-tion de la bibliothèque la plus proche de son utilisation normale. Mise à part quelques méthodeset des noms de classes, l’utilisation sera identique. Cela peut paraître pratique mais une erreurest vite arrivée.

Un fragment simple serait d’afficher simplement du texte. Le but de cet exemple n’est pas deconfectionner un fragment complexe (ce loisir sera donné plus tard). L’idée est de placer un Text-View au centre de l’écran via un fichier XML d’affichage. Il sera désérialisé dans un fragment quisera lui même contenu dans une activité. Voici le fichier XML d’affichage fragment_fixe :

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"android:layout_height="match_parent" >

<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:layout_centerVertical="true"android:text="@string/title_fragment_fixe" />

</RelativeLayout>

Pour ce premier exemple, le fragment associé est extrêmement simple. En temps normal, unfragment joue le contrôleur complet de laportiond’interface qu’il contient afind’alléger l’activitéhôte et d’architecturer convenablement le projet. Ainsi, le fragment FixeFragment ressemble àceci :

package com.siteduzero.android.fragments.fixe;

import android.os.Bundle;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;

import com.siteduzero.android.R;

public class FixeFragment extends Fragment {@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_fixe, container, false);

}}

[[information]] | Remarquez que les importations sont délibérément affichées afin de montrerque la bibliothèque de compatibilité est bien utilisée à la place des originaux. Cela sera une habi-tude prise dans ce tutoriel pour que vous ne soyez jamais perdus.

11

2 Fragmenter vos projets

La création et l’utilisation d’un fragment est aussi simple qu’une activité. Il fautmaintenant atta-cher ce fragment à une activité. Il n’y a aucun changement de ce côté là puisqu’un fragment peutêtre déclaré dans un fichier XML d’affichage commen’importe quelle vue. Il sera chargé dans uneactivité à l’exécution de l’application et ne changera plus. Dans ce cas, il sera fixé à l’activité hôte.Ses attributs sont lesmêmes qu’une vue normalemais, en plus, il faut rajouter le chemin d’accèsvers le fragment. Pour attacher notre fragment FixeFragment, il faut le déclarer ainsi :

<?xml version="1.0" encoding="utf-8"?><fragment xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/fragment_fixe"android:name="com.siteduzero.android.fragments.fixe.FixeFragment"android:layout_width="match_parent"android:layout_height="match_parent" />

Du côté de l’activité, il y a une petite chose à savoir. Il n’est pas possible d’étendre la classe Acti-vity si la bibliothèque de compatibilité est utilisée. Une autre classe est présente dans le support,FragmentActivity. Cette classe est l’équivalente à la classe originale, Activity. Mais elle nedoit pas être utilisée si les originaux sont utilisés.

package com.siteduzero.android.fragments.fixe;

import com.siteduzero.android.R;

import android.os.Bundle;import android.support.v4.app.FragmentActivity;

public class FixeActivity extends FragmentActivity {@Overrideprotected void onCreate(Bundle arg0) {

super.onCreate(arg0);setContentView(R.layout.activity_fragment_fixe);

}}

Tout comme le fragment, l’activité est vide puisque le fragment est fixe et que toute l’interfacey a été déportée. C’est l’une des forces de l’utilisation des fragments. Habituellement, les acti-vités jouent le rôle des contrôleurs dans une architecture MVC (patron architectural sur lequelAndroid se base dans le développement d’applications) mais cette partie contrôleur a été allé-gée pour ne plus avoir des activités énormes et pour devenir les contrôleurs des fragments plutôtque directement des interfaces. Cela permet de maintenir plus aisément le code et de séparer lesresponsabilités des différentes classes.

2.1.2.2 Réarranger les fragments en paysage

Attacher un fragment demanière fixe à une activité n’empêche pas d’en attacher plusieurs, qu’ilssoient identiques ou non. Il suffit de spécifier plusieurs fragments dans le fichier XML d’affichagede l’activité pour voir apparaître les fragments. Une possibilité pour l’illustrer est le mode pay-sage.

Une application n’est pas toujours destinée à s’afficher en portrait (sauf en cas de contre indi-cation dans le fichier Manifest) ni même toujours sur un smartphone. C’est pourquoi, en mode

12

2.1 Fragment

Figure 2.2 – Résultat de l’exécution de l’activité FixeActivity

13

2 Fragmenter vos projets

paysage, il faut repenser les interfaces pour garder une expérience utilisateur optimale.

Dans un dossier res/layout-land, il faut créer un nouveau fichier XML d’affichage dumême nomque celui dans le dossier res/layout de l’activité cible, FixeActivity dans l’exemple ci-présent.Tout comme n’importe quelle vue, les fragments peuvent être placés dans un conteneur commele LinearLayout afin de donner un poids équitable entre les fragments.

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"android:layout_height="match_parent"android:baselineAligned="false"android:orientation="horizontal" >

<fragmentandroid:id="@+id/fragment1"android:name="com.siteduzero.android.fragments.fixe.FixeFragment"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1" />

<fragmentandroid:id="@+id/fragment2"android:name="com.siteduzero.android.fragments.fixe.FixeFragment"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1" />

</LinearLayout>

2.1.3 Utiliser des fragments dynamiques

Les fragments fixes sont simples à implémenter. A partir du moment où le fragment est codé, ilsuffit de l’indiquer dans des fichiers XML d’affichage à désérialiser dans une activité sans rienindiquer en plus. Se limiter à ce type de fragment ne permettrait pas de satisfaire ses promessesde flexibilité et de maintenance.

Dans cet ordre idée, le frameworkAndroid fournit uneAPI pour gérer les fragments pendant l’exé-cution d’une application. Ainsi, il est possible de remplacer un fragment par un autre aisémentet, par la même occasion, afficher une nouvelle portion d’interface.

2.1.3.1 Gérer ses fragments

Le framework Android met à disposition des développeurs une API indispensable pour gérerles fragments, FragmentManager. Cette API offre plusieurs services. La documentation donnetoutes les informations à ce sujet. Dans l’immédiat, le plus intéressant est le service qui per-met de gérer des transactions via une autre API, FragmentTransaction. Les transactionspermettent d’opérer sur les fragments de manière groupée. Les opérations sont classiques. Il

14

2.1 Fragment

Figure 2.3 – Résultat de l’exécution de l’activité FixeActivity en paysage

est possible d’ajouter, remplacer, supprimer, etc. des fragments. Elles ne seront pas toutesexpliquées dans ce tutoriel.

L’utilisation de ces APIs est assez simple mais il faut garder à l’esprit que la bibliothèque decompatibilité est utilisée dans les exemples. La documentation donnée renseigne les méthodesdes classes originales du framework. Même si les signatures des méthodes sont globalement lesmêmes pour chacune d’elles, il arrive que quelques unes changent. Notamment avec l’ajout dumot clé«Support».Par exemple, pour récupérer unFragmentManager, laméthodeutilisée serapublic FragmentManager getSupportFragmentManager() plutôt que public FragmentMa-nager getFragmentManager().

[[attention]] | Il faut rappeler que les fragments qui viennentdupaquetageandroid.app.Fragmentne peuvent pas être utilisés avec les classes de la bibliothèque de compatibilité (et inversement).Dans les exemples de ce tutoriel, Fragment et FragmentManager viennent tous les deux duprojet de compatibilité. Dans le cas contraire, l’application provoquera instantanément uneerreur forcée.

2.1.3.2 Le contrôleur des fragments

Jusqu’à présent, le contrôleur des interfaces était les activités. Elles se chargeaient de désérialiserles fichiers XML et de récupérer les composants graphiques pour y ajouter un comportement etdes évènements. Dorénavant, les fragments jouent ce rôle et les activités deviennent les contrô-leurs des fragments. A savoir, les contrôleurs des contrôleurs.

Il est essentiel de comprendre que les fragments ne sont pas vraiment des nouveautés dans leurrôle. Il y a qu’une déportation du code d’un concept à un autre. Le réel changement se situe dansles activités puisqu’ils doivent gérer les fragments. Son changement de rôle est la réelle nou-veauté.

15

2 Fragmenter vos projets

Les fragments fixes n’illustrent pas clairement ce principe.Même si le framework se charge d’uti-liser l’API de gestion des fragments, il n’est pas visuel pour le développeur. Pour l’illustrer, deuxfragments simples ont été confectionnés. Ils contiennent un TextView pour afficher le nom dufragment courant et un Button pour remplacer le fragment courant par le suivant. Ils ne sontpas compliqués à développer. Leurs réalisations sont disponibles sur le projet GitHub de ce tuto-riel. Ils sont nommés Dynamic1Fragment et Dynamic2Fragment. Quant à l’activité, elle a éténommée DynamicActivity. Plusieurs choses sont à savoir sur sa réalisation.

Première chose, son fichier XML d’affichage ne comportera aucun fragment mais un Frame-Layout avec un identifiant pour pouvoir le récupérer dans l’activité et remplacer son contenupar celui d’un fragment. Pour rappel, ce conteneur permet de superposer des vues à un mêmeendroit sur l’écran. Son utilisation sera plus claire par la suite.

Seconde chose, pour des soucis de structuration et d’apprentissage, une certaine architecturesimpliste sera mise en place. Dans la méthode public void onCreate(Bundle savedIns-tanceState), une méthode sera appelée : private void showFragment(final Fragmentfragment). Elle affiche le fragment donné en paramètre. Elle sera appelée dans la méthode on-Create(...) et lorsque l’utilisateur cliquera sur le bouton du fragment.

2.1.3.3 Initialisation des fragments

L’initialisation des fragments se fait par simple instanciation d’un objet d’un fragment confec-tionnéprécédemment. Par exemple, pour le premier fragmentdynamique, il faudraprocéder de lamanière suivante : Dynamic1Fragment dynamic1Fragment = new Dynamic1Fragment();.Cependant, comme il n’existe pas de petites économies, une optimisation possible serait d’ins-tancier les deux fragments comme attribut de l’activité.

// We instantiate just one time fragments during the life of the activity.private final Dynamic1Fragment mDynamic1Fragment = new Dynamic1Fragment();private final Dynamic2Fragment mDynamic2Fragment = new Dynamic2Fragment();

[[information]] | Cette économie convient pour notre exemple mais nous verrons que cela chan-gera bien vite lorsque l’application deviendra plus complexe.

2.1.3.4 Remplacer les fragments

Afficher le fragment à l’écran est la réelle nouveauté dans le développement de fragments dyna-miques. L’idée est de remplacer le contenu du conteneur, FrameLayout, par un fragment donné.Pour ce faire, il est nécessaire d’utiliser l’API des transactions des fragments, FragmentTran-saction. Sa documentation indique toutes méthodes utiles.

Plusieurs sont utilisées dans showFragment(final Fragment fragment) :

— public abstract FragmentTransaction setCustomAnimations (int enter,int exit) : Anime la transition d’un fragment à un autre en donnant l’animationd’entrée pour le nouveau fragment et l’animation de sortie pour l’ancien ;

— public abstract FragmentTransaction replace (int containerViewId,Fragment fragment) : Remplace le contenu d’un conteneur, donné en premier pa-ramètre, par un nouveau fragment ;

— public abstract FragmentTransaction addToBackStack (String name) : In-dique une valeur null pour retourner au précédent fragment lorsque l’utilisateur cliquerasur le bouton « Back ».

16

2.1 Fragment

Au final, la méthode s’implémentera de la façon suivante :

private void showFragment(final Fragment fragment) {if (fragment == null) {

return;}

// Begin a fragment transaction.final FragmentManager fm = getSupportFragmentManager();final FragmentTransaction ft = fm.beginTransaction();// We can also animate the changing of fragment.ft.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right);// Replace current fragment by the new one.ft.replace(R.id.frameLayoutListView, fragment);// Null on the back stack to return on the previous fragment when user// press on back button.ft.addToBackStack(null);

// Commit changes.ft.commit();

}

[[attention]] | Chaque transaction doit appeler la méthode commit() pour voir ses opérationss’effectuer. En cas contraire, aucune erreur ne sera lancée mais rien ne se passera.

2.1.3.5 Retenir sa position dans les fragments

Une astuce intéressante est de garder enmémoire le fragment affiché à l’écran après une recons-truction de l’activité. Il faut toujours privilégier l’expérience utilisateur. Se souvenir de lui et deses manipulations est un bon début dans cette idée. C’est pourquoi, un nouvel attribut va figurerdans la classe pour retenir le fragment courant. Il suffira alors de le sauvegarder via la méthodepublic void onSaveInstanceState(Bundle outState).

Le changement se fera dans la méthode onCreate(...) de l’activité puisqu’il faut afficher lefragment courant s’il existe en mémoire. La méthode sera complétée de la façon suivante :

if (savedInstanceState!= null) {mFragment = savedInstanceState.getString(KEY_FRAGMENT);

} else {mFragment = getIntent().getStringExtra(KEY_FRAGMENT);

}

if (mFragment!= null) {if (mFragment.equals(mDynamic1Fragment.getClass().getSimpleName())) {

showFragment(this.mDynamic1Fragment);} else if (mFragment.equals(mDynamic2Fragment.getClass().getSimpleName())) {

showFragment(this.mDynamic2Fragment);}

} else {

17

2 Fragmenter vos projets

showFragment(this.mDynamic1Fragment);}

[[attention]] | Il faut remarquer qu’une touche de réfléxivité est utilisée dans l’exemple ci-dessus.mDynamic1Fragment.getClass().getSimpleName() renvoie le nom de la classe, à savoirDynamic1Fragment. Il faut donc sauvegarder dans mFragment une valeur similaire qui se feradans la méthode private void showFragment(final Fragment fragment) de la mêmemanière avec la variable fragment. Ainsi, le fragment courant est sauvegardé.

2.1.3.6 Résultat final

Avant demontrer le résultat final, il reste uneproblématique a résoudre : comment capturer l’évè-nement des boutons des fragments dans l’activité hôte ? En effet, les fragments n’ont pas accèsà FragmentManager, ou plutôt, ce n’est pas leur rôle de contrôler les autres fragments. En tantnormal, il faut utiliser les listeners mais cette pratique sera expliquée plus tard. La techniqueutilisée est plus simple pour un premier aperçu des fragments dynamiques. Il faut utiliser unnouveau attribut dans les fichiers XML d’affichage des fragments sur les éléments Button : an-droid:onClick. Cet attribut permet de définir une méthode Java qui sera appelée automatique-ment lorsque l’utilisateur cliquera dessus.

Il n’y a que deux restrictions :

— La signature de la méthode doit être public, renvoyer void et avoir un seul paramètre dutype View ;

— Elle doit être déclarée dans une activité.

Les méthodes ressembleront à ceci pour des attributs avec les valeurs goToFragment2 et goTo-Fragment1 :

public void goToFragment1(View v) {showFragment(this.mDynamic1Fragment);

}

public void goToFragment2(View v) {showFragment(this.mDynamic2Fragment);

}

L’exécution de l’application permet d’afficher un premier fragment avec un TextView qui nechange pas et un bouton qui propose à l’utilisateur d’afficher le second écran. Lorsqu’il cliqueradessus, une animation sera exécutée vers le nouveau fragment. Lorsqu’il cliquera sur le bouton«Back », il reviendra au précédent fragment. Le résultat est le suivant :

2.1.4 Attacher un bundle

Les fragments deviennent les contrôleurs des interfaces qu’ils désérialisent et ils sont indépen-dants les uns des autres. C’est la force de ce nouveau concept mais il peut arriver, suivant les si-tuations, qu’à l’initialisation d’un fragment, des données soient nécessaires. Un exemple simpleserait de donner l’identifiant d’un article. Sur base de cette donnée, le fragment peut récupérertoutes les informations de l’article en question soit par un service web ou dans la base de donnéesinterne de l’application.

18

2.1 Fragment

Figure 2.4 – Résultat de l’exécution de l’activité DynamicActivity

19

2 Fragmenter vos projets

Les bundles ne sont pas utilisés qu’avec les activités. Les fragments aussi peuvent les utiliser.Seulement, il n’est pas question d’Intent avec les fragments puisqu’ils sont gérés par une autreAPI. La méthode avec les fragments est légèrement moins bien pensée que celle avec les activi-tés. Il faut instancier un fragment, un bundle en plaçant les informations dedans et l’attacher aufragment avec la méthode public void setArguments (Bundle args).

MyFragment fragment = new MyFragment();Bundle args = new Bundle();args.putInt(KEY_ID, id);fragment.setArguments(args);

Cette simple solution suffirait mais elle rencontre un problème : si l’instanciation d’un mêmefragment avec un bundle se fait plusieurs fois dans l’application, il faut faire une répétition ducode assez conséquente et la maintenir en cas de modification. Pour palier à cette répétition quialourdie considérablement le code, une bonne pratique est de créer uneméthode statique dans lefragment en question. Cela permet d’encapsuler toutes les informations dans le fragment et dene pas tout éparpiller dans le code. Cette méthode ressemblera à :

public static MyFragment newInstance(/* informations que je veux donner à mon fragment */) {

// renvoie une instance de mon fragment}

[[information]] | Pour rappel, static signifie que laméthode n’est pas directement attachée auxinstances créé de la classe hôte. Par exemple, il est possible de créer des instances de la classe hôtedans cette méthode.

En reprenant l’exemple de l’identifiant à passer au fragment pour afficher le contenu de l’ar-ticle sélectionné, il suffit de passer cet entier en paramètre, de reprendre le code précédemmentdonné et de renvoyer le fragment. Ainsi, dans le code de l’application, il suffira d’appeler laméthode MyFragment.newInstance(idArticle); pour automatiquement créer un fragmentavec le bundle attaché et de modifier le code à un seul endroit en cas de changements.

Pour récupérer les informations contenues dans le bundle du fragment, rien de plus simple. Lesarguments sont accessibles par saméthode d’accès public final Bundle getArguments ()qui renvoie le bundle précédemment attaché au fragment et d’en récupérer les données grâce auxidentifiants donnés à chacune.

2.1.5 En résumé

— Un fragment est une portion d’interface qui peut être attaché à plusieurs activités diffé-rentes.

— Il possède son propre cycle de vie mais il est étroitement lié à son activité hôte.— La bibliothèque de compatibilité Google permet de rendre les applications compatibles à

partir des terminaux sur Android 2.1 et supérieur.— Les fragments fixes sont attachés directement dans le fichier XML d’affichage de l’activité.— Les fragments dynamiques sont attachés pendant l’exécution de l’application via une API,

FragmentManager.

20

2.2 ListFragment

2.2 ListFragment

Les listes sont, sans aucun doute, les composants graphiques les plus utilisés dans les applica-tions mobiles, qu’elles soient sur Android, iOS ou autres. Elles permettent d’afficher facilementdes informations les unes à la suite des autres verticalement avec des vues personnalisées ou nonpour chaque ligne.

Plusieurs choses seront abordées dans ce chapitre : comment intégrer une liste mêlée à la puis-sance des fragments, personnaliser ses vues et la rendre dynamique en indiquant des vues spéci-fiques pour chaque ligne qui compose une liste.

2.2.1 Utilisation simple des listes

Il existes deux possibilités pour intégrer une liste dans une application : la renseigner dans unfichier XML d’affichage ou étendre la classe ListActivity. L’équivalent avec les fragments estsimilaire à celle des activités à l’exceptionprès qu’il faut tenir comptequ’une listenepeutpas êtreinitialisée sans un contexte et qu’elle n’est créée qu’une fois l’activité hôte du fragment créée.

2.2.1.1 Déclarer sa liste dans des fichiers XML

Une petite astuce méconnue consiste à coupler un fichier XML d’affichage avec une classe quiétend ListFragment pour indiquer une vue à afficher si aucune ligne n’est contenue dans laliste. Cet exemple illustrera les deux possibilités décrites précédemment.

Sa mise en place est assez simple. Elle nécessite simplement l’utilisation d’identifiants déclarésdans le framework Android. Parmi tous les identifiants disponibles dans @android:id/, seule-ment list et empty sont nécessaires pour les listes. Dans le fichier XML d’affichage destinéau ListFragment, il va falloir déclarer une ListView avec l’identifiant @android:id/list et,dans lemême fichier XML, aumêmeniveau que la ListView, une autre vue dont l’élément racinepossèdera un identifiant @android:id/empty. Ainsi, lors de la désérialisation, le frameworkAn-droid saura ce qu’il devra charger lorsqu’il y a des éléments dans la liste ou non.

Dans un exemple où un simple texte est affiché au centre de l’écran s’il n’y a aucun élément dansla liste, le fichier XML ressemblerait à :

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"android:layout_height="match_parent" >

<ListViewandroid:id="@android:id/list"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:layout_alignParentTop="true" />

<TextViewandroid:id="@android:id/empty"

21

2 Fragmenter vos projets

android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:layout_centerVertical="true"android:text="@string/text_empty" />

</RelativeLayout>

2.2.1.2 Création du ListFragment

Le fragment ne sera plus aussi générique que celui du chapitre précédent. Afin de profiterde la création automatique d’une ListView pour le fragment, la classe étendra ListFrag-ment, parfait équivalent de la classe ListActivity. De la même manière que les fragmentsbasiques, un ficher XML d’affichage sera désérialisé dans sa méthode public View onCreate-View(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceS-tate) (chose qui n’est pas obligatoire si aucune vue ne doit être affichée s’il n’y a aucun élémentdans la liste).

Comme avec une ListActivity, un adaptateur doit être attaché à la liste (adaptateur qui ne serapas personnalisé pour le moment). L’unique subtilité dans ce cas présent est la méthode à redé-finir. En effet, comme il faut un contexte pour initialiser un adaptateur, il faudra le créer, l’ini-tialiser et l’attacher uniquement lorsque l’activité hôte sera créée. Cela se fera dans la méthodepublic void onActivityCreated(Bundle savedInstanceState).

Ceci donne le code et le résultat suivant :

package com.siteduzero.android.lists.simple;

import com.siteduzero.android.R;

import android.os.Bundle;import android.support.v4.app.ListFragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ArrayAdapter;

public class SimpleListViewFragment extends ListFragment {

@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_listview, container, false);

}

@Overridepublic void onActivityCreated(Bundle savedInstanceState) {

super.onActivityCreated(savedInstanceState);

final String[] items = getResources().getStringArray(R.array.list_examples);

22

2.2 ListFragment

final ArrayAdapter<String> aa = new ArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_1, items);

setListAdapter(aa);}

}

2.2.2 Intégrer une vue personnalisée

La confection de vues personnalisées dans un ListFragment ne diffère pas de son équivalentavec une ListActivity. Cette personnalisation est complètement indépendante de son hôte.Par conséquent, certains aspects serontmoins expliqués que d’autresmais une piqûre de rappellesera donnée, notamment sur l’architecture à adoptée.

2.2.2.1 Créer une vue personnalisée

Il faut créer plusieurs fichiers pour confectionner sa vue : son fichier XML d’affichage et la classequi le désérialise. Dans cet exemple, chaque ligne de la liste affichera un texte contenu dans unbloc avec un ombrage. Normalement, les ombrages dans le développement Android sont pos-sibles uniquement grâce à la technologie 9-patch. Elle permet d’étendre horizontalement ouverticalement certaines images sans les pixeliser. Cependant, le framework Android met à dis-position des développeurs une série de ressources dont des ombrages.

Le fichier XML d’affichage pourra utiliser ces ressources pour afficher l’ombrage et ressembleraalors à :

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:drawable/dialog_holo_light_frame"android:orientation="vertical" >

<TextViewandroid:id="@+id/textView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="15dp"android:text="@string/default_lorem" />

</LinearLayout>

Quant à la classe qui désérialise ce fichier XML, il doit étendre un conteneur,LinearLayout, redé-finir ses constructeurs et initialiser le texte avec la valeur souhaitée. Ce dernier point est possiblegrâce à une méthode publique qui prend en paramètre l’identifiant d’une chaîne de caractèresprésente dans les ressources du projet.

package com.siteduzero.android.lists.custom;

23

2 Fragmenter vos projets

Figure 2.5 – Résultat de l’exécution d’une liste simple

24

2.2 ListFragment

import com.siteduzero.android.R;

import android.content.Context;import android.util.AttributeSet;import android.widget.LinearLayout;import android.widget.TextView;

public class CustomListViewView extends LinearLayout {private TextView mTextView;

public CustomListViewView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();

}

public CustomListViewView(Context context, AttributeSet attrs) {super(context, attrs);init();

}

public CustomListViewView(Context context) {super(context);init();

}

private void init() {inflate(getContext(), R.layout.view_custom_listview, this);mTextView = (TextView) findViewById(R.id.textView);

}

public void bind(int text) {mTextView.setText(getResources().getString(text));

}}

2.2.2.2 Créer son adaptateur

La création d’un adaptateur prend tout son sens à partir dumoment où une ListView est consti-tuée de vues personnalisées. Un adaptateur doit étendre la classe BaseAdapter, redéfinir lesméthodes obligatoires (il existe des méthodes facultatives pour des listes dynamiques, cela feral’objet du prochain point de ce chapitre) et permettre de rajouter les données à injecter dans laliste. Ces données peuvent être aussi simples que complexes. Il en revient au développeur d’in-jecter l’ « intelligence » nécessaire dans son adaptateur.

La chose à savoir dans la création d’un adaptateur est la façon d’implémenter laméthode publicView getView(int position, View convertView, ViewGroup parent). Dans les appli-cationsmobiles, il est nécessaire d’économiser lamoindre zonemémoire possible. Ainsi, lorsquel’utilisateur parcourt une liste, si la vue d’une ligne est encore en zonemémoire, il est préférablede la réutiliser plutôt que d’en créer une nouvelle. Raison pour laquelle cette méthode renvoie

25

2 Fragmenter vos projets

une vue en second paramètre.

L’adaptateur s’implémente donc de la manière suivante :

package com.siteduzero.android.lists.custom;

import java.util.ArrayList;import java.util.List;

import android.content.Context;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;

public class CustomListViewAdapter extends BaseAdapter {private List<Integer> mModel = new ArrayList<Integer>();private Context mContext;

public CustomListViewAdapter(Context context) {mContext = context;

}

@Overridepublic int getCount() {

return mModel.size();}

@Overridepublic Integer getItem(int position) {

return mModel.get(position);}

@Overridepublic long getItemId(int position) {

return position;}

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {

CustomListViewView v = null;// Notre vue n'a pas encore été construite, nous le faisonsif (convertView == null) {

v = new CustomListViewView(mContext);} // Notre vue peut être récupérée, nous le faisonselse {

v = (CustomListViewView) convertView;}v.bind(getItem(position));return v;

26

2.2 ListFragment

}

public void bind(List<Integer> model) {mModel = model;

}}

Puisque l’objectif est d’utiliser les fragments, le fragment hôte s’occupera d’envoyer les donnéesvia la méthode public void bind(List<Integer> model) de l’adaptateur après son initia-lisation. L’utilisation d’un adaptateur personnalisé est toute aussi simple qu’un des adaptateursdonnés à la disposition des développeurs par défaut dans le framework. Le résultat correspond àla capture suivante :

2.2.3 Vers des listes dynamiques

Une liste dynamique réside dans les différentes vues possibles à appliquer pour ses lignes. A laplace de s’arranger avec une ScrollView et d’ajouter des vues dynamiquement à ce conteneurgrâce à d’autres conteneurs, le framework Android fournit une solution élégante juste en redéfi-nissant deux autres méthodes dans un adaptateur personnalisé.

2.2.3.1 Nouvelles méthodes de l’adaptateur

L’exemple qui illustrera cette liste dynamique consistera à afficher un«header» avec une imageà gauche et du texte à droite. Une seconde vue sera un«body»avec simplement du texte (commela vue du point précédent de ce chapitre). La création de ces vues ne sera pas abordée dans la suitede ce chapitre vu sa simplicité.

Concernant l’adaptateur, pour indiquer qu’il va devoir gérer plusieurs vues différentes dans saliste, deux méthodes, liées au type de la vue courante à instancier, doivent être redéfinies :

— public int getViewTypeCount() : Indique combien de vues différentes comportent laliste ;

— public int getItemViewType(int position) : Indique le type de la vue à instancierpour la ligne courante.

Pour implémenter cesméthodes, il faudra plusieurs constantes et une liste représentant les types.Cette manière de faire est la plus simple et pas du tout obligatoire. Il en revient au développeurde l’adapter pour son propre problème.

L’adaptateur dynamique pourrait ressembler au code cité ci-présent :

public class DynamicListViewAdapter extends BaseAdapter {private static final int TYPE_HEADER = 0;private static final int TYPE_BODY = 1;private static final int TYPE_MAX = 2;private List<Integer> mTypes = new ArrayList<Integer>();

// Autres attributs

@Overridepublic int getViewTypeCount() {

27

2 Fragmenter vos projets

Figure 2.6 – Résultat de l’exécution d’une liste avec une vue personnalisée

28

2.2 ListFragment

return TYPE_MAX;}

@Overridepublic int getItemViewType(int position) {

return mTypes.get(position);}

// Autres méthodes}

La mise en place est aussi simple que cela. Il faut aussi permettre de remplir la liste des types enles faisant correspondre avec les données. Comme le point précédent sur les listes personnalisées,il faut définir desméthodes pour attacher une liste de données à celle de l’adaptateur et s’occuperde la liste des types par la même occasion. Il en faudra deux :

— L’une pour l’en-tête et une seconde pour le corps de la liste. La première méthode prenden paramètre unmodèle qui se trouve être une classe confectionnée spécialement pour cetexemple avec seulement deux attributs représentant une image et un texte ;

— La seconde est identique à la méthode du point précédent, elle prendra une liste d’identi-fiants de chaînes de caractères.

L’implémentation de ces méthodes pourraient ressembler à :

public class DynamicListViewAdapter extends BaseAdapter {// Autres attributs et méthodes

public void bindHeader(DynamicListViewModel model) {mModelHeader = model;mTypes.add(TYPE_HEADER);

}

public void bindBody(List<Integer> model) {mModelBody = model;for (int i = 0; i < model.size(); i++) {

mTypes.add(TYPE_BODY);}

}}

2.2.3.2 Modification de l’existant

Ces ajouts ont des répercussions sur l’implémentation des méthodes existantes. Le nombre delignes et la ligne courante de la liste ne correspondent plus à une seule liste de données. Toutcomme la méthode d’instanciation de la vue courante qui doit se charger d’instancier la bonnevue. Les deux premières méthodes s’implémentent assez facilement pour cet exemple :

public class DynamicListViewAdapter extends BaseAdapter {// Autres attributs et méthodes

29

2 Fragmenter vos projets

@Overridepublic int getCount() {

if (mModelHeader == null)return mModelBody.size();

return 1 + mModelBody.size();}

@Overridepublic Object getItem(int position) {

int type = getItemViewType(position);return type == TYPE_HEADER? mModelHeader : mModelBody

.get(position - 1);}

// Autres méthodes}

La méthode public View getView(int position, View convertView, ViewGroup pa-rent) est un poil plus complexe mais, au final, ne fait qu’utiliser les autres méthodes de l’adap-tateur. Il faut récupérer le type de la ligne courante au début de la méthode pour pouvoir instan-cier la vue adéquate. Son implémentation est très similaire à la précédente, mise à part qu’elleeffectue une série de condition en fonction de ce type.

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {

View v = null;int type = getItemViewType(position);

if (convertView == null) {switch (type) {case TYPE_HEADER:

v = new DynamicHeaderListViewView(mContext);break;

case TYPE_BODY:v = new DynamicBodyListViewView(mContext);break;

}} else {

switch (type) {case TYPE_HEADER:

v = (DynamicHeaderListViewView) convertView;break;

case TYPE_BODY:v = (DynamicBodyListViewView) convertView;break;

}}

switch (type) {

30

2.3 Interfaces dynamiques

case TYPE_HEADER:DynamicListViewModel model1 = (DynamicListViewModel) getItem(position);((DynamicHeaderListViewView) v).bind(model1.getImageRessource(),

model1.getTextRessource());break;

case TYPE_BODY:Integer model2 = (Integer) getItem(position);((DynamicBodyListViewView) v).bind(model2);break;

}

return v;}

Il n’en faut pas plus pour concevoir une liste dynamique. Tous les changements se situent au ni-veau de l’adaptateur. Il suffit alors d’intégrer ce nouvel adaptateur à l’activité ou le fragment hôteen n’oubliant pas d’appeler les méthodes pour lier les données à la liste. Le résultat de l’exempleressemble à ceci :

2.2.4 En résumé

ListFragment est une sous classe de Fragment qui contient automatiquement une ListView ;

— Il est possible d’afficher une vue à l’écran lorsqu’il n’y a aucun élément dans la liste. Celagrâce à un fichier XML d’affichage attaché à une ListFragment ;

— Il est nécessaire de développer son propre adaptateur lorsque les vues, qui composent uneliste, deviennent complexes ;

— Une liste dynamique oblige la redéfinition de deux méthodes supplémentaires dansson adaptateur : public int getViewTypeCount() et public int getItemView-Type(int position).

2.3 Interfaces dynamiques

La création des fragments est maintenant chose connuemais, jusqu’à présent, ils ont été assimi-lés à l’écran complet d’un smartphone. Le promesse derrière ce concept est toute autre puisqueson but est de dynamiser les interfaces à travers les différents appareils (qu’ils soient smart-phones, tablettes, télévisions ou autres). De brefs aperçus sur les possibilités, ont été donnésmais ni les bonnes pratiques ni la communication entre les fragments n’ont été expliquées.

2.3.1 Concevoir une interface dynamique

Concevoir une interface n’est pas une chose aisée, surtout sur Android. Au vu de la diversité desécrans sur tous les types d’appareils mobiles, il faut avant tout penser ses interfaces avant de lesdévelopper. Par exemple, un smartphone se contentera d’afficher un fragment à la fois (qu’il soiten paysage ou en portrait) alors qu’une tablette pourrait afficher deux fragments en portrait et

31

2 Fragmenter vos projets

Figure 2.7 – Résultat de l’exécution d’une liste avec plusieurs vues personnalisées

32

2.3 Interfaces dynamiques

trois en paysage (encore faut-il savoir s’il s’agit d’une tablette 7 ou 10 pouces). C’est encore uneautre histoire avec les télévisions mais le problème reste le même.

En s’imposant une limite aux smartphones et aux tablettes, peu importe leurs tailles, une pra-tique d’ergonomie reconnue consiste a afficher un fragment sur smartphone et deux sur tabletteen mode paysage. Cela permet de naviguer convenablement sur smartphone (comme toutes lesapplications) et de remplir complètement l’écran d’une tablette forcément plus grande.

Figure 2.8 – Deux fragments affichés dans différentes configurations

L’objectif est d’ajouter un fragment à une activité pendant son exécution, dans les deux cas. Pourles smartphones, il faut changer le fragment visible à l’écran. Pour les tablettes, charger lesdonnées nécessaires si une communication est à faire entre le fragment A et le fragment B. Parexemple, le fragment A pourrait être une liste de pays et le fragment B pourrait vouloir afficherson drapeau. Les deux fragments doivent communiquer et rester affichés à l’écran.

[[information]] | Pour simplifier d’avantage, l’exemple d’illustration ne fera aucune distinctionentre les appareils. Les interfaces seront pensées enmode portrait et paysage à la place des taillesminimales.

Lamanière de procéder est simple et requiert l’utilisation de l’API de gestion des fragments. Cou-plé à cela, la création des deux « layouts » du même nom pour déclarer l’interface en mode por-trait et paysage. Le but est d’obtenir le résultat de l’illustration précédente sans tenir compte dutype d’appareil.

2.3.2 Initialiser l’activité hôte

Pour illustrer la communication entre des fragments, il va falloir réviser quelques drapeaux euro-péens. L’idée est d’afficher une liste de pays et son drapeau correspondant. Le projet nécessiteradeux fragments : Un premier pour la liste des pays, un second pour le drapeau du pays sélectionnépar l’utilisateur. Le mode portrait affichera les fragments en plein écran en les remplaçant à lademande de l’utilisateur alors que le mode paysage affichera la liste et le drapeau l’un à côté del’autre. Dans ce dernier cas, la communication prendra un sens tout autre puisque lamodificationdevra se faire directement sans charger un nouveau fragment.

[[attention]] | La confection des fragments ne seront pas abordées. En cas de doute, revenez auxexplications précédentes dans Fragment ou ListFragment.

33

2 Fragmenter vos projets

Intégrer les deux fragments dans une activité hôte n’est pas plus difficile que l’utilisation desfragments dynamiques. Pour le mode paysage, il faut renseigner les deux fragments directementdans le fichier XML d’affichage du dossier des layouts correspondant, à savoir « layout-land ».Dans un souci de visibilité et d’ergonomie, il est préférable de privilégier le contenu à la liste.Dans cet exemple, il s’agit d’un drapeau mais cela aurait pu être un courrier, un site web, etc. Lefichier XML ressemblera à ceci :

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"android:layout_height="match_parent"android:baselineAligned="false"android:orientation="horizontal" >

<fragmentandroid:id="@+id/fragmentList"android:name="com.siteduzero.android.dynamicui.CountryListFragment"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="2" />

<fragmentandroid:id="@+id/fragmentDetails"android:name="com.siteduzero.android.dynamicui.CountryDetailsFragment"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1" />

</LinearLayout>

Quant aumode portrait, il est légèrement plus complexe puisqu’il faut rendre dynamique le frag-ment à afficher à l’écran. Dans le fichier XML d’affichage du dossier layout par défaut, il suffit dedéclarer un simple conteneur FrameLayout avec un identifiant afin de pouvoir l’utiliser avec lesAPIs de gestion des fragments.

<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/frameLayoutDynamicUi"android:layout_width="match_parent"android:layout_height="match_parent" />

Pour finir, dans la méthode public void onCreate(Bundle savedInstanceState) del’activité, il faut rajouter le fragment contenant la liste, CountryListFragment, dans le Frame-Layout. Dans le cas contraire, les fragments s’afficheront correctement en mode paysage maisils ne s’afficheront pas en mode portrait. Pour y parvenir, il faut récupérer une instance d’unetransaction, FragmentTransaction, via le manager, FragmentManager, et utiliser la méthodepublic FragmentTransaction add(int containerViewId, Fragment fragment) enlui passant une instance du fragment.

@Overridepublic void onCreate(Bundle savedInstanceState) {

34

2.3 Interfaces dynamiques

super.onCreate(savedInstanceState);setContentView(R.layout.activity_dynamic_ui);

if (findViewById(R.id.frameLayoutDynamicUi)!= null) {final CountryListFragment listFragment = new CountryListFragment();getSupportFragmentManager().beginTransaction()

.add(R.id.frameLayoutDynamicUi, listFragment).commit();}

}

2.3.3 Communication entre les fragments

Au ce stade, l’application s’exécute correctement et affiche une liste enmode portrait et les deuxfragments avec une liste à gauche et un drapeau à droite mais avec aucune interaction dans lesdeux cas en mode paysage. Pour remédier à ce problème, il faut utiliser des listeners pour per-mettre une communication entre un fragment A et un fragment B en passant par une activitéhôte. En effet, l’activité va devoir gérer les messages entre les fragments qu’elle contient.

Dans le fragment CountryListFragment, il faut déclarer une interface, OnCountrySelected-Listener, et un attribut du même type. Cette interface sera implémentée par l’activité afin depouvoir transmettre le message du fragment contenant la liste vers celui contenant le drapeaupour savoir lequel il faut afficher à l’écran. Pour initialiser l’attribut, les fragments peuvent redé-finir laméthode public void onAttach(Activity activity) afin de connaitre son activitéhôte et caster son instance dans le listener. Le fragmentmettra en place le listener de la manièresuivante :

package com.siteduzero.android.dynamicui;

import android.app.Activity;import android.os.Bundle;import android.support.v4.app.ListFragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.ArrayAdapter;

import com.siteduzero.android.R;

public class CountryListFragment extends ListFragment implementsOnItemClickListener {

private OnCountrySelectedListener mListener = null;

// Autres méthodes

@Overridepublic void onAttach(Activity activity) {

super.onAttach(activity);

35

2 Fragmenter vos projets

try {mListener = (OnCountrySelectedListener) activity;

} catch (ClassCastException e) {// Unchecked exception.throw new ClassCastException(activity.toString()

+ " must implement OnCountrySelectedListener");}

}

public interface OnCountrySelectedListener {void onCountrySelected(int position);

}

@Overridepublic void onItemClick(AdapterView<?> arg0, View arg1, int position,

long arg3) {if (mListener!= null) {

mListener.onCountrySelected(position);}

}}

Lorsque l’utilisateur clique sur un élément de la liste, il faut tester si le listener a été initialisépour éviter tout crash de l’application. Il vaut mieux une application qui ne répond pas qu’uneapplication qui quitte de manière imprévue (dans l’éventualité où il n’a pas été initialisé). Cettepratique est une bonne habitude à prendre et évite tous les risques liés à une refactorisation ducode ou autres manipulations du même genre.

Dans l’activité hôte, il faut prévoir les différents cas :

— Soit l’utilisateur est enmode portrait et il faut remplacer les fragments en passant le frag-ment à afficher ;

— Soit l’utilisateur est en mode paysage et il faut mettre à jour le drapeau à afficher.

Détecter la configuration de l’appareil est identique à la solution implémentée lors de la créationde l’activité. Si le conteneur FrameLayout est différent de null, l’utilisateur est en mode por-trait sinon il est en mode paysage. Dans le premier cas, il suffit d’utiliser une transaction pourremplacer le fragment dans le conteneur. Dans le second, il faut déclarer une méthode dans lefragment CountryDetailsFragment pour mettre à jour le drapeau selon la position donnée àpartir de la méthode public void onCountrySelected(int position).

Elle s’implémente de la façon suivante :

@Overridepublic void onCountrySelected(int position) {

if (findViewById(R.id.frameLayoutDynamicUi) == null) {// If we are in landscape mode, we show article in the second// fragment.final CountryDetailsFragment detailsFragment = (CountryDetailsFragment) getSupportFragmentManager().findFragmentById(R.id.fragmentDetails);detailsFragment.updateCountry(position);

} else {

36

2.3 Interfaces dynamiques

// Else, we show the other fragment in portrait mode.final CountryDetailsFragment detailsFragment = CountryDetailsFragment.newInstance(position);final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();ft.replace(R.id.frameLayoutDynamicUi, detailsFragment);ft.addToBackStack(null);ft.commit();

}}

Maintenant, à l’exécution de l’application, la communication est opérationnelle et elle met bienà jour le drapeau à afficher en mode paysage ou elle crée le fragment avec le bon drapeau à affi-cher dans le mode portrait. Cet exemple est assez simple mais les interfaces peuvent être pluscomplexes. Il en revient au développeur d’y ajouter l’intelligence nécessaire pour convenir auxbesoins de ses applications.

Figure 2.9 – Résultat de l’exécution d’une interface dynamique en mode paysage

2.3.4 En résumé

— Il faut penser ses interfaces pour les différentes tailles d’écran et dans les différentes confi-gurations ;

— Une activité reste indépendante des fragments qu’elle contient ;— Une transaction permet de gérer des fragments dans une activité hôte ;— Un listener permettent de communiquer d’un fragment A à un fragment B en passant par

une activité hôte.

37

2 Fragmenter vos projets

2.4 PreferenceFragment

Les paramètres ont une place à part dans une application Android. Une bonne pratique de designest de les intégrer à l’apparence du système. Même si ceci peut constraster avec une applicationau design élaboré, l’utilisateur Android est habitué à ce pattern et en sera moins déstabilisé. Leframework Android fournit une solution pour faciliter la vie du développeur en lui permettant deconfectionner rapidement ce type d’écran.

Mais il persiste une restriction non négligeable. La bibliothèque de compatibilité, développée parGoogle, est constamment utilisée à travers ce tutoriel mais elle ne compte pas parmi ses classesles préférences. Le framework Android propose deux solutions pour implémenter cette fonction-nalité. Elles seront toutes les deux expliquéesmais la rétro-compatibilité sera plus complexe queles concepts vus précédemment.

2.4.1 Définir ses préférences

Paramétriser une application n’est pas une obligation. Certaines applications n’ont pas forcé-ment besoin de sauvegarder quelque chose. De plus, utiliser l’affichage des paramètres du sys-tèmen’est pas toujours souhaitémême si c’est vivement recommandé par les guidelines Android.

UnutilisateurAndroid a l’habitudede se rendre dans les paramètres de son téléphonepour activerleWi-Fi, le Bluetooth, le NFC, gérer le son, la luminosité, etc. Toutes ces choses sont« standardi-sées » (dans le sens où c’est l’affichage voulu par Google). L’utilisateur est familier avec ce typed’écran. Il sait instinctivement comment l’utiliser. Il n’est alors pas nécessaire d’entamer unephase d’apprentissage qui pourrait en rebuter certains.

2.4.1.1 Créer des groupes de préférences

La définition des préférences se fait simplement grâce à un fichier XML. Ce fichier doit se trouverdans le dossier xml des ressources du projet et son contenu doit avoir comme racine l’élémentPreferenceScreen. A partir de là, il existe trois possibilités pour confectionner les préférences :

La première est de catégoriser plusieurs préférences en les regroupant. Il rajoute un titre soulignésur toute la largeur de l’écran et liste les préférences de la catégorie en dessous. L’élément parentest PreferenceCategory et les préférences doivent se placer comme ses fils.

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

<PreferenceCategoryandroid:key="pref_key_category_1"android:title="@string/title_category_1" ><CheckBoxPreference

android:defaultValue="false"android:key="pref_key_pref_1"android:summary="@string/summary_pref_checkbox"android:title="@string/title_pref_checkbox" />

<Preferenceandroid:dependency="pref_key_pref_1"

38

2.4 PreferenceFragment

android:key="pref_key_pref_2"android:summary="@string/summary_pref_simple"android:title="@string/title_pref_simple" />

<SwitchPreferenceandroid:key="pref_key_pref_3"android:summary="@string/summary_pref_switch"android:switchTextOff="@string/switch_off_switch"android:switchTextOn="@string/switch_on_switch"android:title="@string/title_pref_switch" />

</PreferenceCategory></PreferenceScreen>

La seconde est de placer les préférences dans un nouvel écran. Une préférence sera créée sur lepremier écran et lorsque l’utilisateur cliquera dessus, un second écran affichera les nouvelles pré-férences. L’élément parent est PreferenceScreen et les préférences doivent se placer commeses fils.

<PreferenceScreenandroid:key="pref_key_screen"android:persistent="false"android:title="@string/title_screen" ><Preference

android:summary="@string/summary_pref_simple"android:title="@string/title_pref_simple" />

</PreferenceScreen>

La dernière consiste à déclarer les préférences directement, sans catégorisation ou sous écran.L’inconvénient est le risque d’avoir son seul et unique écran assez brouillon s’il y a trop de préfé-rences.

2.4.1.2 Les différentes préférences

Il existe plusieurs types de préférence. Elles sont disponibles dans des classes java mais ellespeuvent être utilisées directement dans le fichier XML destiné aux préférences en spécifiant unnom identique à l’une de ces classes. Les préférences disponibles sont les suivantes :

— CheckBoxPreference peut retenir un booléen en cochant une case ;— SwitchPreference peut retenir un booléen avec un switcher On/Off ;— EditTextPreference peut sauvegarder une chaîne de caractères ;— RingtonePreference peut saisir une sonnerie à partir du système ou d’une application

proposant de la musique ;— Preference affiche une valeur non éditable. Elle est souvent couplée avec une autre pré-

férence pour mettre à jour sa valeur. Raison pour laquelle elle possède un attribut supplé-mentaire, android:dependency, pour référencer la préférence dont elle est dépendante ;

— ListPreference est la plus complexedespréférences. Elle définie une liste de valeurs quel’utilisateur peut sélectionner. Pour l’initialiser, il faut renseigner plusieurs nouveaux at-tributs : android:entries pour donner le tableau des valeurs qui seront affichés à l’utili-sateur et android:entryValues pour afficher les identifiants de chaque valeur des lignesdu tableau ;

39

2 Fragmenter vos projets

— MultiSelectListPreference est identique à ListPreference mais permet de sélec-tionner plusieurs lignes dans la liste.

Parmi les attributs, plusieurs sont communs à toutes les préférences, voire certains essentielspour pouvoir afficher quelque chose sur la ligne de la préférence ou pour récupérer sa valeur paraprès dans l’application :

— android:key permet de spécifier un identifiant pour la préférence et permettre de récu-pérer sa valeur dans le projet ;

— android:title permet de donner un nom à afficher pour la préférence ou la catégorie ;— android:summary pour donner des précisions supplémentaires sur la préférence ;— android:defaultValue pour donner une valeur par défaut à la préférence ;— android:persistent pour indiquer si la préférence devra être sauvegardée ou non.

2.4.2 Intégrer ses paramètres dans une activité

L’intégration dans une activité dépendra de la version d’Android. Le framework fournit un frag-ment, PreferenceFragment, utilisable comme tous les fragments rencontrés jusqu’à présentmais son utilisation est limité à l’API 11. Il existe deux méthodes pour intégrer les préférences.L’une pour les versions antérieures à la version 3 et l’autre pour les versions supérieures. Sachantque ces deux méthodes peuvent cohabiter pour être compatibles pour toutes les versions.

2.4.2.1 Antérieur à la version 3

[[attention]] | Malgré le fait que toutes les méthodes, qui seront présentées dans ce point, soientmarquées « dépréciées » par l’environnement de travail Eclipse, il n’en reste pas moins le seulmoyen actuel pour intégrer un panneau de préférences pour les versions antérieures à 3.0.

Sans la bibliothèque de compatibilité, les versions 2.x ne peuvent pas utiliser les fragments. C’estpourquoi le framework fournit une activité supplémentaire uniquement destinée auxpréférences,PreferenceActivity. Cette activité étend ListActivity pour fournir une liste qu’il suffit deremplir par les préférences définies dans le fichier XML.

Pour peupler cette liste, il n’est pas question de désérialiser le fichier XML mais d’appeler laméthode, public void addPreferencesFromResource(int preferencesResId), en ren-seignant le fichier XML par son identifiant. Elle sera appelée dans la méthode public voidonCreate(Bundle savedInstanceState) de l’activité. Son implémentation se rapprocheradonc de l’exemple suivant :

package com.siteduzero.android.settings;

import android.preference.PreferenceActivity;

import com.siteduzero.android.R;

public class SettingsActivity extends PreferenceActivity {

@Overridepublic void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

40

2.4 PreferenceFragment

addPreferencesFromResource(R.xml.preferences);}

}

2.4.2.2 Postérieur à la version 3

En revanche, pour les versions plus récentes, la solution est bien plus élégante puisqu’elle uti-lise pleinement la puissance des fragments. La classe PreferenceFragment est conçu pour ac-cueillir les préférences d’une application. La méthode est exactement la même qu’avec une ac-tivité. Il suffit d’appeler la méthode public void addPreferencesFromResource(int pre-ferencesResId) dans la méthode public void onCreate(Bundle savedInstanceState)du fragment.

Quant à l’activité hôte, comme il n’est pas question d’utiliser la bibliothèque de compatibilité,une simple activité suffit pour le gérer. A noter que le framework supporte aussi l’intégration dufragment dans la même activité que pour les versions antérieures à la 3, PreferenceActivity.

Malgré le fait qu’il soit possible de gérer les préférences dans la classe java, le fragment est rare-ment plus complexe que l’exemple ci-présent.

package com.siteduzero.android.settings;

import android.os.Bundle;import android.preference.PreferenceFragment;

import com.siteduzero.android.R;

public class SettingsFragment extends PreferenceFragment {@Overridepublic void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

addPreferencesFromResource(R.xml.preferences);}

}

2.4.3 Affiner ses paramètres avec les en-têtes

Tout comme les catégories ou les sous écrans, les en-têtes ont pour objectif de rassembler despréférences et de les structurer mais à un niveau supérieur. Contrairement aux catégories (quisont recommandées), une petite application aura rarement besoin d’utiliser ce nouveau concept.Cela reste tout de même une bonne chose à connaître.

D’autant plus qu’ils sont largement utilisés, notamment dans l’application des préférences dusystème à partir d’Android 3.0. Structurer une telle application est nécessaire au vu de la quantitédes préférences. Elle utilise à la fois les en-têtes, les catégories, les sous écrans et les préférences.A noter qu’autant de préférences pourraient décourager certains utilisateurs. Il faut tenter derester le plus simple possible tout en restant complet.

41

2 Fragmenter vos projets

Figure 2.10 – Exécution des préférences sur une version supérieure à Android 3.0

42

2.4 PreferenceFragment

2.4.3.1 Supérieur à la version 3

Comme la définition des préférences, les en-têtes se définissent dans un fichier XML contenudans le même dossier ressource, res/xml. L’élément racine est preference-headers. Ses filsseront obligatoirement des éléments header qui comportent une série d’attributs obligatoirespour référencer le fragment des préférences et ses informations personnelles.

— android:fragment renseigne le chemin vers le fragment représentant l’écran des préfé-rences ;

— android:title renseigne le titre de l’en-tête ;— android:summary renseigne les précisions sur l’en-tête ;— android:icon renseigne l’icône à ajouter à gauche de l’en-tête.

La définition des en-têtes n’est pas bien complexe. L’exemple ci-dessous illustre les attributsprésentés en déclarant deux en-têtes qui pointent vers deux fragments différents.

<?xml version="1.0" encoding="utf-8"?><preference-headersxmlns:android="http://schemas.android.com/apk/res/android" >

<headerandroid:fragment="com.siteduzero.android.settings.EditFragment"android:icon="@android:drawable/ic_menu_edit"android:summary="@string/summary_header_edit"android:title="@string/title_header_edit" >

</header><header

android:fragment="com.siteduzero.android.settings.AgendaFragment"android:icon="@android:drawable/ic_menu_agenda"android:summary="@string/summary_header_agenda"android:title="@string/title_header_agenda" >

</header>

</preference-headers>

En ce qui concerne l’activité hôte, il n’est plus question de gérer son fragment avec l’API de ges-tion des fragments. L’activité PreferenceActivity permet de redéfinir la méthode publicvoid onBuildHeaders(List<Header> target) qui sera appelée uniquement si l’applicationest exécutée sur un terminal dont la version d’Android est supérieure à 3.0. Dans cette méthode,il faut charger les en-têtes grâce à public void loadHeadersFromResource (int resid,List<PreferenceActivity.Header> target) :

package com.siteduzero.android.settings;

import java.util.List;

import android.preference.PreferenceActivity;

import com.siteduzero.android.R;

public class SettingsActivity extends PreferenceActivity {

43

2 Fragmenter vos projets

// This methods is called with Android 3.0 and higher.@Overridepublic void onBuildHeaders(List<Header> target) {

loadHeadersFromResource(R.xml.settings_headers, target);}

}

[[information]] | L’utilisation des en-têtes avec une PreferenceActivity propose automa-tique un affichage multi-fragment en mode paysage sur les tablettes. Les en-têtes s’affichentsur une colonne à gauche et les préférences, de l’en-tête sélectionné sur une colonne plus grandeà droite. Cela peut simplifier la vie du développeur.

L’exécution sur un terminal avec une version récente d’Android donne le résultat suivant pourl’affichage des en-têtes.

2.4.3.2 Antérieur à la version 3

La cohabitation reste possible avec les en-têtes mais demande une légère duplication dans lesressources en créant un nouveau fichier XML et d’ajouter des informations dans l’activité hôte.Du côté de la ressource, les vieilles versions ne connaissent pas les en-têtes. Il faut donc ruser enutilisant les éléments existants pour émuler des en-têtes.

En regardant de plus près, les en-têtes ressemblent étrangement à de simples préférences quiouvrent un sous écran. Bien que cela soit une solution envisageable, ce n’est pas lameilleure. S’ilest possible de réutiliser les fichiers XML existants, définissant les écrans, cela rendrait le fichierdes en-têtesmoins lourd. Raison pour laquelle, il va falloir déclarer des Preference avec commefils un élément Intent qui va indiquer l’écran à charger.

Ce nouvel élément déclare trois attributs nécessaires pour indiquer la bonne préférence :

— android:action indique l’action assignée ;— android:targetClass indique la classe qui va recevoir l’Intent ;— android:targetPackage indique le paquetage de la classe cible.

L’exemple démontre l’utilisation de préférences avec les Intent pour émuler les en-têtes pourêtre parfaitement compatible avec les vieilles versions.

<?xml version="1.0" encoding="utf-8"?><PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

<Preferenceandroid:icon="@android:drawable/ic_menu_edit"android:summary="@string/summary_header_edit"android:title="@string/title_header_edit" ><intent

android:action="com.siteduzero.android.settings.EDIT"android:targetClass="com.siteduzero.android.settings.SettingsActivity"android:targetPackage="com.siteduzero.android.settings" />

</Preference><Preference

android:icon="@android:drawable/ic_menu_agenda"android:summary="@string/summary_header_agenda"

44

2.4 PreferenceFragment

Figure 2.11 – Résultat de l’exécution des en-têtes

45

2 Fragmenter vos projets

android:title="@string/title_header_edit" ><intent

android:action="com.siteduzero.android.settings.AGENDA"android:targetClass="com.siteduzero.android.settings.SettingsActivity"android:targetPackage="com.siteduzero.android.settings" />

</Preference>

</PreferenceScreen>

Du côté de l’activité hôte, c’est un petit plus complexe. Il faut pouvoir ajouter les en-têtes desdeux versions et, dans le cas des vieilles versions, traité l’Intent en affichant le bon écran depréférences. La solution pour les versions récentes reste indépendante. Elle ne gène pas la coha-bitation. En revanche, l’inverse n’est pas aussi simple.

L’ajout des en-têtes doit se faire uniquement si la version d’Android est plus petite qu’Honey-comb et l’écran adéquat doit être affiché en fonction de l’action de l’Intent. Dans la méthodepublic void onCreate(Bundle savedInstanceState) de l’activité, il faut récupérer l’ac-tion de l’Intent et tester sa valeur avec les différentes actions des en-têtes. Si aucune actionn’est disponible dans l’Intent, cela veut dire qu’il faut afficher les en-têtes.

package com.siteduzero.android.settings;

import java.util.List;

import android.os.Build;import android.os.Bundle;import android.preference.PreferenceActivity;

import com.siteduzero.android.R;

public class SettingsActivity extends PreferenceActivity {private static final String ACTION_PREF_EDIT = "com.siteduzero.android.settings.EDIT";private static final String ACTION_PREF_AGENDA = "com.siteduzero.android.settings.AGENDA";

@SuppressWarnings("deprecation")@Overridepublic void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

// Preferences for Android 2.3 and lower.final String settings = getIntent().getAction();// Show screen if a preference if header send anif (ACTION_PREF_EDIT.equals(settings)) {

addPreferencesFromResource(R.xml.settings_edit);} else if (ACTION_PREF_AGENDA.equals(settings)) {

addPreferencesFromResource(R.xml.settings_agenda);} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {

// Show header if there aren't intent.addPreferencesFromResource(R.xml.settings_headers_legacy);

}

46

2.4 PreferenceFragment

}

// This methods is called with Android 3.0 and higher.@Overridepublic void onBuildHeaders(List<Header> target) {

loadHeadersFromResource(R.xml.settings_headers, target);}

}

2.4.3.3 Passer un bundle

Avec l’utilisation des PreferenceFragment, cela peut paraître lourd de devoir créer un fragmentpar fichier XML. Les en-têtes peuvent palier à ce problème en prenant un Extra comme fils. Cetélément se place dans les arguments du fragment cible en rajoutant la valeur donnée dans sonattribut android:value à la clé donnée par l’attribut android:name.

Le fragment cible reste identique pour chaque en-tête mais la valeur, pour la même clé, dans lesextras est différente pour savoir quel écran des paramètres afficher.

<?xml version="1.0" encoding="utf-8"?><preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >

<headerandroid:fragment="com.siteduzero.android.settings.SettingsFragment"android:icon="@android:drawable/ic_menu_edit"android:summary="@string/summary_header_edit"android:title="@string/title_header_edit" ><extra

android:name="settings"android:value="header_edit" />

</header><header

android:fragment="com.siteduzero.android.settings.SettingsFragment"android:icon="@android:drawable/ic_menu_agenda"android:summary="@string/summary_header_agenda"android:title="@string/title_header_agenda" ><extra

android:name="settings"android:value="header_agenda" />

</header>

</preference-headers>

L’unique fragment doit simplement gérer l’argument et ajouter les préférences à partir de saressource suivant la valeur donnée.

package com.siteduzero.android.settings;

import android.os.Bundle;import android.preference.PreferenceFragment;

47

2 Fragmenter vos projets

import com.siteduzero.android.R;

public class SettingsFragment extends PreferenceFragment {private static final String KEY_SETTINGS = "settings";private static final String HEADER_EDIT = "header_edit";private static final String HEADER_AGENDA = "header_agenda";

@Overridepublic void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

// Show right preference due to arguments in the fragment.final String settings = getArguments().getString(KEY_SETTINGS);if (HEADER_EDIT.equals(settings)) {

addPreferencesFromResource(R.xml.settings_edit);} else if (HEADER_AGENDA.equals(settings)) {

addPreferencesFromResource(R.xml.settings_agenda);}

}}

2.4.4 Lire les préférences

La lecture des préférences se fait par l’intermédiaire des SharedPreference, utilisables endehors des préférences. Il suffit de récupérer les préférences partagées par défaut via la méthodestatique public static SharedPreferences getDefaultSharedPreferences(Contextcontext) de la classe PreferenceManager.

Une fois l’instance des préférences partagées, il faut récupérer les valeurs des différentes préfé-rences via l’identifiant renseigné dans le fichier XML. Le code de cet exemple pourrait être in-tégré dans la méthode onResume de l’activité ou du fragment pour recharger les données despréférences après une mise en pause de l’activité hôte.

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);boolean pref1 = prefs.getBoolean("pref_key_pref_1", false);mTextViewPref1.setText("" + pref1);String pref2 = prefs.getString("pref_key_pref_2", "Nothing");mTextViewPref2.setText("" + pref2);boolean pref3 = prefs.getBoolean("pref_key_pref_3", false);mTextViewPref3.setText("" + pref3);String pref4 = prefs.getString("pref_key_pref_4", "Nothing");mTextViewPref4.setText("" + pref4);String pref5 = prefs.getString("pref_key_pref_5", "Nothing");mTextViewPref5.setText("" + pref5);String pref6 = prefs.getString("pref_key_pref_6", "Nothing");mTextViewPref6.setText("" + pref6);

En affichant simplement ses préférences et en éditant quelques valeurs parmi elles, cela donnel’exécution suivante.

48

2.4 PreferenceFragment

Figure 2.12 – Affichage des valeurs des paramètres

49

2 Fragmenter vos projets

2.4.5 En résumé

— PreferenceFragment n’est pas disponible dans le projet de compatibilitémais une coha-bitation est possible avec les plus vieilles versions d’Android ;

— Les préférences et les en-têtes se définissent dans un fichier XML et sont ajoutés dans uneactivité ou un fragment à la place d’être désérialisé ;

— PrefrenceActivity est destiné à la compatibilité avec les vieilles versionsmais elle peutêtre utilisée avec les plus récentes ;

— Toutes les préférences possèdent un identifiant unique pour pouvoir en récupérer sa valeurà partir de la mémoire partagée par défaut ;

— Il est possible de rajouter des valeurs dans un bundle pour les préférences et dans les argu-ments d’un fragment pour les en-têtes.

2.5 DialogFragment

Les boites de dialogue sont présentes depuis les toutes premières versions d’Android. Avec laversion 3.0, les DialogFragment amènent les fragments dans ce composant. Ils permettent d’af-ficher une boite de dialogue à l’utilisateur de manière beaucoup plus souple et plus complète quel’API utilisée dans les versions antérieures.

2.5.1 Créer un DialogFragment

Les API AlertDialog et Dialog sont des concepts régulièrement utilisés depuis toujours dansle développement d’applications Android. L’idée derrière cette mise à niveau est de ré-utiliserce type de composant mais en lui attachant des fragments. C’est une pratique qui permettra deconcevoir des boites de dialogue infiniment plus puissantes et réutilisables que les anciennes.

DialogFragment servira de conteneur pour un fragment simple. Il le transformera en boite dedialogue et fournira le style et la structure de la boite. Cette classe fournit tous les contrôles né-cessaires pour créer la boite et gérer son apparence.

Pour spécifier qu’un fragment est destiné à être affiché dans une boite de dialogue, le futur frag-ment doit étendre la classeDialogFragment et redéfinir certainesméthodes. Cesméthodes sontutiles pour sa gestion et pour son intégration dans la boite. Sans surprise, deux méthodes sontdéfinies et redéfinies :

— public View onCreateView(LayoutInflater inflater, ViewGroup contai-ner, Bundle savedInstanceState) : Pour désérialiser le fichier XML représentant lefragment voulu et pour donner un titre à la boite de dialogue (ce dernier étant optionnel).

— public static MyDialogFragment newInstance() : Pour encapsuler la création dufragment et en connaitre les données potentielles.

La classe suivante donne un exemple d’implémentation :

package com.siteduzero.android.dialog;

import android.os.Bundle;

50

2.5 DialogFragment

import android.support.v4.app.DialogFragment;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.Button;

import com.siteduzero.android.R;

public class MyDialogFragment extends DialogFragment {public static MyDialogFragment newInstance(int title) {

MyDialogFragment dialog = new MyDialogFragment();Bundle args = new Bundle();args.putInt("title", title);dialog.setArguments(args);return dialog;

}

@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {View v = inflater.inflate(R.layout.fragment_dialog, container, false);

Button button = (Button) v.findViewById(R.id.buttonShow);button.setOnClickListener(new OnClickListener() {

public void onClick(View v) {((DialogActivity) getActivity())

.showDialogType(DialogActivity.TYPE_ALERT_DIALOG);}

});

getDialog().setTitle(getArguments().getInt("title"));

return v;}

}

Pour afficher la boite de dialogue, il suffit d’appeler la méthode public int show (Fragment-Transaction transaction, String tag) sur une instance d’un DialogFragment commeexpliqué dans la documentation officielle.

2.5.2 Créer un AlertDialog

Le fonctionnement d’une AlertDialog reste inchangé dans les grandes lignes et les nouveau-tés ressemblent au fonctionnement des DialogFragment. Il est toujours nécessaire d’étendre laclasse DialogFragmentmais il faut redéfinir une nouvelle méthode, public Dialog onCrea-teDialog(Bundle savedInstanceState). C’est dans cette méthode qui va falloir utiliser lebuilder, AlertDialog.Builder.

51

2 Fragmenter vos projets

Figure 2.13 – Résultat de l’exécution des boîtes de dialogue DialogFragment

52

2.5 DialogFragment

Vous êtes censé savoir comment créer un AlertDialog. Je ne rentrerai donc pas dans les détailsen ce qui concerne sa création mais beaucoup plus en ce qui concerne son intégration dans unfragment. En fait, c’est vraiment très semblable au DialogFragment et vous devez avoir unepetite idée de la manière de s’y prendre avec les connaissances que je viens de vous apporter etainsi que celles portant sur sa création dans une Activity.

Bien entendu, ce n’est pas exactement la même chose. Nous n’allons pas devoir redéfinir laméthode public View onCreateView(LayoutInflater inflater, ViewGroup contai-ner, Bundle savedInstanceState) mais public Dialog onCreateDialog(BundlesavedInstanceState). A partir de là, il nous suffit de retourner une AlertDialog avec l’aided’un builder.

La redéfinition de la méthode donnera quelque chose comme :

@Overridepublic Dialog onCreateDialog(Bundle savedInstanceState) {

int title = getArguments().getInt("title");

return new AlertDialog.Builder(getActivity()).setIcon(android.R.drawable.ic_dialog_alert).setTitle(title).setNegativeButton(R.string.alert_dialog_cancel,

new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog,

int whichButton) {((DialogActivity) getActivity())

.doNegativeClick();}

}).setPositiveButton(R.string.alert_dialog_ok,

new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog,

int whichButton) {((DialogActivity) getActivity())

.doPositiveClick();}

}).create();}

[[attention]] | N’oubliez pas d’implémenter la méthode static pour créer une instance commenous l’avons fait avec DialogFragment.

Ainsi, le résultat est le suivant :

2.5.3 Afficher une boite de dialogue

Dernière petite chose avant de terminer ce chapitre. Nous allons rapidement voir comment inté-grer nos boites de dialogue dans une activité. Vous comprendrez que ce n’est vraiment pas com-pliqué puisqu’il s’agit d’un fragment comme un autre. C’est la raison pour laquelle je vais vouslaisser réfléchir à la question. Si vous avez bien compris la matière que je vous ai enseigné dansles chapitres précédents, cela ne devrait pas vous prendre trop longtemps.

53

2 Fragmenter vos projets

Figure 2.14 – Résultat de l’exécution des boîtes de dialogue AlertDialog

54

2.5 DialogFragment

Sachez simplement que vous devez implémenter un certain nombre de méthode :

— public void doNegativeClick() : Appelée lorsqu’on clique sur le bouton négatif del’AlertDialog.

— public void doPositiveClick() : Appelée lorsqu’on clique sur le bouton positif del’AlertDialog.

— public void showAlertDialog(View v) : Appelée au clique de l’utilisateur sur le bou-ton de notre activité destiné à l’affichage de l’AlertDialog.

— public void showDialogFragment(View v) : Appelée au clique de l’utilisateur sur lebouton de notre activité destinée à l’affichage de DialogFragment.

— protected void showDialogType(int type) : Pour basculer dynamiquement entreles différentes boîtes de dialogue.

Je vous laisse à vos lignes de codes. Vous trouverez ma solution et son résultat à la suite de cechapitre.

2.5.3.1 Correction

package com.siteduzero.android.dialog;

import android.os.Bundle;import android.support.v4.app.DialogFragment;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentActivity;import android.support.v4.app.FragmentTransaction;import android.view.View;

import com.siteduzero.android.R;import com.siteduzero.android.dialog.alert.MyAlertDialog;

public class DialogActivity extends FragmentActivity {public static final int TYPE_DIALOG_FRAGMENT = 1;public static final int TYPE_ALERT_DIALOG = 2;

@Overrideprotected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);setContentView(R.layout.activity_dialog);

}

protected void showDialogType(int type) {FragmentTransaction ft = getSupportFragmentManager().beginTransaction();Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");if (prev!= null) {

ft.remove(prev);}ft.addToBackStack(null);

DialogFragment newFragment = null;

55

2 Fragmenter vos projets

switch (type) {case TYPE_DIALOG_FRAGMENT:

newFragment = MyDialogFragment.newInstance(R.string.title_fragment_dialog);

break;case TYPE_ALERT_DIALOG:

newFragment = MyAlertDialog.newInstance(R.string.title_fragment_dialog_alert);

break;}newFragment.show(ft, "dialog");

}

public void showDialogFragment(View v) {showDialogType(TYPE_DIALOG_FRAGMENT);

}

public void showAlertDialog(View v) {showDialogType(TYPE_ALERT_DIALOG);

}

public void doPositiveClick() {// TODO Do something

}

public void doNegativeClick() {// TODO Do something

}}

2.5.4 En résumé

— DialogFragment est une sous classe de Fragment qui permet de contenir des fragmentsdans des boites de dialogue.

— Il existe 2 types de boites de dialogue : DialogFragment qui est générique que vous contrô-lez de A à Z ; AlertDialog qui est moins générique car rajoutant des boutons en bas de laboite.

— Ces boites de dialogue se lancent à peu de chose près comme un fragment normal.

56

2.5 DialogFragment

Figure 2.15 – Résultat de l’exécution de l’activité pour afficher nos boîtes de dialogue

57

3 Conclusion

[[attention]] | Bien entendu, ce tutoriel est très loin d’être terminé. N’hésitez pas à me donnervotre avis pour le faire évoluer. Il a pour but d’être le plus communautaire possible. Je suis doncouvert à toutes vos critiques, questions et remarques constructives !

3.1 Remerciements

— Aux lecteurs et bêta-testeurs quime font des critiques constructives sur le contenu demontutoriel.

— Fumble pour la validation de mon tutoriel.— Bluekicks pour l’icône de mon tutoriel.

59