J'apprends à créer des applications Android avec l'environnement de développement Lazarus

En utilisant le framework LAMW

Alcatîz

Dans ce tutoriel, nous allons découvrir comment débuter dans la programmation d'applications pour Android en Pascal, au sein de l'environnement de développement Lazarus. Nous allons partir de l'installation des outils nécessaires pour arriver à l'exécution sur votre mobile, en passant par toutes les étapes du développement d'une application simple - mais complète.

Si vous êtes rompu(e) au développement d'applications pour PC sous Lazarus, accrochez-vous : les principes du développement pour Android peuvent être radicalement différents !

Commentez

- Titre : J'apprends à créer des applications Android avec l'environnement de développement Lazarus

- Auteur : Alcatîz

- Parution : 23 août 2025

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2025 Alcatiz. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Installation sous Linux

- III. Installation sous Windows

- IV. Erreur « Unité AndroidWidget non trouvée »

- V. Réalisation d'une application

- V-A. Création du projet

- V-B. Arborescence des fichiers d'un projet Android

- V-C. L'environnement de développement

- V-D. Partie « interface » du projet

- V-D-1. Menu

- V-D-2. Panneau principal de l'application

- V-D-3. Clavier virtuel

- V-D-4. Dialogue de nouvelle partie

- V-E. Partie « métier » du projet

- V-E-1. Création des différents champs

- V-E-2. Le dictionnaire de mots

- V-E-3. Initialisation de l'application

- V-E-4. Dialogue de nouvelle partie

- V-E-5. Initialisation d'une nouvelle partie

- V-E-6. Gestion du menu

- V-E-7. Gestion du clavier virtuel

- V-E-8. Affichage d'émojis

- V-E-9. Enregistrement des préférences

- V-F. Icône de l'application

- V-F-1. Création d'icônes avec LAMW

- V-F-2. Création d'icônes à la main

- V-F-3. Création d'icônes avec Android Studio

- V-G. Réglage des permissions

- V-H. Création de l'exécutable

- V-I. Exécution de l'application

- V-I-1. Exécution sur un mobile connecté au PC

- V-I-2. Exécution sur un émulateur

- V-I-3. Installation manuelle

- VI. Conclusion

- VI-A. Téléchargements

I - Introduction

Lazarus s'est imposé dans le monde du Pascal comme un véritable couteau suisse. Il permet de créer des applications pour une foultitude de plates-formes et d'architectures, dont Android sur architectures x86_64 et ARM. Dans ce tutoriel, nous allons apprendre comment installer et configurer Lazarus pour pouvoir créer des applications pour ce système d'exploitation en particulier.

La conception d'applications Android se fera au moyen du framework LAMW (pour « Lazarus Android Module Wizard ») créé par Jose Marques Pessoa (https://github.com/jmpessoa/).

La création d'applications pour Android nécessite plusieurs outils, en plus de Lazarus :

- une plate-forme Java, constituée de bibliothèques et d'un environnement d'exécution ;

- un moteur de construction d'application, auquel Lazarus sous-traite la création de l'exécutable destiné à Android ;

- le framework LAMW lui-même ;

- le SDK Android (pour « Software Development Kit »), c'est-à-dire l'ensemble des fichiers système, bibliothèques et composants nécessaires à la création d'applications pour Android.

II - Installation sous Linux

Un script d'installation, réalisé par Daniel Oliveira Souza (https://github.com/dosza/), permet d'installer absolument tout ce qui est nécessaire :

- les dépendances propres à Linux ;

- le compilateur Free Pascal et l'environnement de développement Lazarus ;

