Créer LILI en React Native — Partie 2 — Créer une interface audio originale avec une barre de progression circulaire

Axel de Sainte Marie
6 min readOct 23, 2020

Cette série d’articles fait partie de la série “Les Tutos du Vendredi”, avec la spécificité d’exposer la travail réalisé sur l’application LILI dont la première version a été mise en ligne le 22 septembre 2020.

Le premier article — Créer LILI en React Native — Partie 1 — naviguer dans l’application— de cette série traitait de la navigation dans l’app et de la création d’un menu horizontal animé, pour naviguer dans un pager

LILI est une application qui propose des programmes individualisés Calme, Concentration et Confiance en soi, associant méditation et yoga, ateliers d’improvisation et podcasts de philosophie, pour aider chaque enfant à mieux gérer ses émotions, améliorer sa concentration et prendre confiance à l’oral.

LILI est disponible sur l’AppStore pour iOS et sur le Google Play Store pour Android.

Les différents types d’activités que proposent LILI — Médiation, Yoga, Réflexion autour des émotions, du regard des autres, etc. — sont disponibles sous forme de contenus audio. Nous souhaitions avoir un lecteur audio qui s’intègre proprement dans notre UI.

Les boutons d’actions de nos activités sont ronds, et nous voulions qu’il en soit de même pour le lecteur audio. Créer un bouton rond pour lancer la lecture et la mettre en pause n’est évidemment pas le défi qui s’est posé sur ce développement. Le vrai défi a été de créer une barre de progression circulaire autour du bouton, avec laquelle l’utilisateur doit pouvoir interagir pour avancer ou reculer dans sa lecture. Pour ajouter un peu de complexité, notre lecteur audio se situe dans une ScrollView verticale, qui elle même est incluse dans un ViewPager horizontal.

Notre lecteur audio dans une page de Yoga avec sa barre de progression circulaire

Pour LILI nous avons utilisé le lecteur audio React Native Track Player qui est le lecteur audio React Native le plus complet actuellement. Le but de cet article n’étant pas la mise en place d’un lecteur audio, je simulerai un lecture audio avec un timer.

Commençons par créer une interface simple dans laquelle nous pourrons simuler un player audio, avec une barre de progression qui nous permette de suivre l’avancée dans le temps, d’avancer et de reculer dans la lecture.

Pour cette première étape, nous utiliserons le React Native Slider de React Native Community pour créer notre barre de progression.

import React from ‘react’;
import { StyleSheet, View, Dimensions, Text, SafeAreaView, TouchableOpacity } from ‘react-native’;
import Slider from ‘@react-native-community/slider’;
export default class AudioPlayer extends React.Component {
constructor() {
super();
this.state = {
};
}
render() {
return (
<SafeAreaView style={style.container}>
<View style={style.player}>
<Text style={styles.time}>00:00</Text>
<TouchableOpacity style={styles.playerButton}>
<Text style={styles.playerButtonText}>Play</Text>
</TouchableOpacity>
<Slider
style={styles.slider}
minimumValue={0}
maximumValue={1}
minimumTrackTintColor="#123456"
maximumTrackTintColor="#6789AB" />
</View>
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
container: {
flex:1
},
player:{
flex: 1,
width: Dimensions.get('screen').width,
justifyContent: 'center',
alignItems: 'center'
},
time:{
paddingBottom:20,
fontSize:64,
color:'#123456'
},
playerButton: {
width: 200,
height: 200,
borderRadius: 100,
backgroundColor: '#123456',
justifyContent: 'center',
alignItems: 'center'
},
playerButtonText: {
color: '#FFFFFF',
fontWeight: 'bold',
fontSize: 24
},
slider: {
marginTop:20,
width:260,
height:40
}
});

À ce point du code nous avons uniquement l’interface. Nous allons maintenant ajouter le timer qui va simuler le lecteur audio, ainsi que les interactions liées à un lecteur audio :

  • l’action sur le bouton play / pause ;
  • l’avancement du slider en fonction du temps écoulé ;
  • les interactions de l’utilisateur sur le slider pour avancer ou reculer dans le temps.
import React from ‘react’;
import { StyleSheet, View, Dimensions, Text, SafeAreaView, TouchableOpacity } from ‘react-native’;
import Slider from ‘@react-native-community/slider’;
export default class AudioPlayer extends React.Component {
constructor() {
super();
this.state = {
playerIsPlaying: false, //(1)
duration: 180, //(2)
progress: 0 //(3)

};
this.tooglePlayer = this.tooglePlayer.bind(this);
this.launchTimer = this.launchTimer.bind(this);
this.onValueChange = this.onValueChange.bind(this);

}
render() {
const { playerIsPlaying, progress } = this.state;
const buttonTitle = playerIsPlaying === true ? ‘Pause’ : ‘Play’;
let progressInMinutes = parseInt(progress/60); //(4)
if (progressInMinutes < 10) {
progressInMinutes = '0'+progressInMinutes;
}
let progressInSeconds = progress - progressInMinutes*60;
if (progressInSeconds < 10) {
progressInSeconds = '0'+progressInSeconds;
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.player}>
<Text style={styles.time}>{progressInMinutes+":"+progressInSeconds}</Text>
<TouchableOpacity
onPress={this.tooglePlayer}
style={styles.playerButton}>
<Text style={styles.playerButtonText}>{buttonTitle}</Text>
</TouchableOpacity>
<Slider
ref={(ref) => this.slider = ref}
style={styles.slider}
minimumValue={0}
maximumValue={1}
minimumTrackTintColor=”#123456"
maximumTrackTintColor=”#6789AB”
onValueChange={this.onValueChange} />
</View>
</SafeAreaView>
)
}
tooglePlayer() { //(5)
this.setState({ playerIsPlaying: !this.state.playerIsPlaying });
this.launchTimer();
}
launchTimer() { //(6)
setTimeout(() => {
if (this.state.playerIsPlaying === true) {
const { progress, duration } = this.state;
if (progress < duration) {
const sliderValue = (progress + 1) / duration;
this.slider.setNativeProps({ value: sliderValue });
this.setState({ progress: progress + 1 });
this.launchTimer();
}
}
}, 1000);
}
onValueChange(newValue) { //(7)
const { duration } = this.state;
const newProgress = parseInt(newValue * duration);
this.setState({ progress: newProgress });
}

}
[...]

