Mise en place d’un onboarding en React Native avec une simple ScrollView

Axel de Sainte Marie
8 min readFeb 7, 2020

Dans le cadre d’un développement, un de mes clients a souhaité ajouter un onboarding au premier lancement de l’app pour mettre en avant les fonctionnalités de celle-ci, l’inscription et la connexion à un compte utilisateur.

Je profite donc de cette occasion pour montrer comment réaliser un onboarding en React Native en ayant recours uniquement à des composants de React Native, sans avoir à importer le moindre package externe.

Ce tutoriel se déroulera en 2 phases. Dans cet article je traiterai de la création d’un onboarding simple à l’aide d’une ScrollView. Dans un second article à venir, j’y ajouterai un peu de dynamisme grâce aux animations et au composant Animated.

Mettre en place l’onboarding avec une ScrollView

Commençons par créer un nouveau composant que nous nommerons simplement ‘Onboarding’ dans le fichier onboarding.js

import React from ‘react’;
import { StyleSheet, Text, View, SafeAreaView } from ‘react-native’;
export default class Onboarding extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<SafeAreaView style={styles.container}>
<View>
<Text>Créons ici un onboarding</Text>
</View>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: ‘#fff’,
alignItems: 'center',
justifyContent: 'center'
}
});
Création de notre page onboarding.js

Dans notre logique, nous souhaitons avoir un onboardingen 3 étapes. Nous allons donc mettre en place une ScrollViewhorizontale, contenant 3 vues.

Ajoutons le composant ScrollViewinclus dans React Native

import { StyleSheet, Text, View, ScrollView } from ‘react-native’;

Modifions maintenant la méthode renderpour y inclure une ScrollViewhorizontale, contenant trois vues couvrant chacune l’écran.

render() {
return (
<SafeAreaView style={styles.container}>
<View>
<ScrollView
style={styles.onboardingScrollView}
horizontal={true}
>
<View style={styles.onboardingView}>
<Text>Vue 1</Text>
</View>
<View style={styles.onboardingView}>
<Text>Vue 2</Text>
</View>
<View style={styles.onboardingView}>
<Text>Vue 3</Text>
</View>
</ScrollView>
</View>
</SafeAreaView>
);
}
[...]
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
},
onboardingScrollView: {
flex: 1
},
onboardingView: {
flex: 1,
width:Dimensions.get('screen').width,
justifyContent:'center',
alignItems:'center'
}
});

Nous avons maintenant une page permettant de naviguer entre les 3 vues. Pour retrouver la logique fonctionnelle d’un onboarding, il faut qu’à chaque swipede l’utilisateur la ScrollViews’arrête automatiquement sur la vue suivante ou précédente. Pour cela il suffit d’indiquer à la ScrollViewqu’elle est paginée avec l’option pagingEnabledet de lui donner une largeur pour qu'elle connaisse la largeur d'une page.

Également nous ajouterons plus tard des indicateurs de position pour savoir dans quel écran nous nous situons. Nous allons dors et déjà supprimer l’indicateur horizontale par défaut, en changeant la valeur de showsHorizontalScrollIndicator.

<ScrollView
style={styles.onboardingScrollView}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
>
[...]
onboardingScrollView: {
flex: 1,
width:Dimensions.get('screen').width
}
ScrollView sans pagination
ScrollView avec pagination

Maintenant que nous avons la base de notre onboarding, nous allons travailler sur ce qu’il contient. Il va y avoir des contenus propres à chaque vue, et du contenu propre à l’onboarding(indicateurs de page, boutons, etc.), qui doit donc rester fixe même quand l’utilisateur swipe.

Nous allons commencer par créer le contenu commun, à savoir un indicateur de page, un bouton d’inscription, un bouton de connexion ainsi qu’un bouton ‘passer’, permettant à l’utilisateur de fermer l’onboardingpour rentrer dans l’app.

Le bouton ‘passer’ sera positionné en permanence en haut à droit de l’écran. Le reste sera centré en bas de l’écran.

