FlatList et navigation
Ce billet analyse une portion de code dans lequel 3 "boutons" permettent de naviguer dans les items d'une "flatList" utilisée par exemple pour afficher les enregistrements lus dans une base de données. L'image ci-dessus est une illustration de son exécution.
1. La liste des items à afficher :
Les données affichées sont générées lors de l'exécution de la fonction genData() :
function genData(nb) {
for (i=0 ; i<nb ; i++)
{ enrDb.push({num : i, title : 'enr '+i}) }
}
Le code ci-dessus écrit la fonction dans la forme traditionnelle. Le code suivant l'écrit sous la forme ES6 (dite fléchée)
const genData = (nb) => {
for (i=0 ; i<nb ; i++)
{ enrDb.push({num : i, title : 'enr '+i}) }
}
La fonction "genData" complète le tableau (array) "enrDb". Dans ce tableau les éléments sont des objets dont les clés sont :
num : un entier compris entre 0 et nb par exemple 12 ;
title : une chaîne de caractère, par exemple enr12.
Les 1er elements de ce tableau sont :
{num : 0, title : 'enr0'}
{num : 1, title : 'enr1'}
{num : 0, title : 'enr2'}
Dans un cas plus général, par exemple lors de l'affichage des enregistrements d'une base de données, ce tableau est produit par une requête de lecture envoyée sur le serveur hébergeant la base : (re)voir le paragraphe "la fonction ReadRecords() du billet "Réact-native : cRuD 1/2".
2. L'affichage
(Re)lire si nécessaire le paragraphe "props" de du billet "Etude d'un exemple."
Les propriétés (props) du composant "FlatList" sont initialisées dans le code ci-dessous :
<FlatList
ref={maListe}
data={enrDb}
renderItem={AfficherItem}
getItemLayout={getItemLayout}
/>
Ces propriétés sont :
data qui prend la valeur "enrDb" : la liste des items à afficher (cette propriété est obligatoire) ;
renderItem qui a pour valeur la fonction "AfficherItem" qui "prend" un item dans la liste des items et l'affiche : (cette propriété est obligatoire). Cette fonction est détaillée ci-dessous
getItemLayout qui a pour valeur la fonction "getItemLayout" qui calcule les dimensions des éléments de la liste. Cette fonction est détaillée ci-dessous ;
ref qui a pour valeur "maListe" et mémorise la référence de l'objet "FlatList" utilisé. Cette référence permettra l'implémentation de la navigation (voir le hook useRef et le chapitre "la navigation" ci-dessous).
La fonction "getItemLayout" :
//
const SCREEN_HEIGHT = Dimensions.get("screen").height;
const NAV_HEIGHT = Math.trunc (SCREEN_HEIGHT / 5);
const CONTENT_HEIGHT = NAV_HEIGHT * 4 ;
// ITEM_HEIGHT = 50
const ITEM_HEIGHT = Math.trunc (CONTENT_HEIGHT / 14);
//
const getItemLayout = ( data, index) => {
return {
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * (index ),
index,
};
};
Cette fonction s'appuie sur la constante ITEM_HEIGHT, qui vaut environ 50 sur mon terminal . Elle est calculée à partir de la hauteur d'écran disponible dès lors que les boutons de navigation sont affichés, cette hauteur d'écran disponible est stockée dans la constante CONTENT_HEIGHT et vaut les 4/5 de SCREEN_HEIGHT (ou NAV_HEIGHT * 4). Elle doit donc pouvoir contenir 14 items.
ITEM_HEIGHT, comme l'indique le style "itemContainer", est la hauteur occupée par l'affichage d'un item de la liste.
La fonction getItemLayout() reçoit les paramètres :
data : la liste des items à afficher ;
index : l'index (le rang) de l'item à afficher dans la liste.
Elle rend un objet qui a 3 propriétés :
length : la hauteur de l'item à afficher ;
offset : la distance entre le haut du 1er item de la liste et le haut de l'item à l'index ;
index
La fonction "AfficherItem":
La fonction-composant "afficherItem" a pour paramètre {item, index}. Il s'agit donc de props. Ces props "item" et "index" sont héritées du composant parent : "FlatList" comme l'indique la documentation sur RenderItem de FlatList
const AfficherItem = ({item, index }) => {
return(
<View style={styles.itemContainer}>
<View style={styles.index }>
<Text>
{index} :
</Text>
</View>
<View style={styles.item }>
<Text style={styles.title}> {item.title}</Text>
</View>
</View>
)
};
Cette fonction affiche (avec la mise en forme appropriée) le rang ("index") et la valeur de la clé "title" de l' item de rang "index" de la liste.
flatListContainer:{
height: ITEM_HEIGHT * 11,
marginTop : 2,
marginBottom :2,
marginLeft:"10%",
marginRight:"10%",
paddingBottom: 5,
},
3. La navigation
Le composant "FlatList" offre une barre de défilement permettant à l'utilisateur de faire défiler les items de la liste dans la zone d'affichage. Il peut être confortable d'ajouter des boutons de navigation pour :
atteindre plus rapidement le 1er item de la liste ;
atteindre plus rapidement le dernier item de la liste ;
atteindre plus rapidement le nième item de la liste.
Le code suivant :
<TouchableHighlight
style={styles.u}
onPress= {() => press(0)}
underlayColor="aliceblue"
>
<View style={styles.button}>
<Text style={styles.buttonText}>
1er enregistrement,
</Text>
<Text style={styles.buttonText}>
Index : 0
</Text>
</View>
</TouchableHighlight>
crée le "bouton" permettant d'atteindre le 1er enregistrement de la liste grâce à la fonction press() dont le code est affiché ci-dessous. La modification de la valeur du paramètre permet l'accès à l'item désiré.
const press = (num) => {
if (num < 0 || num > nbEnr)
{
num =0;
Alert.alert( "l'index doit être compris entre 0 et "+ nbEnr);
}
{
switch (num) {
case 0 :
maListe.current.scrollToIndex({ animated: true, index :0});
break;
case nbEnr -1:
maListe.current.scrollToEnd({ animated: true});
break;
default:
maListe.current.scrollToIndex({ animated: true, index: num, });
}
};
};
Cette fonction utilise les propriétés -méthodes "scrollToIndex" et "scrollToEnd" du composant "FlatList". La ligne de code :
maListe.current.scrollToIndex()
utilise la méthode "scrollToIndex()" de la "FlatList" référencée dans la constante "maListe" (cf. le § précédant)
import React, {useRef, useState, useCallback} from 'react';
import {
View,
FlatList,
StyleSheet,
Text,
TextInput,
TouchableHighlight,
Dimensions,
Alert,
} from 'react-native';
//
const SCREEN_HEIGHT = Dimensions.get("screen").height;
const NAV_HEIGHT = Math.trunc (SCREEN_HEIGHT / 5);
const CONTENT_HEIGHT = NAV_HEIGHT * 4 ;
// ITEM_HEIGHT = 50
const ITEM_HEIGHT = Math.trunc (CONTENT_HEIGHT / 14);
// génération des données à afficher dans la FlatList
var enrDb =[];
function genData(nb) {
for (i=0;i<nb;i++)
{enrDb.push({num:i,title:'enr '+i})}
}
//
const getItemLayout = ( data, index) => {
return {
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * (index ),
index,
};
};
//
// le programme principal
const App = () => {
//
const nbEnr = 100;
genData(nbEnr);
const maListe = useRef(null);
// pour afficher les items de la FlatList
const AfficherItem = ({item, index }) => {
return(
<View style={styles.itemContainer}>
<View style={styles.index }>
<Text>
{index} :
</Text>
</View>
<View style={styles.item }>
<Text style={styles.title}> {item.title}</Text>
</View>
</View>
)
};
// action de l' utilisateur sur les "boutons"
const press = (num) => {
if (num < 0 || num > nbEnr)
{
num =0;
Alert.alert( "l'index doit être compris entre 0 et "+ nbEnr);
}
{
switch (num) {
case 0 :
maListe.current.scrollToIndex({ animated: true, index :0});
break;
case nbEnr -1:
maListe.current.scrollToEnd({ animated: true});
break;
default:
maListe.current.scrollToIndex({ animated: true, index: num, });
}
};
console.log (" dans press num: " + num );
};
//
return (
<View style={styles.container}>
<View style={styles.buttonContainer}>
<View style={{flexDirection : "row",}}>
<TouchableHighlight
style={styles.u}
onPress= {() => press(99)}
underlayColor="aliceblue"
>
<View style={styles.button}>
<Text style={styles.buttonText}>
dernier enregistrement,
</Text>
<Text style={styles.buttonText}>
Index : {nbEnr -1}
</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
style={styles.u}
onPress= {() => press(0)}
underlayColor="aliceblue"
>
<View style={styles.button}>
<Text style={styles.buttonText}>
1er enregistrement,
</Text>
<Text style={styles.buttonText}>
Index : 0
</Text>
</View>
</TouchableHighlight>
</View>
<View style={styles.inputContainer}>
<View style={styles.button}>
<Text style={styles.buttonText}>
Afficher un enregistrement :
</Text>
<Text style={styles.buttonText}>
nb : entre 0 et {nbEnr -1}
</Text>
</View>
<TextInput
style={styles.inputAffEnr}
placeholder="nb"
keyboardType="numeric"
onEndEditing={(e)=>(press(e.nativeEvent.text))}
maxLength={4}
/>
</View>
</View>
<View style={styles.flatListContainer}>
<FlatList
ref={maListe}
data={enrDb}
renderItem={AfficherItem}
getItemLayout={getItemLayout}
/>
</View>
</View>
);
};
//
const styles = StyleSheet.create({
container: {
flex: 1,
},
buttonContainer :{
height:NAV_HEIGHT,
flexDirection : "column",
},
item: {
flexDirection:'row',
flex:10,
justifyContent:'center',
alignItems:'center',
backgroundColor: '#f9c2ff',
},
index: {
flexDirection:'row',
flex:1,
justifyContent:'center',
alignItems:'center',
},
title: {
fontSize: 30,
},
itemContainer :{
flexDirection: "row",
marginVertical: 1,
marginHorizontal: 16,
height:ITEM_HEIGHT -2,
},
button:{
flexDirection : "column",
alignItems:'center',
justifyContent:"center",
backgroundColor: "dodgerblue",
color:"black",
fontWeight: 'bold',
fontSize:18
},
inputContainer : {
flexDirection : "row",
alignItems:'center',
justifyContent:"center"
},
flatListContainer:{
height: ITEM_HEIGHT * 11,
marginTop : 2,
marginBottom :2,
marginLeft:"10%",
marginRight:"10%",
paddingBottom: 5,
},
buttonText:{
color:"black",
fontSize:18
},
inputAffEnr :{
textAlign:"center",
color:"black",
fontSize:20,
borderColor:"black",
borderWidth:2,
backgroundColor:'#CCD3FF',
paddingHorizontal: 8,
paddingVertical: 6,
borderRadius: 4,
width: 100
},
u: {
paddingHorizontal: 8,
paddingVertical: 6,
borderRadius: 4,
backgroundColor: "dodgerblue",
alignSelf: "center",
marginHorizontal: "1%",
marginBottom: 6,
marginTop: 6,
minWidth: 40,
textAlign: "center",
color: 'black',
fontWeight: 'bold'
},
});
//
export default App;