Beaucoup de code ont été ajouté ici. Prenons le temps de nous arrêter dessus.

(1) : playerIsPlaying est un booléen qui nous permet de savoir si le lecteur audio est en train de jouer ou en pause.

(2) : duration est une variable nous indiquant la durée de l’audio en cours de lecture (en secondes). Pour la simulation, nous avons indiqué un morceau de 3 minutes.

(3) : progress est une variable dans laquelle nous stockerons le temps écoulé.

(4) : ici nous transformons le temps écoulé exprimé en seconde, en une chaîne de caractères représentant le temps sous le format mm:ss comme il est fréquent de le faire sur un lecteur audio.

(5) : tooglePlayer est la méthode appelée par le onPress du bouton d’action du player, qui lance ou met en pause le player audio.

(6) : launchTimer est la méthode qui simule le lecteur audio en lançant un timer qui bouclera tant que le lecteur ne sera pas en pause ou que le temps écoulé (progress) n’aura pas atteint la durée totale du morceau (duration). C’est dans cette méthode que nous mettons à jour également la valeur du slider pour qu’il représente correctement le temps écoulé.

(7) : onValueChange nous permet d’impacter l’interaction de l’utilisateur sur le lecteur audio.

Nous obtenons une bonne simulation d’un lecteur audio

Nous allons maintenant nous attaquer à modifier la barre de progression pour avoir une barre de progression circulaire (nous verrons dans un prochain article la problématique des ScrollView / ViewPager).

Après quelques recherches, je n’ai pas trouvé de bibliothèque toute faite pour avoir une barre de progression circulaire. Mais j’ai trouvé le code de Steve Liles qui répondait à une question sur Stackoverflow. Dans un dossier utils, je vais créer un nouveau fichier CircularSlider.js dans lequel je vais copier le code de Steve Liles. Pour que son code soit fonctionnel, il faut auparavant installer la bibliotèque react-native-svg.

Nous allons maintenant ajouter notre CircularSlider autour de notre bouton d’action du lecteur audio, tout en le positionnant “en dessous” — dans la hierarchie des vues — pour que le bouton fonctionne toujours.

[…]
import CircularSlider from ‘../utils/CircularSlider’; //(1)
export default class AudioPlayer extends React.Component {
[...]
render() {
[...]
const progressValueForCircularSlider = (progress / duration)*360; //(2)
return (
<SafeAreaView style={styles.container}>
<View style={styles.player}>
<Text style={styles.time}>{progressInMinutes + ":" + progressInSecondes}</Text>
<View style={{justifyContent:'center', alignItems:'center'}}>
<View style={styles.circularSlider}>
<CircularSlider
width={280}
height={280}
value={progressValueForCircularSlider}
meterColor="#123456"
textColor="#123456"
displayText={false}
onValueChange={this.onValueChange} />
</View>
</View>

</View>
</SafeAreaView>
)
}
[...]
onValueChange(newValue) {
const { duration } = this.state;
const newProgress = parseInt(newValue * duration)/360; //(3)
this.setState({ progress: newProgress });
}
}
const styles = StyleSheet.create({
[...]
circularSlider: {
position: 'absolute', //(4)
width: 280,
height: 280
}

}]

(1) : On importe le composant CircularSlider développé par Steve Liles

(2) : Dans le CircularSlider, la position de la progression est exprimée en degrés, donc sur une base 360.

(3) : Quand l’utilisateur déplace le curseur de notre barre de progression circulaire, il fait varier la valeur de celui-ci entre 0 et 360. Pour retrouver notre valeur par rapport à la durée total de notre audio, nous devons donc la diviser par 360.

(4) : Nous utilisons l’attribut position et sa valeur absolute pour positionner notre barre de progression circulaire en dessous de notre bouton.

Une fois ce code en place nous avons remplis notre premier objectif : avoir une barre de progression circulaire.

--

--

Axel de Sainte Marie

Show Runner de projets mobiles. Développeur React Native et passionné par les challenges du monde mobile.