- la plate-forme Java (en l'occurrence, OpenJDK qui est une implémentation libre de la plate-forme Java Platform Standard Edition (souvent nommée « Java SE » ou « J2SE ») ;

- le moteur de construction d'application Gradle, qui fonctionne sur la plate-forme Java ;

- le framework LAMW ;

- le SDK Android.

Téléchargez dans le dossier de votre choix le script d'installation à cette adresse : https://github.com/dosza/LAMWManager-linux/releases/download/v0.6.9/lamw_manager_setup.sh.

Ensuite, ouvrez une console, rendez-vous dans le dossier où se trouve le script, rendez-le exécutable puis lancez-le en tant que simple utilisateur :

cd ~/Téléchargements
chmod +x lamw_manager_setup.sh
./lamw_manager_setup.sh

Lorsque des droits d'administrateur seront requis, le système vous demandera votre mot de passe root.

Le script d'installation est a priori prévu pour fonctionner sur une distribution Debian. Sil s'arrête prématurément en vous prévenant de l'absence de certains paquetages, il faudra procéder manuellement à l'installation de ceux-ci, à l'aide du gestionnaire de paquetages de votre distribution : voyez à ce sujet le paragraphe suivant.

L'installation de LAMW a été testée sur plusieurs familles de distributions. Voici une liste empirique des paquetages à installer manuellement :

Distributions de la famille Debian (Ubuntu, Mint, PureOS, Pop!OS, Elementary, etc.) :

sudo apt-get install gdb git jq xmlstarlet xterm libx11-dev libgtk2.0-dev libxtst-dev freeglut3-dev

Distributions de la famille Red Hat (RHEL, Fedora, CentOS, etc.) :

sudo dnf install gcc git xmlstarlet libX11-devel gtk2-devel.x86_64 libXtst-devel freeglut-devel

Distributions de la famille Mandrake (Mandriva, Mageia, PCLinuxOS, etc.) :

sudo urpmi binutils gdb gcc make git jq xmlstarlet xterm libx11-devel libgtk+2.0-devel libxtst-devel freeglut-devel

Distributions de la famille Arch (Arch Linux, Manjaro, Garuda, etc.) :

sudo pacman -S gdb make jq xmlstarlet unzip zenity bc gtk2

Distributions de la famille SUSE (OpenSUSE, SLES) :

sudo zypper install binutils gdb gcc make git jq xmlstarlet bc libX11-devel gtk2-devel freeglut-devel

Si vous rencontrez toujours des problèmes de paquetages manquants lors de l'installation, aidez-vous du nom du paquet mentionné dans le message d'erreur (« Install on your system the package equivalent to : … »).

Dans la plupart des cas, à la fin de l'installation, l'environnement de développement Lazarus démarrera automatiquement.

Et voilà, en une seule opération, tout est installé et une nouvelle icône « LAMW4Linux IDE », avec la tête du tigre de Lazarus colorée dans les tons orangés, est ajoutée à votre menu de démarrage :

Si une autre version de Lazarus est installée sur votre système, utilisez exclusivement cette nouvelle icône pour lancer la version de Lazarus permettant de créer des applications Android.

III - Installation sous Windows

Un programme d'installation, créé par Daniel Oliveira Souza (https://github.com/dosza/), facilite grandement l'installation de tous les outils nécessaires.

Téléchargez le programme d'installation à cette adresse : https://github.com/dosza/lamWManager-win/releases/latest/download/lamw_manager_setup.exe.

Exécutez-le puis, à la fin de l'installation, lancez l'application LAMW Manager. (cochez la case ad hoc avant de fermer le dialogue d'installation) :

Une console s'ouvre :

L'application procédera à l'installation de tout ce qui est nécessaire :

- divers composants du système ;

- Perl, MinGW (« Minimalist GNU for Windows », un environnement permettant d'exécuter une panoplie d'outils issus du monde Linux) ;

- la plate-forme Java OpenJDK ;

- le moteur de construction d'application Gradle ;

- le SDK Android ;

- un émulateur de système Android ;

- le compilateur Free Pascal et l'environnement de développement Lazarus ;

- le framework LAMW.

Ne soyez pas surpris(e) si tout semble figé pendant plusieurs minutes durant l'installation, c'est normal, car certains composants sont très volumineux.

À la fin de l'installation, une nouvelle icône « LAMW4Windows » - dans les tons orangés, pour bien la distinguer de l'icône classique de Lazarus, dans les tons bleutés - apparaît dans votre menu de démarrage :

Si une autre version de Lazarus est installée sur votre système, utilisez exclusivement cette nouvelle icône pour lancer la version de Lazarus permettant de créer des applications Android.

IV - Erreur « Unité AndroidWidget non trouvée »

Il est possible qu'à la création de votre premier programme, lors du placement d'un composant d'une des palettes dédiées à Android, vous soyez confronté(e) à une erreur d'unité AndroidWidget non trouvée. Il s'agit d'un petit bogue lié à l'installation automatique de LAMW. Pour y remédier, rendez-vous dans le dialogue d'installation de paquets via le menu « Paquet / Installer/désinstaller des paquets » :

Sélectionnez les quatre paquets suivants dans la colonne de gauche (Ctrl + clic) :

- amw_ide_tools ;

- fcl_bridges_pack (s'il existe) ;

- LazAndroidWizardPack ;

- tfandroidbridge_pack.

Cliquez sur le bouton « Désinstaller la sélection » puis, au bas du dialogue, « Enregistrer et recréer l'EDI » : Lazarus va se reconstruire puis se relancer. Retournez alors dans le même dialogue, sélectionnez les quatre mêmes paquets dans la colonne de droite puis sur le bouton « Installer la sélection », puis de nouveau sur « Enregistrer et recréer l'EDI ». Après la reconstruction de Lazarus, l'erreur devrait avoir disparu.

V - Réalisation d'une application

Afin de vous familiariser avec l'environnement de développement et avec les principes de base du développement pour Android, nous allons réaliser pas à pas une application. Il ne s'agira pas d'un simple « Hello World! », mais carrément d'une application complète : le jeu du Pendu. Tout le monde connaît le principe de ce jeu : il s'agit de deviner un mot en un nombre maximum d'essais. Accrochez-vous et suivez scrupuleusement les explications qui vont suivre. Lancez bien Lazarus à partir de l'icône orange ; si vous voyez une icône bleue, c'est la version classique de Lazarus.

V-A - Création du projet

Si vous avez déjà travaillé avec Lazarus, vous savez déjà que pour créer un nouveau projet, il faut aller dans le menu « Projet / Nouveau projet » :

Choisissez le type de projet « LAMW [GUI] Android App ». Le dialogue qui s'ouvre alors nécessite pas mal d'explications :

Allons de haut en bas. Tout d'abord, vous trouvez le dossier par défaut d'enregistrement des projets LAMW, qui a été créé au moment de l'installation. Chaque projet fera l'objet d'un sous-dossier distinct. Si ce dossier par défaut ne vous convient pas, sélectionnez le dossier de votre choix en cliquant sur les trois points à droite.

Ensuite se trouve le nom du projet : indiquez « Pendu ».

Le champ suivant est une sorte d'espace de nommage, notion peu utilisée en Pascal. Vous pouvez sans problème laisser la valeur par défaut, ou bien la remplacer par autre chose.

À droite de ce champ, vous voyez que Gradle est automatiquement sélectionné comme constructeur de l'application (« Apk Builder », apk étant l'extension des applications Android, tout comme exe est celle des applications Windows).

Les trois champs numériques qui suivent correspondent aux versions visées par votre application ; si celle-ci ne nécessite pas de fonctionnalités propres aux versions les plus récentes d'Android, vous pouvez laisser ces trois valeurs inchangées.

Suit ensuite l'architecture-cible. Le choix par défaut « ARMv7a+VFP3 » correspond à l'architecture la plus courante, c'est-à-dire un processeur de la famille ARM ; si vous devez créer une application pour une autre architecture (par exemple, pour Android x86, qui repose sur un processeur Intel), vous choisirez l'option qui convient.

Vient ensuite le thème de votre application : tout d'abord, vous pouvez choisir un thème de couleurs (qui peut être modifié manuellement par la suite si nécessaire) mais, surtout, vous devez choisir un thème d'application : pour un look and feel plus moderne, sélectionnez l'un des deux thèmes « AppCompat ».

Pour notre jeu du Pendu, étant donné que nous allons utiliser un menu, nous allons remplacer le thème par défaut par « AppCompatDarkActionBar » (le menu s'affichant dans la barre d'action supérieure).

Dès que vous cliquez sur le bouton « OK », Lazarus enregistre directement le projet. Renommez tout de suite « unit1.pas » en « ufrmPrincipale.pas ».

Ça y est, un projet vierge est créé :

Si vous êtes familier(e) de Lazarus, vous constatez que la fiche de travail n'est pas une fiche classique pourvue d'un quadrillage : la fiche de travail d'une application Android est basée sur un datamodule et non une forme de type TForm. Première surprise.

V-B - Arborescence des fichiers d'un projet Android

L'arborescence des fichiers d'un projet Android est très touffue. Si vous êtes habitué(e) à travailler avec Lazarus, oubliez tout ce que vous connaissez sur ce sujet ! Nous partons du principe que vous avez conservé l'emplacement par défaut des projets configuré lors de l'installation de LAMW ; votre projet se trouvera donc dans le dossier « /home/utilisateur/Dev/LAMWProjects/Pendu » sous Linux ou « C:\Users\utilisateur\Dev\LAMWProjects\Pendu » sous >Windows. Voici les dossiers les plus intéressants :

Le dossier « jni » contient le code source et les fichiers du projet gérés par Lazarus, dont voici les principaux : « ufrmprincipale.pas » est le code source de la fenêtre principale, « ufrmprincipale.lfm » contient les propriétés des composants utilisés et « controls.lpr » est le code source du programme principal.

Le dossier « assets » contient des fichiers utilisés par l'application, qui seront installés sur le mobile en même temps que celle-ci.

Le dossier « res » et ses sous-dossiers contiennent les ressources de l'application, notamment des images et l'icône de l'application (proposée en différentes résolutions).

Le dossier « build/outputs/apk », non présent à la création du projet, contiendra les exécutables finaux (une version « debug » et une version « release »).

Nous aurons l'occasion de reparler de ces emplacements plus loin dans ce tutoriel. Tous les autres dossiers sont utilisés par les outils de génération de l'exécutable final, et ont peu d'intérêt pour nous.

V-C - L'environnement de développement

Faisons un petit tour de l'environnement de développement. Même si vous connaissez déjà Lazarus, prenez le temps de lire ce qui suit, car vous risquez d'être quelque peu dépaysé(e).

La fenêtre principale de Lazarus est le bandeau horizontal supérieur, avec un menu complet, une barre d'outils qui va très peu - voire pas du tout - nous servir, et une série d'onglets. Ces onglets sont les palettes de composants, c'est très important. La création de l'interface homme-machine se fait en déposant sur la fiche de travail des composants contenus dans les palettes.

Nous travaillerons exclusivement avec trois palettes :

- « Android Bridges », qui contient les composants visuels de base de l'interface de l'application ;

- « Android Bridges Extra », qui contient des composants plus spécialisés permettant d'utiliser les fonctionnalités de votre mobile ;

- « Android Bridges AppCompat », avec des composants plus modernes qui ne peuvent être utilisés que si vous choisissez un des deux thèmes « AppCompat » pour votre application.

Si vous avez déjà créé des applications classiques avec Lazarus, oubliez les composants visuels que vous utilisez habituellement : ils sont incompatibles avec Android. Les trois palettes citées ci-dessus offrent un large éventail de composants, dont le nom commence par « j » : au lieu d'un TMainMenu, vous utiliserez un jMenu, au lieu d'un TButton un jButton, etc.

La partie centrale de l'environnement de développement est l'éditeur de code source. Ce code sera conjointement écrit par Lazarus, au fur et à mesure que vous ajouterez et configurerez les composants visuels (la « partie interface » de l'application), et par vous-même lorsqu'il s'agira de développer la « partie métier » de l'application, c'est-à-dire tous les traitements.

À gauche de l'éditeur de code source se trouve l'inspecteur d'objets. Si celui-ci ne s'affiche pas par défaut, vous pouvez presser la touche « F11 » pour le faire apparaître. Lorsqu'un composant est sélectionné, l'inspecteur d'objets énumère toutes ses propriétés (nous aurons l'occasion d'y revenir) ainsi que, dans un second onglet, tous les événements gérés par le composant.

En dessous de l'éditeur de code source, vous trouvez une fenêtre destinée à l'affichage des messages du compilateur.

Pour terminer, une fenêtre vide appelée « AndroidModule1 » s'affiche quelque part sur votre écran : c'est votre fiche de travail. Si elle est invisible, faites-la apparaître en pressant la touche « F12 ». Cette fiche représente l'écran de votre mobile ! Vous pouvez la déplacer où vous voulez et la redimensionner comme bon vous semble, cela a peu d'importance et en voici la raison :

Sous Android, le placement des composants de l'interface visuelle est relatif. La position d'un composant n'est pas définie par des coordonnées, mais bien relativement au composant qui le contient, ou à un autre composant présent à l'écran.

L'interface de l'application doit se concevoir de manière logique, en commençant par un composant dont la position est relative au contenant (au composant « parent »), puis en ajoutant de proche en proche les autres composants dont la position est relative à un composant déjà présent.

Vous allez très vite comprendre tout cela lors de la conception de la partie interface de notre jeu.

V-D - Partie « interface » du projet

Commençons par montrer à quoi va ressembler le jeu :

C'est parti. Dans l'inspecteur d'objets, commencez par donner un identificateur à la fenêtre principale, qui pour l'instant s'appelle AndroidModule1. Dans l'onglet « Propriétés », cherchez la propriété « Name » et remplacez sa valeur par frmPrincipale.

V-D-1 - Menu

Ouvrez la palette « Android Bridges », cliquez sur la toute première icône pour sélectionner un composant jMenu. Cliquez ensuite n'importe où sur la fiche de travail, pour y déposer le composant, puis déplacez-le vers le bas de la fiche :

Dans l'inspecteur d'objets, donnez mnuPrincipal comme valeur à la propriété « Name ». Sélectionnez la propriété « Options », puis cliquez sur les trois points. Dans le petit dialogue qui apparaît, vous inscrivez l'un en dessous de l'autre les éléments de votre menu. Ici, il n'y en a qu'un seul, « Nouvelle partie » :

Nous ne définissons aucune position pour le menu, puisqu'il apparaîtra sous la forme de trois points verticaux dans la barre supérieure de l'application.

V-D-2 - Panneau principal de l'application

Toujours dans la palette « Android Bridges », cliquez sur le composant jPanel, représenté par un carré gris, puis sur la fiche de travail. Il est inutile de déplacer ce panneau sur la fiche à la souris, car sa position va être réglée dans ses propriétés.

Commencez par nommer le panneau pnlPrincipal dans sa propriété « Name ».

Nous voulons que ce panneau soit positionné tout en haut et fasse toute la largeur de l'écran. Sa hauteur dépendra des composants que nous y déposerons par la suite. Le « parent » du panneau est la fenêtre principale de l'application elle-même ; dans l'inspecteur d'objets, ouvrez la propriété « PosRelativeToParent » en cliquant sur le petit triangle dans la colonne de gauche, puis cochez les trois options suivantes :

- rpTop, pour indiquer que le panneau se trouve tout en haut de la fenêtre ;

- rpLeft et rpRight, pour indiquer qu'il touche également les bords gauche et droit de l'écran.

Vous voyez que Lazarus déplace le panneau dans la fiche principale au fur et à mesure du clic sur les options. Voilà pour le positionnement du composant. Pour sa taille, nous devons définir deux autres propriétés :

- « LayoutParamHeight » (la hauteur du composant) doit valoir lpWrapContent (ce qui signifie que la hauteur sera dimensionnée en fonction du contenu du panneau) ;

- « LayoutParamWidth » (la largeur) doit valoir lpMatchParent (ce qui signifie qu'elle doit correspondre exactement à la largeur du parent, c'est-à-dire la fenêtre principale).

Nous allons déposer successivement cinq composants sur notre panneau.

Premièrement, une zone de texte jTextView, troisième composant de la palette « Android Bridges ». Surtout, déposez-le à l'intérieur du panneau. Vous devriez d'ailleurs voir que dans l'inspecteur d'objets, il vient se positionner sous le panneau principal, en retrait ; si ce n'est pas le cas, c'est que vous avez cliqué sur la fiche de travail en dehors du panneau : supprimez-le (clic droit dans l'inspecteur d'objets) et recommencez. Dans sa propriété « Name », renommez le nouveau composant en lblMotADeviner.

Le jTextView est équivalent au TLabel standard, ce dernier étant incompatible avec Android.

Nous voulons que cette zone de texte figure au centre et en haut du panneau. Le panneau, précisément, est le « parent » du composant ; nous allons donc travailler sur la propriété « PosRelativeToParent » et y sélectionner :

- rpTop, pour qu'il soit tout au-dessus ;

- rpCenterHorizontal, pour qu'il soit centré horizontalement.

Voyez-vous comment Lazarus a automatiquement adapté la hauteur du panneau ?

Notre zone de texte doit contenir un… texte, que nous allons inscrire dans la propriété « Text » (vous aviez certainement déjà trouvé) : 'Mot à deviner :'.

Nous allons faire quelques petits réglages cosmétiques, pour que le rendu soit plus joli :

- réglez la propriété « Alignment » sur taCenter, pour que le texte soit bien centré ;

- inscrivez 20 comme valeur de la propriété « FontSize » ;

- donnez comme valeur 20 à la propriété « MarginTop », de manière à décoller un peu le composant du bord supérieur du panneau.

Jusqu'à présent, nous avons positionné nos composants exclusivement relativement à leurs parents respectifs : le panneau relativement à la fenêtre principale et la zone de texte relativement au panneau. Les composants suivants, eux, vont être positionnés non seulement relativement à leur parent, mais aussi relativement à un autre composant.

Notion d'ancre : un composant relativement auquel un autre composant est positionné est considéré comme l'ancre de cet autre composant.

L'élément suivant que nous allons déposer est une autre zone de texte, qui va contenir le mot à deviner (des points d'interrogation au départ, progressivement remplacés par des lettres au fur et à mesure que celles-ci seront devinées par le joueur).

Sélectionnez donc de nouveau un jTextView dans la palette et déposez-le n'importe où sur le panneau (pas à côté !). Nommez-le tvMotADeviner et indiquez comme contenu '?' dans sa propriété « Text ». Parlons de sa position : il sera centré horizontalement et sera affiché juste en dessous de la zone de texte déjà déposée. Cette dernière sera l'ancre du nouveau composant ; nous allons matérialiser cela en sélectionnant le composant lblMotADeviner dans la propriété « Anchor » du nouveau composant.

Développez la propriété « PosRelativeToParent » et cochez l'option rpCenterHorizontal : le composant se centre dans la fenêtre principale. Développez ensuite la propriété « PosRelativeToAnchor » et cochez l'option raBelow : il vient se placer juste en dessous de l'autre composant.

Occupons-nous un peu de l'aspect du composant :

- centrez le texte contenu en réglant la propriété « Alignment » sur taCenter ;

- augmentez la taille du texte en mettant 40 comme valeur à la propriété « FontSize » ;

- mettez le texte en gras en réglant la propriété « TextTypeFace » sur tfBold ;

- si vous voulez, mettez le mot à deviner en couleur en sélectionnant une des couleurs prédéfinies dans la propriété « FontColor ».

Si vous avez bien compris le principe du positionnement des composants, vous n'aurez aucun mal à ajouter successivement sur le panneau les trois jTextView suivants, l'un en dessous de l'autre :

- une zone de texte nommée lblEchecsrestants, dont le texte sera 'Échecs restants :'. Quelle est son ancre ? Oui, c'est le composant tvMotADeviner ;

- une zone de texte nommée tvEchecsRestants, dont le texte sera '4', de taille 50 ;

- une zone de texte nommée lblClavierVirtuel, dont le texte sera 'Tentez une lettre :'. Définissez 20 comme marge inférieure (« MarginBottom ») pour décoller le composant de ceux qui seront affichés en dessous.

Votre fiche de travail devrait ressembler à ceci :

V-D-3 - Clavier virtuel

La partie inférieure de l'écran du jeu sera constituée d'une sorte de clavier virtuel. Nous allons déposer 26 boutons correspondant aux 26 lettres de l'alphabet. Pour obtenir un beau rendu visuel, nous allons répartir les touches du clavier sur quatre lignes :

- 7 lettres de A à G ;

- 6 lettres de H à M ;

- 7 lettres de N à T ;

- 6 lettres de U à Z.

Chaque ligne de touches fera l'objet d'un panneau. Pour les lignes contenant un nombre impair de touches, la solution est évidente : nous commencerons par la quatrième touche, le D, qui sera centré horizontalement dans le panneau, puis nous ajouterons le C, qui sera positionné à gauche du D (qui sera son ancre), puis le B à gauche du C, puis le A à gauche du B ; ensuite, le E sera positionné à droite du D, le F à droite du E et enfin le G à droite du F.

Comprenez-vous bien la logique de cette construction ? D'abord un composant dont nous fixons la position par rapport à son parent (le D fixé par rapport au panneau), puis les autres composants successivement relatifs les uns aux autres (le C relatif au D, le B relatif au C, etc.).

Par contre, pour les lignes comportant un nombre pair de touches, aucune d'entre elles ne sera centrée et il va falloir recourir à une astuce : nous incorporerons un panneau vertical, de très faible largeur, au centre du panneau de la ligne, puis nous ajouterons les touches par rapport à ce minuscule panneau vertical invisible. Ainsi, pour la seconde ligne de touches, le J aura comme ancre le panneau vertical central et sera placé à sa gauche, puis le I à gauche du J, et ainsi de suite.

Capice ?

Allons-y avec la première ligne à sept touches. Déposez un jPanel n'importe où sur la fiche de travail, mais en dehors du panneau principal. Appelez-le pnlAaG. Réglez les propriétés suivantes :

- « Anchor » sur pnlPrincipal ;

- « LayoutParamHeight » sur lpWrapContent ;

- « LayoutParamWidth » sur lpMatchParent ;

- « PosRelativeToAnchor » sur raBelow (il vient se positionner juste en dessous du panneau principal) ;

- « PosRelativeToParent » sur rpCenterHorizontal.

Depuis la palette « Android Bridges », déposez un contrôle de type jButton (représenté par une icône « OK ») sur ce nouveau panneau et appelez-le btnD. Nous allons lui donner la bonne taille et le centrer sur le panneau en réglant les propriétés suivantes :

- « FontSize » : 30 ;

- « MarginBottom », « MarginLeft », « MarginRight » et « MarginTop » : toutes les marges à 0 ;

- « LayoutParamHeight »: lpWrapContent ;

- « LayoutParamWidth » : lpOneEightOfParent, c'est-à-dire que la largeur du bouton sera d'un huitième de celle de l'écran ;

- « PosRelativeToParent » : lpCenterHorizontal et lpCenterVertical ;

- « Text » : simplement 'D'.

Voilà notre première touche virtuelle, centrée sur le premier panneau du clavier. Si vous voulez définir la couleur de fond (« BackgroundColor ») ou de texte (« FontColor ») des touches, faites-le tout de suite sur celle-ci.

Pour poursuivre la création du clavier virtuel, nous allons procéder par copier-coller successifs, mais avant d'aller plus loin il faut associer une action à cette première touche. Dans l'inspecteur d'objets, sélectionnez le bouton btnD puis allez dans l'onglet « Événements ». Cliquez sur l'événement « OnClick », qui correspond à l'appui sur le bouton, puis sur les trois points afin de créer un gestionnaire d'événement.

Lazarus a automatiquement créé dans le code source une méthode appelée btnDClick, dans l'interface et aussi dans l'implémentation :

interface

[ . . . ]

TfrmPrincipale = class(jForm)
btnD: jButton;
mnuPrincipal: jMenu;
pnlAaG: jPanel;
pnlPrincipal: jPanel;
lblMotADeviner: jTextView;
lblEchecsRestants: jTextView;
lblClavierVirtuel: jTextView;
tvEchecsRestants: jtextView;
tvMotADeviner: jTextView;
procedure btnDClick(Sender: TObject);   // <-- gestionnaire d'événement créé
private
{private declarations}
public
{public declarations}
end;

[ . . . ]

implementation

[ . . . ]

procedure TfrmPrincipale.btnDClick(Sender: TObject);
begin

end;

Nous pourrions créer un gestionnaire d'événement pour chaque lettre (btnAClick, btnBClick, etc.), et ainsi multiplier par 26 le même traitement, mais pour plus d'efficience nous allons garder un seul gestionnaire d'événement qui servira à toutes les lettres.

Dans l'extrait de code ci-dessus, renommez btnDClick en btnLettreClick, puis pressez la combinaison de touches « Majuscule+Ctrl+C » pour automatiquement renommer la méthode dans son implémentation, puis retournez dans l'inspecteur d'objets pour réassigner le gestionnaire d'événement sous son nouveau nom à l'événement « OnClick » :

Dorénavant, chaque fois que nous ferons un copier-coller d'une touche, le gestionnaire d'événement sera directement assigné à la nouvelle touche.

Détaillons les premiers copier-coller. Sur la fiche de travail, faites un clic droit sur la touche D et sélectionnez « Copier » dans le menu contextuel. À côté de la touche, et en tout cas sur le même panneau, faites un clic droit et sélectionnez « Coller » dans le menu contextuel. Tout de suite, une nouvelle touche vient se superposer sur la première – ce qui est logique, puisqu'elles ont exactement les mêmes propriétés, hormis que Lazarus a nommé le nouveau bouton btnD1.

Dans l'inspecteur d'objets, sélectionnez le nouveau bouton et définissez les propriétés suivantes :

- « Anchor » : btnD ;

- « Name » : btnC ;

- « PosRelativeToAnchor » : raToLeftOf – mais le bouton C ne se positionnera à la gauche du bouton D que lorsque sa position par rapport au panneau sera modifiée ;

- « PosRelativeToParent » : décochez rpCenterHorizontal pour ne laisser coché que rpCenterVertical – et voilà, le bouton se déplace à la gauche du bouton D ;

- « Text » : 'C'.

Voici notre deuxième touche :

Faites un clic droit sur le bouton C, sélectionnez « Copier », puis un clic droit sur le même panneau et sélectionnez « Coller » : un nouveau bouton vient se superposer au bouton C. Nous allons modifier ses propriétés :

- « Anchor » devient btnC ;

- « Name » devient btnB ;

- « text » devient 'B'.

Refaites l'opération une fois pour créer la touche A. À présent, nous allons créer les touches E, F et G.

Faites un clic droit sur le panneau puis faites « Coller ». Modifiez les propriétés du nouveau bouton comme ceci :

- « Anchor » : btnD ;

- « Name » : btnE ;

- « PosRelativeToAnchor » : toRightOf, ce qui positionne le nouveau bouton à droite du D ;

- « Text » : 'E'.

Copiez la touche E et répétez les opérations de collage pour créer les touches F et G. Vous avez à présent compris le principe ! Voici déjà la première ligne de touches de notre clavier virtuel achevée.

La ligne suivante ne contiendra que six touches. Déposez un composant jPanel sur la fiche de travail, bien sûr pas sur un des panneaux déjà présents. Détaillons encore une fois ses propriétés :

- « Anchor » : pnlAaG, c'est-à-dire le panneau de la première ligne de touches ;

- « LayoutParamHeight » : lpWrapContent ;

- « LayoutParamWidth » : lpMatchParent ;

- « Name » : pnlHaM ;

- « PosRelativeToAnchor » : raBelow ;

- « PosRelativeToParent » : rpLeft et rpRight.

Comme ce panneau contiendra un nombre pair de touches, nous allons recourir à une astuce et créer un fin panneau horizontal au centre. Déposez donc un nouveau jPanel, mais cette fois-ci sur la surface du panneau, pas à côté ; le panneau pnlHaM sera donc bien le parent du nouveau panneau. Voici les propriétés du nouveau panneau :

- « LayoutParamHeight » : lpWrapContent ;

- « LayoutParamWidth » : lpExact, ce qui signifie que sa largeur sera définie par sa propriété « Width », que nous allons fixer dans un instant ;

- « Name » : pnlCentreHaM ;

- « PosRelativeToParent » : rpCenterVertical et rpCenterHorizontal ;

- « Width » : 1, le panneau se réduit à une fine ligne verticale.

Créons la première touche de cette deuxième ligne, qui sera positionnée juste à gauche du fin panneau vertical. Il s'agira de la lettre J, en copiant une des touches de la première ligne, par exemple la lettre C, et en la collant sur le panneau de la deuxième ligne. Réglez les propriétés du nouveau bouton :

- « Anchor » : pnlCentreHaM ;

- « Name » : btnJ ;

- « PosRelativeToAnchor » : raToLeftOf ;

- « Text » :'J' bien sûr !

À ce stade, il se peut que Lazarus n'ait pas réussi à redimensionner correctement le panneau pnlHaM et que la hauteur de ce dernier ne se soit pas réduite automatiquement à celle du bouton J. Si c'est le cas, il est possible de forcer Lazarus à refaire le calcul en changeant sa valeur dans sa propriété « Height ».

Je vous laisse à présent créer successivement les touches I, H, K, L et M selon le même principe qu'à la première ligne.

À présent, nous allons fortement accélérer la vitesse de création des touches du clavier ! La troisième ligne étant composée de sept touches comme la première, nous allons tout simplement faire un copier-coller de la première ligne. Faites un clic droit sur le panneau de la première ligne (pas sur une touche, cliquez bien dans un endroit vide du panneau), sélectionnez « Copie » dans le menu contextuel, puis faites un clic droit sur la fiche de travail, n'importe où sous la seconde ligne de touche, et faites « Coller ». Lazarus crée automatiquement un nouveau panneau comportant sept touches et nous n'avons plus qu'à modifier quelques-unes de leurs propriétés :

- renommez le nouveau panneau (« Name ») pnlNaT ;

- choisissez comme ancre (« Anchor ») pnlHaM, ce qui déplace le panneau sous le précédent ;

- renommez chacun des sept boutons de btnN à btnT et inscrivez la bonne lettre dans sa propriété « Text ».

Vous avez compris : il faut ensuite recopier le panneau de la seconde ligne pour créer la dernière ligne du clavier virtuel.

V-D-4 - Dialogue de nouvelle partie

L'étape suivante est de créer un dialogue qui permet de choisir le niveau de difficulté d'une nouvelle partie, et de lancer la nouvelle partie. Ce dialogue sera appelé lorsque l'utilisateur sélectionnera « Nouvelle partie » dans le menu ; mais tenons-nous-en, pour l'instant, à la création du dialogue.

Juste à droite du jPanel, dans la palette « Android Bridges », se trouve le composant jCustomDialog. Cliquez sur son icône dans la palette, puis sur la fiche de travail, en dessous du clavier virtuel. Commencez par renommer le composant dlgNouvellePartie dans sa propriété « Name ».

Comme son nom (« custom ») l'indique en anglais, ce dialogue doit être personnalisé, en ce sens que par défaut il est complètement vide et que c'est nous qui décidons quels composants il doit contenir.

Dans la palette « Android Bridges Extra » se trouve un autre type de dialogue : jDialogYN. Ce petit dialogue permet d'afficher un message et contient deux boutons « Oui » et « Non ».

La petite difficulté avec le dialogue jCustomDialog est que nous ne disposons pas d'une surface comme la fiche de travail pour visualiser les composants que nous y déposons. Nous travaillons à l'aveugle mais ce n'est pas grave, puisque nous appliquons toujours le même principe : les composants sont positionnés relativement au dialogue (qui sera le parent) et/ou à d'autres composants déjà présents (qui seront les ancres).

Voici à quoi ressemblera le dialogue terminé :

Les composants nécessaires sont donc deux jTextView à gauche, deux jSpinner à droite et un jButton « OK » en dessous. Tous ces composants se trouvent sur la palette « Android Bridges ». Voici l'ordre dans lequel nous allons les placer :

- un jSpinner en haut à droite du dialogue ;

- un jTextView à gauche du jSpinner, les deux étant alignés par le bas ;

- le second jSpinner juste sous le premier, aligné à droite dans le dialogue ;

- le second jTextView à gauche du second jSpinner, aligné par le bas ;

- le jButton en dessous du second jSpinner, centré horizontalement dans le dialogue.

Android s'occupera de placer et dimensionner le dialogue à l'écran.

Allons-y : déposez un jSpinner sur l'icône du dialogue qui se trouve sur la fiche de travail. Vérifiez que dans l'inspecteur d'objets, il apparaisse décalé sous le jCustomDialog, indiquant bien que le dialogue est son parent. Renommez-le spNbMinimumLettres, cochez rpTop et rpRight dans la propriété « PosRelativeToParent » et fixez sa largeur « LayoutParamWidth » sur lpOneThirdOfParent (soit un tiers de la largeur du parent, qui est… Qui est ? Le dialogue, évidemment). Indiquez 30 comme taille de police de caractères (« FontSize »). À présent, il faut définir les éléments que contiendra le contrôle ; cliquez sur les trois points en regard de la propriété « Items » et inscrivez ces quatre lignes dans le dialogue qui apparaît :

Le contrôle suivant est un jTextView, que vous déposez précisément sur l'icône du dialogue et que vous nommez lblNbMinimumLettres. Sélectionnez comme ancre (« Anchor ») le jSpinner que vous avez déposé juste avant. Dans la propriété « PosRelativeToAnchor », cochez raToLeftOf (à gauche de l'ancre) et raAlignBottom (aligné par le bas avec l'ancre). Indiquez comme texte du label 'Nombre minimum de lettres :'.

Au tour du second jSpinner : nommez-le spNbMaximumEchecs, sélectionnez spNbMinimumLettres comme ancre, 30 comme taille de caractères, cochez raBelow dans la propriété « PosRelativeToAnchor » et rpRight dans « PosRelativeToParent », et fixez comme largeur lpOneThirdOfParent. Il reste à définir les éléments que l'utilisateur pourra choisir :

Le second jTextView, lblNbMaximumEchecs, doit être placé à gauche de son ancre spNbMaximumEchecs, et aligné par la base avec elle. Il affichera 'Nombre maximum d'échecs :'.

Déposez enfin un jButton sur l'icône du dialogue. Son identificateur sera btnDlgNouvellePartieOk, son ancre spNbMaximumEchecs, relativement à laquelle il sera positionné en dessous (raBelow), tandis qu'il sera centré horizontalement dans le dialogue parent (rpCenterHorizontal).

Vous savez quoi ? L'interface de l'application est terminée ! C'est le moment de se prendre un petit café.

V-E - Partie « métier » du projet

V-E-1 - Création des différents champs

Notre application va avoir besoin de quelques variables, qui seront définies en tant que champs de la classe de fenêtre principale. Nous les préfixons de la lettre « F », pour « field » (champ) :

- FDico, de type chaîne de caractères : ce sera le dictionnaire de mots ;

- FMotADeviner, de type chaîne de caractères : le mot qu'il faudra deviner ;

- FLettresTrouvees, de type chaîne de caractères : les lettres devinées par le joueur ;

- FEchecsRestants, de type entier : le nombre d'échecs restants avant de perdre la partie ;

- FNbMinLettres, de type entier : le nombre minimal de lettres que devra contenir le mot à deviner ;

- FNbMaxEchecs, de type entier : le nombre maximal d'échecs avant de perdre la partie.

Nous aurons également besoin de savoir à quel stade de la partie on est, pour éviter par exemple que le joueur puisse continuer à essayer des lettres alors que la partie est déjà gagnée ou perdue. Le type énuméré est excellent dans ce genre de cas. Dans la section « type », avant le type TfrmPrincipale, déclarez le type suivant :

type

{ TEtatPartie }

TEtatPartie = (epNonCommencee, epEnCours, epVictoire, epDefaite);

{ TfrmPrincipale }

TfrmPrincipale = class(jForm)
[ . . . ]

Tous les champs à créer doivent se trouver dans la partie « private » de la classe de fenêtre principale :

TfrmPrincipale = class(jForm)

[ . . . ]   { Tous les composants }

private
FEtatPartie: TEtatPartie;
FDico: String;
FNbMinLettres: Integer;
FNbMaxEchecs: Integer;
FEchecsRestants: Integer;
FMotADeviner: String;
FLettresTrouvees: String;
public
{public declarations}
end;

V-E-2 - Le dictionnaire de mots

Pour les besoins du livre d'initiation au développement avec Lazarus, j'ai été amené à utiliser une liste de mots français libre de droits, « nettoyée » de manière à ne laisser que des mots en majuscules, expurgés de leurs accents et d'une longueur d'au moins quatre lettres. Un des fondements de l'informatique étant la fainéantise, j'ai réutilisé pour le présent projet la même liste de mots, sauvegardée dans un simple fichier texte appelé « dico.txt ». Les 13.879 mots qu'il contient sont simplement séparés par des espaces. Ce fichier est présent dans les sources disponibles à la fin de ce tutoriel, mais vous pouvez déjà le télécharger à cette adresse : https://alcatiz.developpez.com/tutoriel/creer-applications-android-lazarus-lamw/fichiers/dico.txt (faites un clic droit sur le lien puis « Enregistrer la cible du lien sous… »).

Lorsque nous avons survolé les différents dossiers qui composent l'arborescence du projet, nous avons évoqué « assets ». C'est à cet endroit qu'il faut placer les fichiers qui doivent être installés avec l'application et c'est en effet là qu'il faut déposer le fichier « dico.txt ».

Pour éventuellement pouvoir utiliser une autre liste de mots sans devoir modifier tout le code source, créez deux constantes au début de la section « interface » :

unit ufrmPrincipale;

{$mode delphi}

interface

uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils, StrUtils, AndroidWidget, menu, And_jni, Laz_And_Controls,
customdialog, Spinner, textfilemanager;

const

{ Dictionnaire }

C_NOMDICO    = 'dico.txt';
C_NBMOTSDICO = 13879;

La constante C_NOMDICO représente le nom du fichier et C_NBMOTSDICO le nombre de mots qu'il contient. Ainsi, en cas de changement de nom de fichier ou de nombre de mots contenus, il suffira de modifier ces deux constantes sans devoir aller dans le reste du code source.

Depuis la palette « Android Bridges Extra », déposez un composant jTextFileManager (vers la fin de la palette) sur la fiche principale, par exemple à côté du menu principal. Renommez-le textFileManager dans sa propriété « Name ».

Dans la partie « private » de la fenêtre principale, créez la méthode suivante :

procedure ChargerDico;

Grâce à la combinaison de touches « Maj+ctrl+C », créez l'implémentation de cette méthode et complétez-la comme suit :

procedure TfrmPrincipale.ChargerDico;
(* Chargement du contenu du dictionnaire *)
begin
FDico := textFileManager.LoadFromAssets(C_NOMDICO);
end;

Cette unique instruction demande au composant de type jTextFileManager de charger dans le champ FDico le contenu du fichier texte présent dans le répertoire « assets » de l'application.

Le composant jImageFileManager, également sur la palette « Android Bridges Extra », permet de faire la même chose avec des images.

Créez ensuite, dans la partie « private » de la fenêtre principale, une méthode SelectionnerMot, qui va retourner un mot choisi au hasard dans le dictionnaire :

function SelectionnerMot: String;

Utilisez la combinaison de touches « Maj+Ctrl+C » pour créer le corps de la méthode dans la partie « implementation » et complétez-la comme suit :

function TfrmPrincipale.SelectionnerMot: String;
(* Retourne un mot sélectionné dans le dictionnaire *)
var
Li: Integer;
LTrouve: Boolean = False;
begin
Result := '';
(* Choix d'un mot au hasard *)
Li := Random(C_NBMOTSDICO + 1);
repeat
(* Extraction du Lième mot *)
Result := ExtractWord(Li, FDico, StdWordDelims);
if Length(Result) >= FNbMinLettres
then   (* Le mot est assez long : on arrête les recherches *)
LTrouve := True
else   (* Le mot est trop court *)
if Li = C_NBMOTSDICO
then   (* On arrive à la fin de la liste : on recommence au début *)
Li := 1
else   (* On passe au mot suivant *)
Inc(Li);
until LTrouve;
end;

Concrètement, on tire au hasard l'indice d'un mot puis on vérifie si sa longueur répond bien au critère de nombre minimal de lettres ; si ce n'est pas le cas, on passe au mot suivant jusqu'à en trouver un qui réponde à la condition.

Pour pouvoir utiliser la fonction ExtractWord, qui extrait le énième mot d'une chaîne de caractères - chaque mot étant séparé des autres par un délimiteur (ici une espace) - il faut ajouter l'unité StrUtils (routines de gestion de chaînes de caractères) dans la clause « uses » tout au début du code source.

L'utilisation de la fonction Random implique que le générateur de nombres aléatoires soit préalablement initialisé. Abordons donc le sujet de l'initialisation d'une application Android.

V-E-3 - Initialisation de l'application

Voici un principe très important :

L'endroit où il convient d'initialiser les champs et les contrôles de la fenêtre est le gestionnaire de l'événement « OnJNIPrompt ».

Les autres initialisations peuvent éventuellement se faire dans le gestionnaire de l'événement « OnCreate » (mais tout peut être regroupé dans le gestionnaire de l'événement « OnJNIPrompt »).

L'initialisation du générateur de nombres aléatoires n'ayant rien à voir avec les champs de la classe de fenêtre elle-même, allez dans l'onglet « Événements » du composant frmPrincipale et cliquez sur les trois points en regard de l'événement « OnCreate » afin de créer le gestionnaire d'événement suivant :

procedure TfrmPrincipale.frmPrincipaleCreate(Sender: TObject);
(* Initialisations *)
begin
Randomize;
end;

Nous allons confier l'initialisation des champs définis dans la classe de fenêtre au gestionnaire de l'événement « OnJNIPrompt » ; cliquez donc sur les trois points en regard de l'événement et complétez le gestionnaire fraîchement créé par Lazarus :

procedure TfrmPrincipale.frmPrincipaleJNIPrompt(Sender: TObject);
(* Initialisation des champs et des contrôles *)
begin
(* Initialisation des champs *)
FEtatPartie := epNonCommencee;
FNbMinLettres := C_NBMINLETTRES;
FNbMaxEchecs := C_NBMAXECHECS;
FEchecsRestants := FNbMaxEchecs;
(* Chargement du dictionnaire *)
ChargerDico;
end;

Vous voyez en passant que deux nouvelles constantes doivent être déclarées, au même endroit que le nom de fichier du dictionnaire et le nombre de mots :

const

{ Valeurs par défaut }

C_NBMINLETTRES = 4;
C_NBMAXECHECS  = 4;

Ainsi, au démarrage du jeu, avant que le joueur ait éventuellement pu choisir son niveau de difficulté, des valeurs par défaut sont assignées au nombre minimum de lettres et au nombre maximum d'échecs. Nous verrons tout à la fin du tutoriel comment sauvegarder les préférences du joueur, mais pour l'instant tenons-nous-en à ces valeurs par défaut.

V-E-4 - Dialogue de nouvelle partie

Occupons-nous du dialogue personnalisé que nous avons créé et garni de ses composants. Rappelez-vous, le premier jSpinner contient le nombre minimal de lettres d'un mot, et le second le nombre maximal d'échecs avant de perdre la partie. Ces deux données se trouvent respectivement dans les champs FNbMinLettres et FMaxEchecs. Au démarrage du dialogue, nous devons donc sélectionner ces deux montants dans les jSpinner.

Dans l'inspecteur d'objets, sélectionnez le dialogue dlgNouvellePartie puis, dans l'onglet « Événements », cliquez sur les trois points correspondant à l'événement « OnShow ». Complétez le gestionnaire d'événement créé par :

procedure TfrmPrincipale.dlgNouvellePartieShow(Sender: TObject; dialog: jObject; title: string);
(* Charge les valeurs des deux spinners *)
begin
spNbMinimumLettres.SetSelectedIndex(FNbMinLettres div 2 - 2);
spNbMaximumEchecs.SetSelectedIndex(FNbMaxEchecs - 3);
end;

Les indices des éléments à sélectionner dans les deux contrôles sont calculés sur base de la valeur du champ correspondant. Par exemple, si FNbMinLettres vaut 4, l'indice de l'élément à sélectionner dans spNbMinimumLettres vaut 4 div 2 – 2, c'est-à-dire 0 : '0' est bien le premier élément qui a été inscrit dans le jSpinner. Voilà tout pour l'initialisation du dialogue.

Lorsque l'utilisateur pressera le bouton « OK », il faudra recopier dans les champs FNbMinLettres et FNbMaxEchecs les éléments sélectionnés dans les deux jSpinner. Dans l'inspecteur d'objets, sélectionnez le bouton btnNouvellePartieOk et cliquez sur les trois points en regard de l'événement « OnClick », pour créer le gestionnaire d'événement suivant :

procedure TfrmPrincipale.btndlgNouvellePartieOKClick(Sender: TObject);
(* Lance une nouvelle partie *)
begin
(* Sauvegarde du nombre minimum de lettres et du nombre maximum d'échecs *)
FNbMinLettres := StrToInt(spNbMinimumLettres.GetSelectedItem);
FNbMaxEchecs := StrToInt(spNbMaximumEchecs.GetSelectedItem);
FEchecsRestants := FNbMaxEchecs;
NouvellePartie;
(* Fermeture du dialogue *)
dlgNouvellePartie.Close;
end;

Vous voyez que les champs FNbMinLettres et FNbMaxEchecs reçoivent les valeurs sélectionnées dans les jSpinner ; il ne s'agit plus ici des index mais des éléments eux-mêmes, donc des chaînes de caractères, qu'il faut donc retransformer en nombres entiers à l'aide de StrToInt.

Vous voyez également que la dernière instruction ferme le dialogue : si cette instruction n'est pas exécutée, le dialogue restera à l'écran.

Enfin, vous constatez que c'est dans ce gestionnaire d'événement que démarre une nouvelle partie : le champ FEchecsRestants est réinitialisé et tout le reste se trouve dans une nouvelle procédure, que nous allons créer maintenant.

V-E-5 - Initialisation d'une nouvelle partie

Dans la partie « private » de la classe TfmPrincipale, ajoutez cette méthode :

private
FEtatPartie: TEtatPartie;
FDico: String;
FNbMinLettres: Integer;
FNbMaxEchecs: Integer;
FEchecsRestants: Integer;
FMotADeviner: String;
FLettresTrouvees: String;
procedure ChargerDico;
function SelectionnerMot: String;
procedure NouvellePartie;   // <-- Nouvelle méthode
public
{public declarations}
end;

Pressez la combinaison de touches « Maj+Ctrl+C » puis complétez le code :

procedure TfrmPrincipale.NouvellePartie;
(* Initialise une nouvelle partie *)
begin
(* Choisit un mot *)
FMotADeviner := SelectionnerMot;
(* Initialise les lettres trouvées et affiche les points d'interrogation à l'écran *)
FLettresTrouvees := AddChar('?', '', Length(FMotADeviner));
tvMotADeviner.Text := FLettresTrouvees;
(* Affiche le nombre d'échecs restants *)
tvEchecsRestants.Text := IntToStr(FEchecsRestants);
(* Met en route la partie *)
FEtatPartie := epEnCours;
end;

Dans le jTextView du mot à trouver, ce sont uniquement des points d'interrogation qui s'affichent. Au fur et à mesure de la découverte des lettres, ces dernières viendront s'insérer à leur place.

À présent que nous avons créé une méthode pour initialiser une nouvelle partie, ajoutons-la à la fin du gestionnaire d'événement frmPrincipaleJNIPrompt :

procedure TfrmPrincipale.frmPrincipaleJNIPrompt(Sender: TObject);
(* Initialisation des champs et des contrôles *)
begin
(* Initialisation des champs *)
FEtatPartie := epNonCommencee;
FNbMinLettres := C_NBMINLETTRES;
FNbMaxEchecs := C_NBMAXECHECS;
FEchecsRestants := FNbMaxEchecs;
(* Chargement du dictionnaire *)
ChargerDico;
(* Démarrage d'une nouvelle partie *)
NouvellePartie;
end;

Ainsi, dès le démarrage de l'application, le joueur pourra commencer à jouer sans devoir lancer une partie dans le menu.

V-E-6 - Gestion du menu

Une des premières choses que nous avons faites, au début de la création de l'interface de l'application, était le dépôt d'un composant jMenu. Si vous êtes rompu(e) à l'utilisation avec Lazarus de menus (en l'occurrence, TMainMenu) dans les applications classiques, le principe suivant en diffère grandement :

Aucun événement n'est défini pour le composant jMenu : les événements liés au menu de l'application sont définis au niveau de la classe de fenêtre elle-même.

Dans l'inspecteur d'objets, sélectionnez le composant frmPrincipale puis l'onglet « Événements ». Cliquez successivement sur les trois points en regard des événements « OnCreateOptionMenu » et « OnClickOptionMenuItem » pour créer les deux gestionnaires suivants :

procedure TfrmPrincipale.frmPrincipaleCreateOptionMenu(Sender: TObject; jObjMenu: jObject);
(* Affiche les éléments du menu principal *)
begin
mnuPrincipal.ShowOptions(jObjMenu);
end;

procedure TfrmPrincipale.frmPrincipaleClickOptionMenuItem(Sender: TObject; jObjMenuItem: jObject; itemID: integer; itemCaption: string; checked: boolean);
(* Réagit au choix d'un élément du menu principal *)
begin
case itemID of
1: dlgNouvellePartie.Show('Nouvelle partie');
end;
end;

La première méthode s'occupe de l'affichage des éléments du menu lorsque l'utilisateur presse sur les trois points verticaux dans la barre d'action supérieure de l'application. La seconde affiche le dialogue dlgNouvellePartie lorsque le joueur choisit le premier (et unique) élément du menu. Remarquez que le « case…of » est un peu superflu ici, mais comme cela vous voyez comment gérer un menu à plusieurs éléments.

V-E-7 - Gestion du clavier virtuel

Nous avons beaucoup à dire à propos du clavier qui compose la partie inférieure de l'application. Rappelez-vous, lors de la construction de l'interface du jeu, nous avons créé le gestionnaire d'événement btnLettreClick, partagé par les 26 boutons qui composent le clavier. Il est temps de compléter le code de cette méthode, pour définir ce qu'il se passe lorsque le joueur essaie une lettre :

procedure TfrmPrincipale.btnLettreClick(Sender: TObject);
(* Choix d'une lettre *)
begin
if FEtatPartie = epEnCours
then   (* On ne réagit que si la partie est en cours *)
begin
FEtatPartie := Tentative(jButton(Sender).Text[1]);
(* Désactive la touche du clavier *)
jButton(Sender).Enabled := False;
end;
end;

Tout d'abord, il faut s'assurer que le fait de presser une touche du clavier virtuel n'a d'effet qu'en cours de partie, ce qui fait l'objet de la première instruction. Si l'on est bien en cours de partie, il faut voir si la lettre pressée est bien dans le mot, ce qui fait l'objet de la fonction Tentative, que nous allons écrire tout de suite. Enfin, une fois que le bouton a été pressé, il faut le désactiver jusqu'à la prochaine partie afin ,d'une part, que le joueur ne retente pas la même lettre et, d'autre part, qu'il ait une vue sur toutes les lettres qu'il a déjà essayées – puisqu'à l'état désactivé, le bouton va apparaître grisé. L'activation du bouton fait l'objet de la propriété « Enabled ».

Dans la partie « interface » de la classe de fenêtre principale, créez la méthode Tentative :

function TfrmPrincipale.Tentative(const ALettre: Char): TEtatPartie;
(* Tentative de placer une lettre dans le mot *)
var
Li: Integer;
LTrouve: Boolean = False;
begin
for Li := 1 to Length(FMotADeviner) do
if FMotADeviner[Li] = ALettre
then
begin
FLettresTrouvees[Li] := ALettre;
LTrouve := True;
end;
if LTrouve
then   (* Au moins une lettre a été trouvée *)
begin
tvMotADeviner.Text := FLettresTrouvees;
if FLettresTrouvees = FMotADeviner
then   (* Gagné ! *)
begin
Result := epVictoire;
tvEchecsRestants.Text := Self.ParseHtmlFontAwesome('F2E7');   (* Smiley de circonstance *)
ShowMessage('Bravo !');
end
else   (* On continue *)
Result := epEnCours;
end
else   (* Échec *)
if FEchecsRestants = 1
then   (* La partie est perdue *)
begin
Result := epDefaite;
tvEchecsRestants.Text := Self.ParseHtmlFontAwesome('F2ED');   (* Smiley de circonstance *)
tvMotADeviner.Text := FMotADeviner;
ShowMessage('Vous ferez mieux la prochaine fois !');
end
else   (* On continue *)
begin
Result := epEnCours;
Dec(FEchecsRestants);
tvEchecsRestants.Text := IntToStr(FEchecsRestants);
end;
end;

En sortie de fonction, l'état de la partie et le nombre d'échecs restants sont mis à jour :

- soit la partie reste en cours : la valeur retournée est epEnCours et le nombre d'échecs est soit inchangé (si une lettre est trouvée) soit décrémenté d'une unité ;

- soit la partie est gagnée : la valeur retournée est epVictoire, un émoji rieur s'affiche à la place du nombre d'échecs restants et le petit message « Bravo ! » s'affiche au bas de l'écran ;

- soit la partie est perdue : la valeur retournée est epDefaite, un émoji triste s'affiche à la place du nombre d'échecs restants et un petit message d'encouragement s'affiche au bas de l'écran.

La procédure ShowMessage permet d'afficher brièvement un petit message au bas de l'écran.

Ne vous souciez pas pour l'instant des deux émojis, nous en parlerons plus loin. En attendant, nous n'en avons pas tout à fait terminé avec notre clavier virtuel.

Comme les touches du clavier sont désactivées au fur et à mesure de l'avancement de la partie, il faudra logiquement les réactiver au début de la partie suivante. Il faut donc compléter la méthode NouvellePartie :

procedure TfrmPrincipale.NouvellePartie;
(* Initialise une nouvelle partie *)
var
Li: Integer;
begin
(* Réactive les touches du clavier virtuel éventuellement désactivées *)
for Li := 0 to (frmPrincipale.ComponentCount - 1) do
if frmPrincipale.Components[Li] is jButton then
jButton(frmPrincipale.Components[Li]).Enabled := True;
(* Choisit un mot *)
FMotADeviner := SelectionnerMot;
// Ce qui suit figurait déjà dans la procédure
(* Initialise les lettres trouvées et affiche les points d'interrogation à l'écran *)
FLettresTrouvees := AddChar('?', '', Length(FMotADeviner));
tvMotADeviner.Text := FLettresTrouvees;
(* Affiche le nombre d'échecs restants *)
tvEchecsRestants.Text := IntToStr(FEchecsRestants);
(* Met en route la partie *)
FEtatPartie := epEnCours;
end;

Dans une boucle « for », nous faisons le tour de tous les composants présents sur la fenêtre principale ; s'il s'agit de boutons, de type jButton, c'est forcément une lettre et donc le composant est activé, quel que soit son état.

V-E-8 - Affichage d'émojis

Pour éviter de devoir recourir à des composants spécialisés dans l'affichage d'images, en cas de victoire ou de défaite, nous affichons un émoji directement dans le jTextView où figure le nombre d'échecs restants. Pour ce faire, nous recourons à une police de caractères du web : « Material Design Icons », de Google, sous licence Apache 2.0. C'est ainsi que dans les sources du projet, vous trouverez un fichier « materialdesignicons-webfont.ttf » dans le dossier « assets », à côté du dictionnaire.

Pour pouvoir charger cette police de caractères pour l'utiliser dans le jTextView, complétez le gestionnaire d'événement frmprincipaleJNIPrompt :

procedure TfrmPrincipale.frmPrincipaleJNIPrompt(Sender: TObject);
(* Initialisation des champs et des contrôles *)
begin
(* Initialisation des champs *)
FEtatPartie := epNonCommencee;
FNbMinLettres := C_NBMINLETTRES;
FNbMaxEchecs := C_NBMAXECHECS;
FEchecsRestants := FNbMaxEchecs;
(* Chargement d'une police avec caractères spéciaux *)
tvEchecsRestants.SetFontFromAssets('materialdesignicons-webfont.ttf');
tvEchecsRestants.Text := IntToStr(FEchecsRestants);
(* Chargement du dictionnaire *)
ChargerDico;
(* Démarrage d'une nouvelle partie *)
NouvellePartie;
end;

Pour afficher un émoji dans la fonction Tentative, il faut recourir à la méthode ParseHtmlFontAwesome, qui attend l'indice du caractère sous forme d'une chaîne représentant un nombre hexadécimal :

Self.ParseHtmlFontAwesome('F2E7');

V-E-9 - Enregistrement des préférences

Nous allons terminer ce tutoriel par l'ajout de la possibilité d'enregistrer les préférences de l'utilisateur quant au nombre minimal de lettres et au nombre maximal d'échecs. Ainsi, dès le démarrage de l'application, sa première partie aura le même niveau de difficulté que la dernière partie qu'il avait faite lors de la précédente exécution de l'application. Et c'est tout simple, ce qui est très bien pour finir en douceur.

Dans la palette « Android Bridges Extra », sélectionnez le composant jPreferences, qui se trouve juste avant le jTextFileManager et déposez-le sur la fiche de travail. Nommez-le prefNiveau.

Dans la partie « private » de la classe de fenêtre principale, ajoutez les deux méthodes suivantes :

procedure TfrmPrincipale.ChargerNiveau;
(* Restaure le nombre minimum de lettres et le nombre maximum d'échecs depuis les préférences *)
begin
FNbMinLettres := prefNiveau.GetIntData(C_PREFNBMINLETTRES, C_NBMINLETTRES);
FNbMaxEchecs := prefNiveau.GetIntData(C_PREFNBMAXECHECS, C_NBMAXECHECS);
end;

procedure TfrmPrincipale.SauverNiveau;
(* Sauvegarde le nombre minimum de lettres et le nombre maximum d'échecs dans les préférences *)
begin
prefNiveau.SetIntData(C_PREFNBMINLETTRES, FNbMinLettres);
prefNiveau.SetIntData(C_PREFNBMAXECHECS, FNbMaxEchecs);
end;

Au début du code source, il faut donc rajouter deux constantes, avec les noms des « variables » stockées dans les préférences :

{ Préférences }

C_PREFNBMINLETTRES = 'NbMinLettres';
C_PREFNBMAXECHECS  = 'NbMaxEchecs';

Une série de méthodes permettent de charger (get) ou de sauvegarder (set) des données dans les préférences de l'application, qui sont stockées par le système et qui restent valides jusqu'à la désinstallation de l'application :

- GetBoolData et SetBoolData pour des données de type booléen ;

- GetFloatData et SetFloatData pour des nombres à virgule flottante ;

- GetIntData, GetLongData, SetIntData et SetLongData pour des entiers ;

- GetStringData et SetStringData pour des chaînes de caractères.

Il reste à préciser où exécuter les deux routines. Fort logiquement, ChargerNiveau sera exécutée lors de l'initialisation des contrôles de l'application, dans le gestionnaire d'événement frmPrincipaleJNIPrompt, que nous complétons une dernière fois :

procedure TfrmPrincipale.frmPrincipaleJNIPrompt(Sender: TObject);
(* Initialisation des champs et des contrôles *)
begin
(* Initialisation des champs *)
FEtatPartie := epNonCommencee;
FNbMinLettres := C_NBMINLETTRES;
FNbMaxEchecs := C_NBMAXECHECS;
(* Chargement du nombre minimum de lettres et du nombre max d'échecs depuis les préférences *)
ChargerNiveau;
FEchecsRestants := FNbMaxEchecs;
(* Chargement d'une police avec caractères spéciaux *)
tvEchecsRestants.SetFontFromAssets('materialdesignicons-webfont.ttf');
tvEchecsRestants.Text := IntToStr(FEchecsRestants);
(* Chargement du dictionnaire *)
ChargerDico;
(* Démarrage d'une nouvelle partie *)
NouvellePartie;
end;

La sauvegarde des préférences, par contre, peut se faire à la fermeture de l'application. Or, ce qui suit est très important :

Contrairement à une application classique pour Linux ou Windows, dans une application pour Android il ne faut pas se servir de l'événement « OnDestroy » de la fenêtre principale, par exemple pour libérer des variables dynamiques ou faire des sauvegardes.

Contrairement aux systèmes d'exploitation comme Windows ou Linux, sous Android les applications ont très rarement un bouton de fermeture de l'application, c'est contraire aux habitudes d'utilisation. Sur mobile, l'utilisateur mettra généralement l'application en arrière-plan pour en utiliser une autre, éventuellement la balayera vers le haut de l'écran pour la fermer. Android gère efficacement la mémoire et la batterie, et peut également fermer l'application sans crier gare. Bref, il n'y a aucune certitude sur le fait qu'une application soit correctement fermée comme sur un PC.

Par conséquent, dans le cas de sauvegardes comme dans le cas présent, il vaut mieux réagir à un événement qui aura une nettement plus forte probabilité de se produire que la fermeture de l'application, par exemple la mise en pause, qui se produit dès que l'application n'a plus le focus à l'écran.

Dans l'inspecteur d'objets, sélectionnez la fenêtre principale et, dans l'onglet « Événements », créez un gestionnaire pour l'événement « OnActivityPause » :

procedure TfrmPrincipale.frmPrincipaleActivityPause(Sender: TObject);
(* Sauvegarde du nombre minimal de lettres et du nombre maximal d'échecs dans les préférences *)
begin
SauverNiveau;
end;

Nous avons presque terminé notre application, il ne reste qu'à lui assigner une icône.

V-F - Icône de l'application

Alors que cette étape est très simple pour une application classique, la création d'icônes pour une application Android est assez complexe, puisqu'il faut la fournir sous différentes résolutions et sous différents formats. La meilleure approche est d'aller voir ce qui existe par défaut dans le dossier « res » (les ressources) du projet. Les icônes se trouvent dans les dossiers suivants :

- « drawable-ldpi » : contient une image « ic_launcher.png » de résolution 36*36 pixels ;

- « drawable-mdpi » : contient une image « ic_launcher.png » de résolution 48*48 pixels ;

- « drawable-hdpi » : contient une image « ic_launcher.png » de résolution 72*72 pixels ;

- « drawable-xhdpi » : contient une image « ic_launcher.png » de résolution 96*96 pixels ;

- « drawable-xxhdpi » : : contient une image « ic_launcher.png » de résolution 144*144 pixels ;

- « drawable » : contient un fichier xml « ic_launcher_background.xml » contenant une définition de l'arrière-plan d'une icône adaptative (voir « mipmap-anydpi-v26 » ci-dessous) ;

- « drawable-v24 » : contient un fichier xml « ic_launcher_foreground.xml » contenant une définition de l'avant-plan d'une icône adaptative (voir « mipmap-anddpi-v26 » ci-dessous) ;

- « mipmap-mdpi » : contient une image « ic_launcher.webp » de résolution 48*48 pixels et une image « ic_launcher_round.webp » (la version arrondie de la précédente) de résolution 48*48 pixels ;

- « mipmap-hdpi » : contient une image « ic_launcher.webp » de résolution 72*72 pixels et une image « ic_launcher_round.webp » (la version arrondie de la précédente) de résolution 72*72 pixels ;

- « mipmap-xhdpi » : contient une image « ic_launcher.webp » de résolution 96*96 pixels et une image « ic_launcher_round.webp » (la version arrondie de la précédente) de résolution 96*96 pixels ;

- « mipmap-xxhdpi » : contient une image « ic_launcher.webp » de résolution 144*144 pixels et une image « ic_launcher_round.webp » (la version arrondie de la précédente) de résolution 144*144 pixels ;

- « mipmap-xxxhdpi » : contient une image « ic_launcher.webp » de résolution 192*192 pixels et une image « ic_launcher_round.webp » (la version arrondie de la précédente) de résolution 192*192 pixels ;

- « mipmap-anydpi-v26 » : contient une icône adaptative « ic_launcher.xml » et sa version arrondie « ic_launcher_round.xml ».

Pourquoi autant de dossiers ? Pour pouvoir adapter au mieux votre application aux différentes résolutions d'écran et aux différentes versions d'Android, selon le mobile sur lequel elle sera installée.

Toute cette arborescence est le fruit d'une évolution des possibilités techniques des appareils, allant vers toujours plus de résolution d'écran, et de changements au niveau d'Android même. Dans l'ordre chronologique :

- les sous-dossiers « drawable », qui contiennent des images png ;

- les sous-dossiers « mipmap », qui contiennent soit des images png, soit des images webp (plus modernes) ;

- les icônes adaptatives au format xml.

Une icône adaptative (« adaptive icon » en anglais) est en fait un fichier xml dont le contenu permet de construire l'icône à la bonne résolution à partir des définitions du fond et de l'avant-plan, contenues soit dans un des dossiers « drawable », soit dans un des dossiers « mipmap » sous forme d'un fichier xml, png ou webp.

Voici par exemple le contenu de l'icône adaptative « ic_launcher.xml » contenue dans le sous-dossier « mipmap-anydpi-26 » :

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

V-F-1 - Création d'icônes avec LAMW

Un outil installé avec LAMW permet, au départ d'une image, de répliquer celle-ci dans tous les sous-dossiers « drawable », dans les bonnes résolutions. À votre charge, ensuite, de les recopier dans les sous-dossiers « mipmap » correspondants. Disons-le tout de suite, cette solution ne donne pas des résultats fantastiques, et il n'est pas question d'icônes adaptatives. Cette méthode implique de partir d'une image de la meilleure résolution possible, sous peine de vous retrouver avec des icônes pixelisées à l'excès.

Créez votre image de dimensions 192*192 pixels, au format png. Ouvrez le menu « Outils / [LAMW] Android Module Wizard / Use/Import Image/Picture » et, dans le dialogue qui apparaît, sélectionnez l'image en cliquant sur les trois points :

Cochez tous les sous-dossiers « drawable » puis pressez « OK ».

V-F-2 - Création d'icônes à la main

Vous pouvez aussi créer à la main une icône de taille 192*192 pixels, constituée de deux calques (un fond et un avant-plan). Enregistrez le calque de fond dans un fichier « ic_launcher_background.png » (ou « .webp » ou « .svg », en version vectorielle) et le calque d'avant-plan dans un fichier « ic_launcher_foreground.png » (ou « .webp » ou « .svg »). Vous pouvez alors rédiger à la main le contenu du fichier « ic_launcher.xml » d'après l'exemple montré plus haut. Puis, à l'aide d'un logiciel de dessin, rassemblez les deux calques en une seule image, adaptez successivement sa résolution et enregistrez-en une copie dans chaque sous-dossier correspondant.

Cette méthode peut être chronophage !

V-F-3 - Création d'icônes avec Android Studio

La solution la plus simple implique de confier toutes ces tâches à Android Studio, un environnement de développement d'applications Android en Java ou en Kotlin, gratuit et disponible pour Linux et Windows, doté d'excellents outils dont un dédié à la création des ressources telles que l'icône de l'application. En plus, installer Android Studio peut également vous faciliter la vie si vous désirez exécuter vos applications sur un émulateur – sujet qui va être abordé à la fin de ce tutoriel.

Créez une image de taille 256*256 pixels. Créez dans Android Studio un projet vide, allez dans le gestionnaire de ressources (« Resource Manager »), faites le petit « + » puis « Image Asset » :

Dans l'onglet « Foreground Layer », sélectionnez votre image dans le champ « Path » puis, dans l'onglet « Background Layer », cliquez sur le champ « Color » pour choisir une couleur de fond dans le nuancier. Cliquez enfin sur le bouton « Next » : Android Studio crée absolument toutes les déclinaisons de l'icône.

Dans le dossier « /app/src/main/res » du projet Android Studio, vous retrouvez les images créées et vous n'avez qu'à les recopier dans les sous-dossiers correspondants du projet Pendu. Attention, dans les sous-dossiers « drawable », il faudra convertir les images du format « .webp » vers le format « .png », ce qu'à peu près n'importe quel logiciel de dessin peut faire.

V-G - Réglage des permissions

Selon ce qu'elle est censée faire, et selon la version minimale d'Android visée, votre application peut nécessiter certains droits pour fonctionner. Le sujet est assez complexe, aussi n'allons-nous faire que l'effleurer : les permissions.

Lors de sa distribution, l'application est accompagnée d'un fichier manifeste qui répertorie toutes les permissions nécessaires à son fonctionnement. Le fichier « AndroidManifest.xml » est présent à la racine du projet ; vous pouvez l'éditer à la main, comme tout fichier xml, mais LAMW met à votre disposition un dialogue dans lequel vous pouvez activer ou désactiver les permissions. Ce dialogue est accessible via le menu « Projet / Options du projet… / [LAMW] Android Project Options » :

Essayez d'activer un minimum de permissions, mais prenez garde de ne pas en désactiver certaines qui empêcheraient votre application de fonctionner de manière optimale.

Si certaines permissions propres à une version récente d'Android ne sont pas disponibles dans le dialogue, vous pouvez les ajouter manuellement dans le fichier « AndroidManifest.xml ».

Quelques exemples, parmi les dizaines de permissions qui existent :

- « ACCESS_FINE_LOCATION » : accès au GPS ;

- « ACCESS_NETWORK_STATE » : accès à l'état du réseau (Wi-Fi, données mobiles) ;

- « ACCESS_WIFI_STATE » : accès à l'état du réseau Wi-Fi ;

- « BATTERY_STATS » : accès à l'état de charge de la batterie ;

- « BLUETOOTH » : connexion Bluetooth à d'autres appareils appariés ;

- « BLUETOOTH_ADMIN » : repérage et appariement à d'autres appareils par Bluetooth ;

- « CAMERA » : accès à la caméra et à la lampe de poche ;

- « CHANGE_NETWORK_STATE » : changement de l'état de la connectivité réseau ;

- « CHANGE_WIFI_STATE » : changement de l'état de la connectivité Wi-Fi ;

- « INTERNET » : accès à internet ;

- « READ_CALENDAR » : lecture du calendrier ;

- « READ_CONTACTS » : accès à la liste des contacts ;

- « READ_EXTERNAL_STORAGE » : lecture du stockage externe de l'appareil ;

- « READ_PHONE_STATE » : accès à l'état du téléphone, au numéro IMEI, etc. ;

- « READ_SMS » : lecture de SMS ;

- « RECEIVE_SMS » : réception de SMS ;

- « RECORD_AUDIO » : accès au micro ;

- « SEND_SMS » : envoi de SMS ;

- « WRITE_CALENDAR » : écriture dans le calendrier ;

- « WRITE_CONTACTS » : écriture dans la liste de contacts ;

- « WRITE_EXTERNAL_STORAGE » : écriture sur le stockage externe de l'appareil.

V-H - Création de l'exécutable

Si vous utilisez régulièrement Lazarus, vous vous dites certainement que vous retombez dans un domaine que vous connaissez bien. Détrompez-vous !

La compilation par le compilateur Free Pascal ne crée pas d'exécutable. Elle crée un ensemble de fichiers sources en Java qui pourront être utilisés par une chaîne d'outils externes pour créer un exécutable Android.

La compilation ne peut se faire que via le menu « Exécuter / Compiler » ou par la combinaison de touches « Ctrl-F9 ». Dans la fenêtre de messages de l'environnement de développement, vous pouvez suivre le processus de compilation et corriger dans votre code source les erreurs qui sont relevées, jusqu'à obtenir le sésame, c'est-à-dire le message « Succès » sur fond vert.

Ensuite, lancez la construction de l'exécutable via le menu « Exécuter / [LAMW] Build Android Apk and Run ». Une série de commandes va lancer successivement les outils externes qui ont été installés au début de ce tutoriel, le plus important étant Gradle. Sous Windows, la sortie des commandes de construction de l'exécutable est récupérée dans la fenêtre « Messages » de Lazarus ; sous Linux, tout se passe dans le terminal :

L'opération peut prendre plusieurs minutes. Lorsqu'elle est terminée, vu que la commande se termine par « and Run », Lazarus essaie d'installer l'application sur un mobile connecté et de l'exécuter. S'il n'y a pas de mobile connecté, rien ne se passe mais l'essentiel est fait : le paquetage final, au format apk, est créé.

V-I - Exécution de l'application

Vous disposez de trois possibilités pour exécuter l'application :

- laisser Lazarus l'installer et l'exécuter sur un mobile connecté par USB à votre PC ;

- laisser Lazarus l'installer et l'exécuter sur un émulateur ;

- télécharger et installer vous-même l'application sur un mobile.

V-I-1 - Exécution sur un mobile connecté au PC

Vous pouvez donc laisser Lazarus installer et exécuter l'application directement sur votre mobile connecté par USB à votre PC. Cependant, il faut préalablement y activer le mode développeur, ce qui se fait typiquement comme suit :

- dans les paramètres, chercher l'option « À propos du téléphone » ;

- afficher les informations sur le logiciel (Android) ;

- presser sept fois le numéro de version, ce qui ouvrira un nouveau menu « Options de développement » ;

- y activer « Rester activé » ;

- activer « Débogage USB » ;

- activer « Vérifier les applis via USB ».

Ainsi, non seulement votre application sera directement exécutée sur votre mobile, mais elle y restera installée.

Il suffit que le mobile soit connecté à votre PC quand vous exécutez la commande « [LAMW] Build Android Apk and Run » pour qu'il soit automatiquement détecté et utilisé.

V-I-2 - Exécution sur un émulateur

Dans la panoplie d'outils installés avec LAMW se trouve un émulateur, qui se trouve dans le dossier suivant :

- « ~/LAMW/sdk/emulator » sous Linux ;

- « C:\lamw_manager\sdk\emulator » sous Windows.

Cet émulateur (de son nom emulator) nécessite de créer un mobile virtuel à l'aide de l'utilitaire AVDManager situé dans le dossier :

- « ~/LAMW/sdk/cmdline-tools/latest/bin » sous Linux ;

- « C:\lamw_manager\LAMW\sdk\cmdline-tools\latest\bin » sous Windows.

Clairement, cette possibilité n'est pas la plus simple. Vous pouvez aussi télécharger un des nombreux émulateurs gratuits disponibles sur internet ou carrément installer Android Studio, y créer votre mobile virtuel en choisissant votre version d'Android, la taille et la résolution de l'écran, etc., et exécuter l'émulateur depuis Android Studio.

Le principe est le même qu'avec un vrai mobile connecté au PC : si le mobile virtuel est en cours d'exécution (donc il faut qu'il soit virtuellement allumé), l'application sera automatiquement installée et exécutée.

V-I-3 - Installation manuelle

Vous pouvez installer manuellement votre application sur votre mobile, par exemple en la copiant dans un cloud depuis votre PC et en la téléchargeant sur votre appareil. Il faut juste la trouver !

Allez dans le dossier du projet puis descendez dans l'arborescence jusqu'à « build » puis « outputs ». Deux sous-dossiers sont présents : « debug » et « release ». Le second contient une version « non signée » de l'application, qui peut être rendue compatible avec Google Play moyennant toute une procédure de certification (hors de propos dans ce tutoriel), tandis que le premier contient une version de débogage qui s'installera sans problème sur votre mobile.

Le paquetage généré par Gradle, qui se trouve donc dans le sous-dossier « debug », porte le nom « pendu-armeabi-v7a-debug.apk ». Il s'agit ici d'une version pour une architecture ARM actuelle, puisqu'à la création du projet nous avons gardé l'architecture-cible par défaut « ARMv7a+VFPv3 ».

VI - Conclusion

Dans ce tutoriel, nous n'avons fait qu'effleurer le développement d'applications pour Android ; nous n'avons manipulé qu'une infime partie des composants disponibles. Mais le but principal était de vous présenter les grands principes et, surtout, ceux qui diffèrent - parfois radicalement - du développement classique d'applications pour PC.

Je remercie Yves Lemaire pour sa relecture technique et f-leb pour ses corrections orthographiques et grammaticales.

VI-A - Téléchargements

Téléchargez le projet complet ici : https://lazarus.developpez.com/telecharger/detail/id/7522/Jeu-du-Pendu-sous-Android-LAMW.

Téléchargez l'exécutable ici : https://alcatiz.developpez.com/fichiers/Pendu-armeabi-v7a-debug.apk. Vous pouvez l'installer directement sur votre mobile.