Nous allons donc positionner ces éléments en dehors de la ScrollView, et les positionner dans l’écran grâce à l’attribut positionned:’absolute’.

Pour ajouter des boutons nous allons utiliser le composant TouchableOpacityfournit avec React Native. (J’en profiterai pour changer la couleur de fond de notre onboardingen bleu)

import { StyleSheet, Text, View, ScrollView, Dimensions, SafeAreaView, TouchableOpacity } from ‘react-native’;
[...]
render() {
return (
<SafeAreaView style={styles.container}>
<View>
<ScrollView
style={styles.onboardingScrollView}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
>
[...]
</ScrollView>
<TouchableOpacity
onPress={this.closeTheOnboarding}
style={styles.closeButton}
>
<Text>Passer</Text>
</TouchableOpacity>
<View style={styles.bottomOptions}>
<View>
<Text>Page #</Text>
</View>
<TouchableOpacity
onPress={this.signUp}
style={styles.signUpButton}
>
<Text style={styles.signUpButtonText}>
S'INSCRIRE GRATUITEMENT
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.signIn}
style={styles.signInButton}
>
<Text style={styles.signInButtonText}>
SE CONNECTER
</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
}
closeTheOnboarding = () => {
//TODO : Mettre la méthode de fermeture de l'onboarding
}
signUp = () => {
//TODO : Mettre la méthode d'ouverture de la page d'inscription
}
signIn = () => {
//TODO : Mettre la méthode d'ouverture de la page de connexion
}
[...]
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#6789a0',
alignItems: 'center',
justifyContent: 'center'
},
[...]
closeButton: {
position:'absolute',
top:10,
right:20
},
bottomOptions: {
position: 'absolute',
bottom: 0,
left: 0,
width: Dimensions.get('screen').width,
alignItems:'center'
},
signUpButton: {
width:200,
height:40,
backgroundColor:'#fff',
alignItems:'center',
justifyContent:'center',
marginVertical:10
},
signUpButtonText: {
textAlign:'center',
paddingHorizontal:20,
color:'#404040'
},
signInButton: {
width:200,
height:40,
alignItems:'center',
justifyContent:'center'
},
signInButtonText: {
textAlign:'center',
paddingHorizontal:20,
color:'#fff'
},
});
Nous pouvons voir le résultat avec l’onboarding toujours actif et les éléments fixes restant en place

Nous allons maintenant travailler sur les indicateurs de position. Ici nous avons affiché “Page #”, mais le but est bien d’avoir 3 cercles à la suite représentant les 3 pages, et remplir celui présentant la page en cours de consultation.

Nous allons d’abord mettre en place la méthode permettant de savoir quelle est la page affichée à l’écran, et nous changerons le # par la valeur de la page (1, 2 ou 3).

Ajoutons une variable à notre objet statequi indiquera la position de l’écran. Par défaut elle prendra la valeur 1. Et nous allons afficher la valeur de cette nouvelle variable à la place du #. Ce qui aura pour effet d’afficher 1 à la place de #.

