1. Avant de commencer
Dans cet atelier de programmation, vous allez apprendre à ajouter une animation simple à votre application Android. Les animations permettent de rendre votre application plus interactive, plus intéressante et plus facile à interpréter. Animer des changements spécifiques sur un écran rempli d'informations peut aider l'utilisateur à identifier ce qui change.
Vous pouvez utiliser de nombreux types d'animations dans l'interface utilisateur d'une application. Les éléments peuvent apparaître et disparaître en fondu, se déplacer sur l'écran ou en dehors, ou être transformés de façon intéressante. Cela rend l'interface utilisateur de l'application plus lisible et plus facile à utiliser.
Les animations permettent également d'améliorer l'aspect général de l'application, pour la rendre plus élégante tout en simplifiant son utilisation.
Conditions préalables
- Vous maîtrisez Kotlin, y compris les fonctions, et composables sans état.
- Vous disposez de connaissances de base en création de mises en page dans Jetpack Compose.
- Vous disposez de connaissances de base en création de listes dans Jetpack Compose.
- Vous disposez de connaissances de base en Material Design.
Points abordés
- Créer une animation de rétroaction simple avec Jetpack Compose
Objectifs de l'atelier
- Vous allez utiliser l'application Woof (créée dans l'atelier Thématisation Material avec Jetpack Compose) pour ajouter une animation simple confirmant l'action de l'utilisateur.
Ce dont vous avez besoin
- La dernière version stable d'Android Studio
- Une connexion Internet pour télécharger le code de démarrage
2. Présentation de l'application
Dans l'atelier de programmation Thématisation Material avec Jetpack Compose, vous avez utilisé Material Design pour créer l'application Woof qui affiche une liste de chiens et les informations correspondantes.
Dans cet atelier de programmation, vous allez ajouter une animation à l'application Woof. Vous ajouterez des informations sur le passe-temps, qui s'afficheront lorsque l'élément de liste est développé. Vous ajouterez également une animation de rétroaction pour animer l'élément de liste qui est développé.
Télécharger le code de démarrage
Pour commencer, téléchargez le code de démarrage :
Vous pouvez également cloner le dépôt GitHub du code :
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git $ cd basic-android-kotlin-compose-training-woof $ git checkout material
Vous pouvez parcourir le code dans le dépôt GitHub Woof app
.
3. Ajouter une icône Développer
Dans cette section, vous allez ajouter les icônes Développer et Réduire à votre application.
Icônes
Les icônes sont des symboles qui aident les utilisateurs à comprendre l'interface utilisateur en communiquant visuellement la fonction souhaitée. Elles s'inspirent souvent d'objets du monde physique que l'utilisateur est supposé connaître. La conception des icônes réduit souvent le niveau de détail au minimum requis pour être reconnues par l'utilisateur. Par exemple, dans le monde physique, un crayon sert à écrire. Une icône de crayon symbolise généralement la création ou la modification d'un élément.
Photo par Angelina Litvin, publiée sur Unsplash |
Material Design propose un certain nombre d'icônes classées dans des catégories courantes répondant à la plupart des besoins.
Ajouter une dépendance Gradle
Ajoutez la dépendance de bibliothèque material-icons-extended
à votre projet. Vous utiliserez les icônes Icons.Filled.ExpandLess
et Icons.Filled.ExpandMore
de cette bibliothèque.
- Dans le volet Project (Projet), ouvrez Gradle Scripts > build.gradle.kts (Module :app) (Scripts Gradle > build.gradle.kts (Module :app)).
- Faites défiler la page jusqu'à la fin du fichier
build.gradle.kts (Module :app)
. Dans le blocdependencies{}
, ajoutez la ligne suivante :
implementation("androidx.compose.material:material-icons-extended")
Ajouter le composable d'icône
Ajoutez une fonction permettant d'afficher l'icône Développer à partir de la bibliothèque d'icônes Material et de l'utiliser comme bouton.
- Dans
MainActivity.kt
, après la fonctionDogItem()
, créez une fonction composable nomméeDogItemButton()
. - Transmettez
Boolean
pour l'état développé, une expression lambda pour le gestionnaire onClick du bouton et unModifier
facultatif, comme suit :
@Composable
private fun DogItemButton(
expanded: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
}
- Dans la fonction
DogItemButton()
, ajoutez un composableIconButton()
qui accepte un paramètre nomméonClick
, un lambda utilisant la syntaxe lambda de fin, appelé lorsque l'utilisateur appuie sur cette icône, et unmodifier
facultatif. Définissez les valeursIconButton's onClick
etmodifier value parameters
sur celles transmises àDogItemButton
.
@Composable
private fun DogItemButton(
expanded: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
){
IconButton(
onClick = onClick,
modifier = modifier
) {
}
}
- Dans le bloc lambda
IconButton()
, ajoutez un composableIcon
et définissezimageVector value-parameter
surIcons.Filled.ExpandMore
. C'est ce qui s'affichera à la fin de l'élément de liste . Android Studio affiche un avertissement pour les paramètres composablesIcon()
que vous corrigerez à l'étape suivante.
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.Icons
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
IconButton(
onClick = onClick,
modifier = modifier
) {
Icon(
imageVector = Icons.Filled.ExpandMore
)
}
- Ajoutez le paramètre de valeur
tint
et définissez la couleur de l'icône surMaterialTheme.colorScheme.secondary
. Ajoutez le paramètre nommécontentDescription
et définissez-le sur la ressource de chaîneR.string.expand_button_content_description
.
IconButton(
onClick = onClick,
modifier = modifier
){
Icon(
imageVector = Icons.Filled.ExpandMore,
contentDescription = stringResource(R.string.expand_button_content_description),
tint = MaterialTheme.colorScheme.secondary
)
}
Afficher l'icône
Affichez le composable DogItemButton()
en l'ajoutant à la mise en page.
- Au début de
DogItem()
, ajoutez unevar
pour enregistrer l'état développé de l'élément de liste. Définissez la valeur initiale surfalse
.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
var expanded by remember { mutableStateOf(false) }
- Affichez le bouton d'icône dans l'élément de liste. Dans le composable
DogItem()
, à la fin du blocRow
, après l'appel àDogInformation()
, ajoutezDogItemButton()
. Transmettez l'étatexpanded
et un lambda vide pour le rappel. Vous définirez l'actiononClick
ultérieurement.
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
DogItemButton(
expanded = expanded,
onClick = { /*TODO*/ }
)
}
- Consultez
WoofPreview()
dans le volet Design (Conception).
Notez que le bouton "Développer" n'est pas aligné à la fin des éléments de liste. Vous résoudrez ce problème à l'étape suivante.
Aligner les boutons "Développer"
Pour aligner les boutons de développement à la fin des éléments de liste, vous devez ajouter un espace vide dans la mise en page, avec l'attribut Modifier.weight()
.
Dans l'application Woof, chaque ligne de la liste contient une image et des informations sur le chien, ainsi qu'un bouton "Développer". Pour aligner correctement les icônes du bouton, vous devez ajouter un composable Spacer
avant le bouton "Développer" et lui attribuer une pondération de 1f
. Comme cet espace vide est le seul élément enfant pondéré de la ligne, il remplira l'espace restant après avoir mesuré la longueur des autres éléments enfants non pondérés.
Ajouter l'espace à l'élément de liste
- Dans
DogItem()
, entreDogInformation()
etDogItemButton()
, ajoutez unSpacer
. Transmettez unModifier
avecweight(1f)
.Modifier.weight()
permet à l'espace vide de remplir l'espace disponible restant dans la ligne.
import androidx.compose.foundation.layout.Spacer
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(modifier = Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { /*TODO*/ }
)
}
- Consultez
WoofPreview()
dans le volet Design (Conception). Notez que le bouton "Développer" est désormais aligné au bout de l'élément de liste.
4. Ajouter un composable pour afficher le passe-temps
Dans cette tâche, vous allez ajouter des composables Text
afin d'afficher des informations sur le passe-temps des chiens.
- Créez une fonction composable appelée
DogHobby()
, qui récupère l'ID de ressource de chaîne du passe-temps d'un chien ainsi qu'unModifier
facultatif.
@Composable
fun DogHobby(
@StringRes dogHobby: Int,
modifier: Modifier = Modifier
) {
}
- Dans la fonction
DogHobby()
, créez uneColumn
et transmettez le modificateur transmis àDogHobby()
.
@Composable
fun DogHobby(
@StringRes dogHobby: Int,
modifier: Modifier = Modifier
){
Column(
modifier = modifier
) {
}
}
- Dans le bloc
Column
, ajoutez deux composablesText
: un pour afficher le texte About (Passe-temps) au-dessus des informations sur le passe-temps, et l'autre pour afficher les informations en question.
Définissez le text
du premier composable sur about
à partir du fichier strings.xml et définissez le style
sur labelSmall
. Définissez le text
du second composable sur dogHobby
, qui est transmis, et le style
sur bodyLarge
.
Column(
modifier = modifier
) {
Text(
text = stringResource(R.string.about),
style = MaterialTheme.typography.labelSmall
)
Text(
text = stringResource(dogHobby),
style = MaterialTheme.typography.bodyLarge
)
}
- Dans
DogItem()
, le composableDogHobby()
ira en dessous de laRow
contenant les élémentsDogIcon()
,DogInformation()
,Spacer()
etDogItemButton()
. Pour ce faire, encapsulez laRow
avec uneColumn
afin que le passe-temps puisse être ajouté sous laRow
.
Column() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(modifier = Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { /*TODO*/ }
)
}
}
- Ajoutez
DogHobby()
après laRow
en tant que deuxième enfant de laColumn
. Transmettezdog.hobbies
, qui contient le passe-temps unique du chien transmis, et unmodifier
avec la marge intérieure du composableDogHobby()
.
Column() {
Row() {
...
}
DogHobby(
dog.hobbies,
modifier = Modifier.padding(
start = dimensionResource(R.dimen.padding_medium),
top = dimensionResource(R.dimen.padding_small),
end = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_medium)
)
)
}
Une fois terminée, la fonction DogItem()
doit se présenter comme suit :
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
var expanded by remember { mutableStateOf(false) }
Card(
modifier = modifier
) {
Column() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { /*TODO*/ },
)
}
DogHobby(
dog.hobbies,
modifier = Modifier.padding(
start = dimensionResource(R.dimen.padding_medium),
top = dimensionResource(R.dimen.padding_small),
end = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_medium)
)
)
}
}
}
- Consultez
WoofPreview()
dans le volet Design (Conception). Le passe-temps des chiens s'affiche.
5. Afficher ou masquer le passe-temps lorsque l'utilisateur appuie sur le bouton
Votre application dispose d'un bouton Développer pour chaque élément de liste, mais ce bouton n'a aucun effet pour l'instant. Dans cette section, vous ajouterez la possibilité d'afficher ou masquer les informations concernant le passe-temps lorsque l'utilisateur appuie sur le bouton "Développer".
- Accédez à la fonction composable
DogItem()
. Dans l'appel de fonctionDogItemButton()
, définissez l'expression lambdaonClick()
et appliquez la valeur d'état booléennetrue
pourexpanded
lorsque l'utilisateur appuie sur le bouton, et rétablissezfalse
lorsque l'utilisateur appuie de nouveau.
DogItemButton(
expanded = expanded,
onClick = { expanded = !expanded }
)
- Dans la fonction
DogItem()
, encapsulez l'appel de fonctionDogHobby()
avec une vérificationif
sur la valeur booléenneexpanded
.
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
var expanded by remember { mutableStateOf(false) }
Card(
...
) {
Column(
...
) {
Row(
...
) {
...
}
if (expanded) {
DogHobby(
dog.hobbies, modifier = Modifier.padding(
start = dimensionResource(R.dimen.padding_medium),
top = dimensionResource(R.dimen.padding_small),
end = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_medium)
)
)
}
}
}
}
Désormais, les informations sur le passe-temps du chien ne s'affichent que si la valeur de expanded
correspond à true
.
- L'aperçu vous permet de voir à quoi ressemble l'interface utilisateur et d'interagir avec. Pour interagir avec l'aperçu de l'interface utilisateur, pointez sur le texte WoofPreview dans le volet Design (Conception), puis cliquez sur le bouton Mode interactif en haut à droite de ce même volet. L'aperçu passe en mode interactif.
- Interagissez avec l'aperçu en cliquant sur le bouton "Développer". Notez que les informations du passe-temps du chien sont affichées ou masquées lorsque vous cliquez sur le bouton.
Notez que l'icône du bouton "Développer" reste la même lorsque l'élément de liste est développé. Pour offrir une meilleure expérience utilisateur, vous devez modifier l'icône afin que ExpandMore
affiche la flèche vers le bas , et ExpandLess
affiche la flèche vers le haut .
- Dans la fonction
DogItemButton()
, ajoutez une instructionif
qui met à jour la valeurimageVector
en fonction de l'étatexpanded
, comme suit :
import androidx.compose.material.icons.filled.ExpandLess
@Composable
private fun DogItemButton(
...
) {
IconButton(onClick = onClick) {
Icon(
imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
...
)
}
}
Notez comment vous avez écrit if-else
dans l'extrait de code précédent.
if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore
Cela revient à utiliser les accolades { } dans le code suivant :
if (expanded) {
`Icons.Filled.ExpandLess`
} else {
`Icons.Filled.ExpandMore`
}
Les accolades sont facultatives si l'instruction if
-else
ne comporte qu'une seule ligne de code.
- Exécutez l'application sur un appareil ou un émulateur, ou passez à nouveau en mode interactif dans l'aperçu. Notez que l'icône alterne entre
ExpandMore
etExpandLess
.
Bravo ! Vous avez mis à jour votre icône.
Avez-vous noté le changement abrupt de hauteur lorsque vous développez un élément de la liste ? Dans une application, ce type de changements brusques ne reflète pas une finition soignée. Pour résoudre ce problème, vous allez ajouter une animation à votre application.
6. Ajouter une animation
Les animations peuvent ajouter des repères visuels pour informer les utilisateurs de ce qui se passe dans votre application. Ils sont particulièrement utiles lorsque l'interface utilisateur change d'état (par exemple lorsque de nouveaux contenus se chargent ou que de nouvelles actions deviennent disponibles). Les animations permettent également d'améliorer l'aspect général de l'application.
Dans cette section, vous ajouterez une animation de rétroaction au changement de hauteur de l'élément de liste.
Animation de rétroaction
Une animation de rétroaction s'inspire des lois de la physique, et plus spécifiquement de la force de tension. Avec une animation de rétroaction, la valeur et la vitesse du mouvement sont calculées en fonction de la tension appliquée.
Par exemple, si vous faites glisser une icône d'application sur l'écran, puis la relâchez en relevant votre doigt, l'icône revient à son emplacement d'origine sous l'effet d'une force invisible.
L'animation suivante illustre l'effet de rétroaction. Lorsque l'icône est relâchée, elle revient à sa place, comme poussée par un ressort.
Effet de rétroaction
La force de tension ("effet ressort") est déterminée par les deux propriétés suivantes :
- Taux d'amortissement : la résistance à la déformation, qui limite les rebonds du ressort.
- Niveau de raideur : la résistance à l'étirement, qui définit la vitesse du ressort en fin de course.
Vous trouverez ci-dessous quelques exemples d'animations avec différents paramètres d'amortissement et de raideur.
Rebond élevé | Aucun rebond |
Raideur élevée | Raideur très faible |
Examinez l'appel de fonction DogHobby()
dans la fonction composable DogItem()
. Les informations concernant le passe-temps du chien sont incluses dans la composition, en fonction de la valeur booléenne expanded
. La hauteur de l'élément de liste change, selon que les informations de passe-temps sont affichées ou masquées. Pour le moment, la transition est brusque. Dans cette section, vous allez utiliser le modificateur animateContentSize
pour ajouter une transition plus fluide entre les états développé et non développé.
// No need to copy over
@Composable
fun DogItem(...) {
...
if (expanded) {
DogHobby(
dog.hobbies,
modifier = Modifier.padding(
start = dimensionResource(R.dimen.padding_medium),
top = dimensionResource(R.dimen.padding_small),
end = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_medium)
)
)
}
}
- Accédez à
MainActivity.kt
. DansDogItem()
, ajoutez un paramètremodifier
à la mise en pageColumn
.
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
...
Card(
...
) {
Column(
modifier = Modifier
){
...
}
}
}
- Enchaînez ce modificateur au modificateur
animateContentSize
pour animer le changement de dimensions (hauteur de l'élément de liste).
import androidx.compose.animation.animateContentSize
Column(
modifier = Modifier
.animateContentSize()
)
L'implémentation actuelle anime la hauteur de l'élément de liste dans votre application. Cependant, l'animation est si subtile qu'il est difficile de la discerner lorsque vous exécutez l'application. Pour résoudre ce problème, utilisez le paramètre facultatif animationSpec
, qui permet de personnaliser l'animation.
- Pour Woof, l'animation ne comporte aucun rebond. Ajoutez le paramètre
animationSpec
à l'appel de fonctionanimateContentSize()
. Définissez-le sur une animation de rétroaction avecDampingRatioNoBouncy
pour qu'il n'y ait pas de rebond et définissez un paramètreStiffnessMedium
afin de rendre le rebond un peu plus rigide.
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
Column(
modifier = Modifier
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMedium
)
)
)
- Consultez
WoofPreview()
dans le volet Design (Conception) et utilisez le mode interactif, ou exécutez votre application sur un émulateur ou un appareil pour tester votre animation de rétroaction.
Bravo ! Profitez de votre superbe application avec des animations.
7. Tester d'autres animations (facultatif)
animate*AsState
Les fonctions animate*AsState()
comptent parmi les API d'animation les plus simples dans Compose. Elles permettent d'animer une seule valeur. Vous fournissez uniquement la valeur finale (ou la valeur cible), et l'API lance une animation menant de la valeur actuelle à la valeur spécifiée.
Compose fournit des fonctions animate*AsState()
pour Float
, Color
, Dp
, Size
, Offset
et Int
, entre autres. Vous pouvez facilement prendre en charge d'autres types de données en utilisant animateValueAsState()
, qui utilise un type générique.
Essayez d'utiliser la fonction animateColorAsState()
pour modifier la couleur lorsqu'un élément de liste est développé.
- Dans
DogItem()
, déclarez une couleur et déléguez son initialisation à la fonctionanimateColorAsState()
.
import androidx.compose.animation.animateColorAsState
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
var expanded by remember { mutableStateOf(false) }
val color by animateColorAsState()
...
}
- Définissez le paramètre nommé
targetValue
en fonction de la valeur booléenneexpanded
. Si l'élément de liste est développé, définissez sa couleur surtertiaryContainer
. Sinon, définissez-la surprimaryContainer
.
import androidx.compose.animation.animateColorAsState
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
var expanded by remember { mutableStateOf(false) }
val color by animateColorAsState(
targetValue = if (expanded) MaterialTheme.colorScheme.tertiaryContainer
else MaterialTheme.colorScheme.primaryContainer,
)
...
}
- Définissez
color
comme modificateur d'arrière-plan surColumn
.
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
...
Card(
...
) {
Column(
modifier = Modifier
.animateContentSize(
...
)
)
.background(color = color)
) {...}
}
- Découvrez comment la couleur change lorsque l'élément de liste est développé. Les éléments de liste non développés sont de couleur
primaryContainer
, tandis que les éléments de liste développés sont de couleurtertiaryContainer
.
8. Télécharger le code de solution
Pour télécharger le code de l'atelier de programmation terminé, utilisez la commande Git suivante :
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git
Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.
Si vous souhaitez voir le code de solution, affichez-le sur GitHub.
9. Conclusion
Félicitations ! Vous avez ajouté un bouton pour afficher et masquer les informations concernant les chiens. Vous avez amélioré l'expérience utilisateur grâce aux animations de rétroaction. Vous avez également appris à utiliser le mode interactif dans le volet Design (Conception).
Vous pouvez également explorer un autre type d'animation Jetpack Compose. N'oubliez pas de partager le fruit de vos efforts sur les réseaux sociaux avec le hashtag #AndroidBasics.
En savoir plus
- Animation Jetpack Compose
- Atelier de programmation : Animer des éléments dans Jetpack Compose
- Vidéo : Repenser les animations (en anglais)
- Vidéo : Les animations dans Jetpack Compose (en anglais)