Créer la surface View
Contexte :
Nous allons démarrer la programmation de notre première animation graphique : le petit billard. Elle est très simple.
Néanmoins, tout ce que nous allons découvrir par la suite nous sera d'une très grande utilité pour la deuxième animation, plus réaliste et ludique à la fois. Nous allons avant tout créer une surfaceView
, faire le lien avec le fichier activity_main.xml
et approfondir quelque peu la notion de View
en programmation Android.
Comme autre exemple de View, nous allons présenter et utiliser les ImageView
.
Objectifs de la partie
Mettre en place les bases graphiques qui vont permettre au billard et au jeu canon d'apparaître à l'écran.
Les premières manipulations que nous allons vous demander d'effectuer risquent de vous apparaître à nouveau des plus ésotériques. L'idée est de créer et d'installer dans votre application une vue spéciale, une DrawingView
qui sera la surface de jeu, sur laquelle vous représenterez tout ce qui constituera l'aspect graphique du billard. C'est donc un composant essentiel de nos 2 projets et il est normal que nous y consacrions à nouveau un peu de temps sans rentrer dans le vif d'Android et de Kotlin.
Ces manipulations font appel aux mécanismes clés de l'orienté objet, que nous expliquerons plus loin avec les balles et les parois du billard. Prenez encore votre mal en patience.
Comme première manipulation glissez dans la fenêtre de code un élément SurfaceView
, que vous trouverez dans le répertoire widget
de la palette des composants graphiques. C'est en effet un des composants graphiques (widgets) qu'Android Studio met à votre disposition. Collez-le à même l'écran en dessous d'un bouton comme l'illustration ci-dessous vous l'indique.
À nouveau, Android Studio prend quelques initiatives, dont celle de produire le XML correspondant. N'hésitez pas à recourir à l'infer constraint
pour faciliter les choses et apportez des modifications à même le fichier XML, de manière à obtenir le code suivant :
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="45dp"
android:text="Mon Premier Bouton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.smallbillard.DrawingView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/vMain"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="104dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Les valeurs numériques présentes dans ce XML sont souvent des distances en pixels prises à partir de l'extrémité droite, gauche, ou supérieure de votre écran. Les plus passionnés d'entre vous pourront se ruer sur Internet pour comprendre leur signification précise et s'amuser à en modifier la valeur. Elles vous sont données ici à titre indicatif, mais rien ne vous empêche évidemment d'ajuster le positionnement relatif des composants apparaissant sur votre écran.
La présence des match_parent
et autres wrap_content
sert précisément à cela : ajuster vos composants par rapport à ceux sur lesquels vous les déposez délicatement (les parents).
Prenez garde aussi à ce surfaceView que vous avez transformé comme par enchantement en un com.example.smallbillard.DrawingView
. Remplacez évidemment « smallbillard » par le nom que vous avez donné à votre projet. Cette modification est effectuée afin de faire le lien avec le code Kotlin de votre application principale, que nous allons découvrir tout de suite. Le chemin qui mène au DrawingView
est exactement celui qui contient votre fichier MainActivity
.
Notez finalement son id
qui permettra d'y avoir accès dans ce même code source. Reproduisez donc au pixel près ce code XML.
Voici la nouvelle version de votre fichier MainActivity.kt
:
package com.example.smallbillard
import android.app.Activity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity: Activity() {
lateinit var drawingView: DrawingView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
drawingView = findViewById(R.id.vMain)
}
fun onClick(v: View) {
val x = 10
if (x >= 10)
Toast.makeText(this, "Je presse mon premier bouton",
Toast.LENGTH_LONG).show()
}
}
Android Studio vous propose souvent de taper « alt-enter
» de manière à ce qu'il prenne l'initiative pour importer les packages qui font défaut. Obéissez-lui car il a souvent raison. Il lit dans vos pensées.
Là encore, remarquez bien les modifications qu'il convient d'apporter à l'existant. Votre AppCompActivity
s'est transformée en une simple Activity
, dont hérite votre classe MainActivity
(nous ne tarderons pas à vous expliquer le concept d'héritage dans la suite – n'oubliez pas les alt-enter
).
Vous voyez également apparaître dans le code une variable appelée drawingView
de type DrawingView
. Elle est déclarée comme var
et lateinit
. Cette dernière précision nous informe que son initialisation ne se fait pas au moment de sa déclaration (comme est en droit de l'attendre Kotlin afin d'en inférer le type), mais plus bas dans le code. Et, de fait, nous initialisons bien cette variable dans la fonction onCreate
en allant la chercher dans le fichier XML.
C'est une particularité remarquable d'Android Studio que de permettre à tout moment cette synchronisation entre les parties graphiques de votre application (codées dans le fichier activity_main.xml
) et les instructions Kotlin qui en font usage. Ici l'instruction findViewById(R.id.vMain
) va, en effet, rechercher dans le répertoire ressource (toujours ce fameux R
) l'objet drawingView
dans le fichier XML qu'elle récupère grâce à son id
.
Malheureusement, les ajouts sont loin d'être terminés. En effet, à ce stade, le type DrawingView
est souligné de rouge car non reconnu dans votre code MainActivity
. Il nous reste donc à définir ce type, l'élément évidemment central de notre projet, car responsable de toute l'activité graphique de l'application, non pas ce à quoi les composants ressemblent mais ce qu'ils sont censés faire.
Pour cela, nous allons créer une nouvelle classe appelée DrawingView
et l'installer dans un nouveau fichier qui, tradition objet oblige, portera le même nom. La manipulation consiste donc, dans Android Studio, à se rendre dans le même répertoire que celui contenant le fichier MainActivity
.kt et à y créer un nouveau fichier/classe Kotlin appelé DrawingView
, dont le contenu devra être le suivant :
package com.example.smallbillard
import android.content.Context
import android.util.AttributeSet
import android.view.SurfaceView
class DrawingView constructor (context: Context, attributes: AttributeSet? = null, defStyleAttr: Int = 0): SurfaceView(context, attributes,defStyleAttr) {
}
Cette écriture est assez sophistiquée et ésotérique ; certains aspects en seront démystifiés par la suite. Bien qu'il nous reste encore à nous y plonger plus à fond, la démarche orientée objet consiste en la séparation d'un projet en ces acteurs essentiels que nous installons dans des classes.
À ce stade, nous nous contentons de 2 classes, la MainActivity
et la DrawingView
mais, bien vite, les balles et les parois requerront l'ajout des 2 classes correspondantes, ce qui fait que, à la toute fin de notre petit projet, 4 classes y seront présentes.
Ici, la classe MainActivity
s'avère pivot dans notre projet car elle est responsable du démarrage de l'application et de sa synchronisation avec les informations graphiques contenues dans le fichier XML. La classe DrawingView
, quant à elle, est responsable de l'animation graphique à proprement parler.
Autre aspect non négligeable, elle hérite de la classe SurfaceView
(présence des :SurfaceView
), déjà connue dans l'environnement Android Studio, et dont notre DrawingView
va récupérer un ensemble de fonctionnalités sans avoir à les reprogrammer.
Et l'on découvre ici un autre aspect bien précieux de l'orienté objet, l'héritage, qui permet (et nous y reviendrons) à une classe fille de bénéficier d'un ensemble de fonctionnalités déjà définies dans la classe mère. En orienté objet, les classes, soit dépendent les unes des autres, comme MainActivity
qui utilise les fonctionnalités de DrawingView
(la première réfère bien la deuxième parmi ses attributs), soit héritent les unes des autres, comme DrawingView
et SurfaceView
. On dira aussi plus simplement que MainActivity
dépend de DrawingView
et que cette dernière est un cas particulier de SurfaceView
.
Par exemple, petit avant-goût de ce qui va suivre, la classe Voiture
est une classe fille de la classe Moyen de transport
et dépend des classes Moteur
et Roues
. Vous assistez à une sous-classe de la classe Cours-En-Ligne-De-Programmation
(un objet hérité de la classe Cours-En-Ligne
) contenant des objets de la classe Ecran
dont l'intérêt ne se dément pas au fil de la lecture.
Exécutez votre projet. Rien de très existant ne devrait se produire sur votre écran si ce n'est ce que nous connaissons déjà : le fameux bouton. Mais au moins cela permet de vous rassurer que tout fonctionne comme prévu et qu'aucune erreur de compilation ou de synchronisation entre le xml et le Kotlin n'est à déplorer.