constructor(props) {
super(props);
this.state = {
pagePosition:1
}
}
[...]
render() {
return (
<SafeAreaView style={styles.container}>
<View>
[...]
<View style={styles.bottomOptions}>
<View>
<Text>
Page {this.state.pagePosition}
</Text>
</View>
[...]
</View>

Nous allons maintenant modifier notre ScrollViewpour qu’à chaque scrollde celle-ci, la valeur de notre pagePositionse mette à jour.

Pour cela nous allons utiliser l’option onScrollde la ScrollViewqui va appeler la méthode onScrollHandlerà chaque scroll. Cette méthode va recevoir la donnée eventqui va nous contient l’abscisse xde la position de la ScrollViewvia event.nativeEvent.contentOffset.x. Étant donné que nous connaissons la largeur d’une page — à savoir Dimensions.get(‘screen’).width — il va nous être aisé de retrouver le numéro de la page en cours.

render() {
return (
<SafeAreaView style={styles.container}>
<View>
<ScrollView
style={styles.onboardingScrollView}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onScroll={this.onScrollHandler}
>
[...]
</ScrollView>
</View>
</SafeAreaView>
);
}
[...]
onScrollHandler = (event) => {
let pageNumber = 1;
const scrollViewAbscissa = event.nativeEvent.contentOffset.x;
const pageWidth = Dimensions.get(‘window’).width;

//On va voir si le reste de la division de l'abscisse par rapport à la largeur de l'écran est égale à 0. Si oui, le résultat de la division, nous donnera la page
const remain = scrollViewAbscissa % pageWidth;
if (remain === 0) {
pageNumber = parseInt(scrollViewAbscissa / pageWidth) + 1;
//Si le numéro de page ainsi calculé est différent de celui en cours, on le met à jour
if (this.state.pageNumber !== pageNumber) {
this.setState({ pageNumber: pageNumber });
}
}
}

Une fois cette nouvelle méthode en place, vous pouvez swiperentre les vues, et vous verrez le numéro de la page changer.

Il nous reste plus qu’à afficher les fameux 3 cercles, permettant d’indiquer la position.

Construisons d’abord un cercle avec une vue et les attributs de style borderRadius, borderWidthet borderColor.

<View style={styles.positionIndicatorCircle} />[..]positionIndicatorCircle: {
width:20,
height:20,
borderRadius:10,
borderWidth:2,
borderColor:’#fff’
}

Nous allons maintenant en construire 3 que nous mettrons en ligne dans une vue dédiée qui remplacera notre indicateur de page.

Nous remplaçons donc le code suivant :

<View>
<Text>
Page {this.state.pagePosition}
</Text>
</View>

Par le code ci-dessous :

<View style={styles.positionIndicatorsRow}>
<View style={styles.positionIndicatorCircle} />
<View style={styles.positionIndicatorCircle} />
<View style={styles.positionIndicatorCircle} />
</View>

Sans oublier d’ajouter les styles correspondants :

positionIndicatorsRow: {
flexDirection:’row’,
justifyContent:’space-between’,
width:100,
marginVertical:10
},
positionIndicatorCircle: {
width:20,
height:20,
borderRadius:10,
borderWidth:2,
borderColor:’#fff’
}
Mise en place des indicateurs de position

Enfin il ne nous reste plus qu’à changer la couleur de fond du cercle correspondant à la position de l’onboarding.

Nous allons créer un tableau à 3 entrées, contenant les couleurs de fond de nos trois cercles, et qui sera initialisé avec la valeur ‘transparent’ pour les 3 cercles.

let circleBackgroundColor = ['transparent', 'transparent', 'transparent'];

Ensuite nous modifions l’entrée correspondant à la page en cours pour une couleur de fond blanche.

circleBackgroundColor[this.state.pagePosition-1] = '#fff';

Enfin nous modifions les styles de nos vues pour qu’elles acceptent 2 styles : le style par défaut et la couleurs de fond propre à chacune.

<View style={styles.positionIndicatorsRow}>
<View style={[styles.positionIndicatorCircle, {backgroundColor:circleBackgroundColor[0]}]} />
<View style={[styles.positionIndicatorCircle, {backgroundColor:circleBackgroundColor[1]}]} />
<View style={[styles.positionIndicatorCircle, {backgroundColor:circleBackgroundColor[2]}]} />
</View>

Et voilà le résultat :

Résultat final de notre onboarding

Voilà comment construire un onboarding simple avec des composants fournis par React Native sans avoir besoin de passer par un package externe. Il ne reste plus qu’à créer les vues de chaque page et d’ajouter les méthodes de fermeture de l’écran, d’inscription et de connexion.

Je vous ajoute le code complet de la page ci-dessous.

import React from ‘react’;
import { StyleSheet, Text, View, ScrollView, Dimensions, SafeAreaView, TouchableOpacity } from ‘react-native’;
export default class Onboarding extends React.Component {
constructor(props) {
super(props);
this.state = {
pagePosition: 1
}
}
render() {
let circleBackgroundColor = [‘transparent’, ‘transparent’, ‘transparent’];
circleBackgroundColor[this.state.pagePosition-1] = ‘#fff’;

return (
<SafeAreaView style={styles.container}>
<View>
<ScrollView
style={styles.onboardingScrollView}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onScroll={this.onScrollHandler}
>
<View style={styles.onboardingView}>
<Text>Vue 1</Text>
</View>
<View style={styles.onboardingView}>
<Text>Vue 2</Text>
</View>
<View style={styles.onboardingView}>
<Text>Vue 3</Text>
</View>
</ScrollView>
<TouchableOpacity
onPress={this.closeTheOnboarding}
style={styles.closeButton}
>
<Text>Passer</Text>
</TouchableOpacity>
<View style={styles.bottomOptions}>
<View style={styles.positionIndicatorsRow}>
<View style={[styles.positionIndicatorCircle, {backgroundColor:circleBackgroundColor[0]}]} />
<View style={[styles.positionIndicatorCircle, {backgroundColor:circleBackgroundColor[1]}]} />
<View style={[styles.positionIndicatorCircle, {backgroundColor:circleBackgroundColor[2]}]} />
</View>
<TouchableOpacity
onPress={this.signUp}
style={styles.signUpButton}
>
<Text style={styles.signUpButtonText}>
S’INSCRIRE GRATUITEMENT
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.signIn}
style={styles.signInButton}
>
<Text style={styles.signInButtonText}>
SE CONNECTER
</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
}
closeTheOnboarding = () => {
//TODO : Mettre la méthode de fermeture de l’onboarding
}
signUp = () => {
//TODO : Mettre la méthode d’ouverture de la page d’inscription
}
signIn = () => {
//TODO : Mettre la méthode d’ouverture de la page de connexion
}
onScrollHandler = (event) => {
let pagePosition = 1;
const scrollViewAbscissa = event.nativeEvent.contentOffset.x;
const pageWidth = Dimensions.get(‘window’).width;
//On va voir si le reste de la division de l’abscisse par rapport à la largeur de l’écran est égale à 0. Si oui, le résultat de la division, nous donnera la page
const remain = scrollViewAbscissa % pageWidth;
if (remain === 0) {
pagePosition = parseInt(scrollViewAbscissa / pageWidth) + 1;
//Si le numéro de page ainsi calculé est différent de celui en cours, on le met à jour
if (this.state.pagePosition !== pagePosition) {
this.setState({ pagePosition: pagePosition });
}
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: ‘#6789a0’,
alignItems: ‘center’,
justifyContent: ‘center’
},
onboardingScrollView: {
flex: 1,
width: Dimensions.get(‘screen’).width
},
onboardingView: {
flex: 1,
width: Dimensions.get(‘screen’).width,
justifyContent: ‘center’,
alignItems: ‘center’
},
closeButton: {
position: ‘absolute’,
top: 10,
right: 20
},
bottomOptions: {
position: ‘absolute’,
bottom: 0,
left: 0,
width: Dimensions.get(‘screen’).width,
alignItems: ‘center’
},
signUpButton: {
width: 200,
height: 40,
backgroundColor: ‘#fff’,
alignItems: ‘center’,
justifyContent: ‘center’,
marginVertical: 10
},
signUpButtonText: {
textAlign: ‘center’,
paddingHorizontal: 20,
color: ‘#404040’
},
signInButton: {
width: 200,
height: 40,
alignItems: ‘center’,
justifyContent: ‘center’
},
signInButtonText: {
textAlign: ‘center’,
paddingHorizontal: 20,
color: ‘#fff’
},
positionIndicatorsRow: {
flexDirection:’row’,
justifyContent:’space-between’,
width:100,
marginVertical:10
},
positionIndicatorCircle: {
width:20,
height:20,
borderRadius:10,
borderWidth:2,
borderColor:’#fff’
}
});

--

--

Axel de Sainte Marie
Axel de Sainte Marie

Written by Axel de Sainte Marie

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

No responses yet