TP : dé couvérté d'android avéc uné todo list Android Studio : Au premier lancement : on ne configure rien ( 1) pas d import 2) puis on ferme avec la croix en haut à gauche la fenêtre suivante). Il faut configurer dans Configure > Project Default > Project Structure et là on précise le chemin du sdk ( /u/net/tmp/android ) Chemin vers l exécutable Android Studio : /usr/local/android-studio/bin/studio.sh Chemin vers le sdk : /u/net/tmp/android (à préciser au lancement d android studio) Créez un dossier dans /tmp/<votre_nom_ou_login> : Vous préciserez ce dossier pour le chemin vers votre projet. Il faut que votre projet soit sur le disque dur local et non pas sur le réseau. Recopier ce dossier (votre projet) à la fin de la séance (tar puis gzip pour la compression) dans votre dossier personnel. /tmp peut être effacé entre deux séances Pensez à être en java 7 (ajout de PATH=/usr/local/java/jdk1.7.0_03/bin:$PATH dans.zshrc par exemple) 00. Introduction Les objectifs pédagogiques de ce tp sont de faire une 1ère application android, de manipuler de l'interface graphique et de manipuler les fragments, notamment lors de la rotation. La sujet porte sur la réalisation d'une "todo list" qu'il sera possible de trier par date. La persistance de la liste ne sera pas aborder. Les différentes étapes, avec l'évolution (visuelle) de l'application à réaliser sont illustrées dans le tableau à la page suivante. Voici les étapes que vous devriez atteindre au bout des différentes séances : Au bout d 1h30, l étape 3 devrait être commencée Au bout de 3h, l étape 5 devrait être commencée Au bout de 4h30, l étape 6 devrait être finie Au bout de 6h, l étape 8 devrait être finie
1) Faire une application avec un textedit et un bouton 2) Pouvoir ajouter le même fragment simple plusieurs fois : faire un fragment (un fragment = une tâche) et pouvoir le recharger (et l'adapter) à chaque clic sur le bouton "ajouter une tâche" 3) Pouvoir retirer un fragment (avec une autre transaction) 4) Pouvoir déplacer un fragment, bouton monter / descendre pour gérer l'ordre des View (venant des fragments) 5) Gérer la rotation du dispositif (sauvegarde de l'état dans l'activité) 6) Ajouter la date butoir avec une boite de dialog (un autre fragment) 7) Faire un menu pour trier par ordre de création par date butoir, dans un sens ou dans l'autre, avec les Comparable de Java 8) Gérer la rotation pour le tri
01. Création de l'application Il s'agit essentiellement de prendre en main l'environnement de développement. Créer votre projet/application dans votre éditeur, choisissez le nom de l'application (par exemple "todo list"). Précisez également votre nom de package (pour vous différencier entre vous... surtout si vous utilisez la même tablette). Pour les versions d'android, choisissez la 19 (4.4.2), celle de la tablette. Pour les thèmes, choisissez "aucun" (ou none). Ensuite, ne créez pas d'icône. Puis choisissez "blank activité". Finalement donnez un nom à votre activité (par exemple ToDoList) et à votre layout (par exemple list_ui.xml). Une fois le projet créé, allez éditer (graphique et/ou directement dans le xml) le "layout" activity_to_do_list. Dans un relative layout, placer en haut et aligner un EditText [caché dans l éditeur graphique sous le nom «Text Fields / Plain Text»] (avec un hint, le texte explicatif qui disparaît lorsqu'on tape) et un Button en dessous des deux. En dessous, placez un linear layout vertical (appelé liste par la suite) qui servira plus tard pour accueillir les tâches. Placez les constantes (les chaines de caractères) dans le fichier strings.xml.
02. Ajout d'une tâche Il faut pouvoir ajouter des tâches à notre "todo list". Il faut donc pouvoir ajouter dynamiquement des éléments à l'interface graphique. Pour cela, vous allez utiliser un fragment, et lors de l'instanciation du fragment lui passer un paramètre (le texte de la tâche). Il faudra aussi ajouter un écouteur sur le bouton "ajouter à la liste". Commencez par créer le fragment. Pour cela, il faut créer son layout, nommé item.xml. Pour créer ce layout (dont la racine est un RelativeLayout), faites un clic droit sur le dossier layout dans le dossier res : Pour l'instant il est simple : un TextView dans un RelativeLayout. Spécifiez le background du layout pour qu'il soit encadré. Il faut donc définir un drawable, c'est à dire un fichier xml contenant une "Shape" à placer dans le dossier drawable si vous ciblez la tablette. Cette "<shape>" sera de forme rectangulaire, avec des <corners> arrondi (et précisez le trait avec la balise <stroke>). Pensez aussi à mettre un padding au RelativeLayout. Ensuite, vous devez faire une classe (dans le dossier src, dans le même package que l'application) qui encapsule cette partie graphique, une classe appelée Item qui hérite de android.app.fragment.
Toujours dans Item.java, il faut surchager la "public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle saved) ". Cette méthode sera appelée automatiquement en fin de création du fragment. Il faudra y charger le xml pour créer un objet de type View, c'est ce que fait la méthode "inflater.inflate(r.layout.item, container, false); " 1. Il faut ensuite récupérer le paramètre (le texte de la tâche) depuis le Bundle passé en argument au fragment lors de la création (méthode setarguments sur le fragment). Ce Bundle s'obtient avec la méthode getarguments(). Il faudra affecter ce texte (méthode getstring du bundle avec une clef, par exemple "tache") au TextView de la partie graphique que vous venez de créer. Encore le même problème, il faut pour obtenir une référence sur le TextView en question. Il serait possible d'utiliser un id (défini dans le xml item.xml), mais comme il sera possible d'ajouter plusieurs fois le fragment, l'id risque de ne pas être unique. A la place, vous utiliserez un tag. C'est une chaine de caractère, que l'on spécifie dans le xml avec l'attribut android:tag="un_tag". Les View proposent la méthode getviewwithtag(string tag), ce qui permet de retrouver une View (ici un TextView) à partir d'un tag... Pour créer un Item en créant un Bundle pour lui donner les paramètres (ce Bundle sera réutilisé par android s'il faut recréer le fragment...), créez une méthode static "public static Item newinstance(string txt)" dans la classe Item. Dans cette méthode, créez un Item (constructeur par défaut), le bundle, remplissez le bundle avec la String txt (avec la clef "tache"), affectez le bundle (méthode setarguments) et retournez l'item ainsi créé. Dans l'activité, ajoutez un écouteur sur le bouton (setonclicklistener) que vous récupérez (dans la méthode oncreate) par la méthode findviewbyid. Dans cet écouteur, créez une FragmentTransaction (à partir du FragmentManager de l'activité obtenu avec getfragmentmanager() et en utlisisant la méthode begintransaction() ) pour ajouter un nouvel Item. Utilisez la méthode add(int containerviewid, Fragment fragment) où containerviewid est l'id de la liste (le LinearLayout) dans laquelle sera "placé" le fragment. N'oubliez pas d'appeler la méthode commit sur la transaction. 1 Ici, il faut mettre faut false dans le paramètre de inflate, c'est avec la add de la FragmentTransaction que l'ajout à l'interface graphique sera fait.
03. Effacer une tâche C'est une autre application des événements graphiques et de la gestion des transactions Il faut ajouter un bouton "effacer cette tâche" fait partie du fragment (classe Item / item.xml). Dans la classe Item, il faut écouter ce bouton (abonnement dans la méthode oncreateview). Dans l'écouteur, il faut refaire une transaction (FragmentTransaction, obtenue avec getfragmentmanager().begintransaction( ) ) pour retirer ce fragment (méthode remove).
04. Déplacement les tâches Pour que l'utilisateur puisse changer l'ordre des tâches, vous allez ajouter deux boutons : un pour monter la tâche, un autre pour la descendre. Vous ferez la distinction entre un clic et un clic long (qui permettra d'aller tout en haut ou tout en bas). Il faut donc modifier item.xml, en ajoutant des ImageButton. Pour les images, vous pouvez prendre : http://deptinfo.unice.fr/~renevier/poo/tps/tp03-android/up.png http://deptinfo.unice.fr/~renevier/poo/tps/tp03-android/down.png Ces boutons seront positionnés relativement à gauche de (soit du bouton "effacer cette tâche" soit du bouton "up"). Pour les listeners, il y aura des OnClickListener et des OnLongClickListener. L'élément parent (de type ViewGroup) contenant la View du fragment est celui passé en paramètre du oncreateview. Avec la méthode indexofchild de ViewGroup, il est possible de connaitre la position d'une View dans son parent. Avec la méthode addview(view v, int index), vous pourrez placer une View à la position que vous voulez. Attention à ne pas avoir un index trop petit ( < 0) ou trop grand (supérieur ou égale au nombre d'éléments enfants du ViewGroup donné par la méthode getchildcount). Avant de pouvoir rajouter un élément, il faut d'abord le retirer (removeview). Lancer votre programme, essayez le... faites pivoter la tablette (ou le simulateur). L'ordre des tâches est perdu... c'est ce que vous allez régler dans le point suivant.
05. Gérer la rotation du dispositif Pour parvenir à mémoriser l'ordre des View associées aux fragments, vous implentez onsaveinstancestate et onrestoreinstancestate de l'activité et vous donnerez un tag aux View concernées. A chaque rotation de l'écran, l'activité est détruite et reconstruite. Les fragments également. Après la rotation se sont donc de nouveaux objets, recréés avec les mêmes paramètres. La position des View provenant des fragments doit être sauvegardée. Les objets étant changeant, toutes références à ceux-ci est donc périssables (nouveaux objets, nouvelles références). Une solution est de pouvoir reconnaitre les View, avec un tag (cela fonctionne aussi avec les id) qui doit être unique. Ce tag devra être recréé à l'identique dans le fragment. Soit ce qui permet de le créer sera dans les paramètres de création du fragment, soit le fragment devra le mémoriser (dans onsaveinstancestate) et restaurer dans oncreate (du fragement, cela sera dans le Bundle en paramètre). Pour faire un tag "unique", basez-vous sur l'heure (en milliseconde ou en seconde) : Calendar c = Calendar.getInstance(); long time = c.gettimeinmillis(); Le temps en milliseconde (depuis 1er janvier 1970) est trop grand pour être codé sur un int, donc il représenté par un long int. Notons qu'ici, le fait de rendre les fragments "retainable" doit également permettre de mémoriser le tag, mais cette sauvegarde est moins "grande" que les deux autres évoquées. Pour sauvegarder l'ordre des vues, implémentez la méthode onsaveinstancestate dans l'activité : @Override public void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); } /** * ajouter un Bundle "ordre" dans outstate * ordre associe à chaque tag des View des fragments * leur position dans le parent */ Cette implémentation utilise les méthodes : getchildcount et getchildat pour le LinearLayout contenant toutes les View gettag pour les View
putint pour le nouveau Bundle (ordre) putbundle pour outstate Implémentez également onrestaureinstancestate pour modifier l'ordre des View après leur (re- )création. A partir du tag des View, retrouver les positions d'avant la rotation et appliquez-les. Souvenezvous qu'une View doit être retirer avant d'être rajouter (pour être replacée). Autre modification dans le layout de l'activité (list_ui.xml) : placer le LinearLayout dans un ScrollView, ce qui permet d'accéder à tous les éléments si la liste est longue.
06. Ajout de dates aux tâches Vous allez utiliser une autre forme de fragment, pour les boites de dialogues. Vous allez ajouter une seconde date, en plus de la date de création. Il y aura aussi la date buttoir de la tâche. Il y aura donc trois évolutions du programme à faire (dans l'ordre que vous voulez) : Créez un "FragmentDialog" pour qu'il soit possible de saisir la date buttoir Modifiez l'activité et son interface graphique (list_ui.xml) et pour ouvrir une boite de dialogue, et réceptionnez la valeur de retour Modifiez la création des Item et leur interface graphique (item.xml) pour afficher les dates. Voici des informations sur ces évolutions. La boite de dialogue : Vous devez créer une classe : public class ChoisirDateButtoir extends DialogFragment implements DatePickerDialog.OnDateSetListener { } Cette classe sera plutôt simple : elle surcharge la méthode oncreatedialog pour retourner un nouveau DatePickerDialog (c.f. http://developer.android.com/reference/android/app/datepickerdialog.html, considérez le 1er constructeur). ChoisirDateButtoir implémente l'interface OnDateSetListener pour pouvoir récupérer la date saisie (méthode ondateset). Dans cette méthode, il faudra transmettre à l'activité la date choisie. Pour éviter un cast "explicite" dans la classe concrète de l'activité (TodoList), l'activité pourra implémenter une interface logicielle : public interface EcouterChangementDate { } public void setdate(int year, int month, int day); L'instance de la classe ChoisirDateButtoir (qui devra avoir un constructeur sans paramètre, c'est celui utilisé par android pour recréer une instance...) pourra alors rappeler un objet de type EcouterChangementDate. Attention toutefois à la rotation, l'activité étant ré-instanciée, il est possible de rester l'instance de l'activité en cours pour retrouver un EcouterChangementDate! if (monecouteur == null) { Activity app = getactivity(); if (app instanceof EcouterChangementDate) monecouteur = (EcouterChangementDate) app ; }
L'évolution du fragment Item : Vous devez modifiez la classe et le layout. Il faut donc prévoir un TextView de plus, mais il faudra revoir l'organisation de la View que produit le fragement pour avoir assez de place. Vous pouvez mettre le libellé de la tâche sur toute la largeur et tout sera placer en dessous. Puis conserver le placement "à gauche de", et aligner les hauts et les bas. Pour la classe Item, elle aura un champ de classe supplémentaire pour la date butoir et utilisera une SimpleDateFormat pour mettre en forme les dates (avec la méthode format) : SimpleDateFormat formatdate = new SimpleDateFormat("dd/MM/yyyy", Locale.FRANCE); Il faudra veiller à ce que les dates (de création et buttoir) puissent être recréées à l'identique (pour la rotation notamment). Normalement pour la date buttoir, cela passera par un paramètre à la création de l'item, donc cela devrait être fait "naturellement". L'évolution de l'activité : Vous devez faire évoluer l'activité pour permettre de choisir une date buttoir. Il faut tout d'abord ajouter un nouveau bouton et l'écouter (ou écouter les événements touch sur la TextView). De là, il faut lancer une nouvelle boite de dialogue ChoisirDateButtoir. Pensez à lui fournir son "EcouterChangementDate" pour mémoriser cette nouvelle date buttoir et changer le texte du TextView associé. Il faudra donc que l'activité implémente cette interface, et lors de la création d'un nouvel Item, il faudra passer la date buttoir en paramètre.
07. Tri des tâches Vous allez utiliser le menu d'une application android et appliquer un tri (avec les mécanismes de java). L'objectif de cette étape est de trier les tâches par ordre de création (croissante ou décroissante) ou par ordre de date buttoir (croissante ou décroissante). Lors de la création du projet, Eclipse a dû créer un fichier dans le dossier "res/menu" (todolist.xml ou nommé autrement en fonction du nom de l'activité). Enlevez l'item (balise xml pour les menus) par défaut (settings) et créez deux items : "tri par date buttoir" et "tri par date de création". Donnez-leur à chacun un id unique. Dans l'activité, la méthode oncreateoptionsmenu devrait déjà être correcte (pour faire l'inflate du fichier xml). Il faut aussi implémenter "public boolean onoptionsitemselected(menuitem item)" pour savoir sur quel item l'utilisateur a pressé. A partir de l'id (méthode item.getitemid() ) vous pourrez déterminer lequel. Là, il faudra lancer le tri. permettant de la retourner. Pour trier, il faut trier les fragments et à partir de là, placer leur View dans le bon ordre, la méthode getview de Fragment Il faudra donc gérer la liste des fragments. Une activité "de base" ne le peut pas, et plutôt que d'utiliser une autre version proposée dans android dans android.support.v4.app, vous allez devoir maintenir la liste des fragments dans l'application dans une collection (une LinkedList<Item> par exemple) et votre application devra proposer des méthodes add ou remove à cette liste à appeler lors de la création et du retrait d'un Item (notamment pour l'appel à remove lors du retrait d'un fragment suite à un clic sur le bouton "effacer cette tâche"). Ces deux méthodes peuvent définir une interface logicielle qu'implémente votre activité.
Puis, pour le tri, vous ferez appel à la méthode static Collections.sort avec en paramètre votre liste de fragments (les Item) et un Comparable (voir ci-dessous). Anis votre liste sera triée. Puis, il faudra retirer toutes les View pour les rajouter dans l'ordre de la liste de fragment. Un comparable est un objet dont le type instancie l'interface Comparator<T>. Comme il faut 4 Comparator différents (2 tri selon date de création ou date buttoir, selon deux sens, croissant ou décroissant), faites une énumération : public enum ComparerDate implements Comparator<Datee> { CREATION_CROISSANT, CREATION_DECROISSANT, BUTTOIR_CROISSANT, BUTTOIR_DECROISSANT; } @Override public int compare(datee lhs, Datee rhs) { /** A remplir **/ } Datee est une interface logicielle implémentée par la classe Item, elle propose deux méthodes : public interface Datee { public long getcreation(); } public long getbuttoir(); La méthode compare peut s'appuyer sur la méthode compareto de la Classe Long. 08. Tri et Rotation Maintenant, faites en sorte que le tri fonctionne encore après une rotation. Pour cela, il faut que la liste d'item dans l'activité soit reconstruite après la rotation, lorsque les fragments sont recréés.