Aller au contenu principal


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.

Cliquer pour agrandir l'image

Billet créé le :
26 déc 2023

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 :

  1. {num : 0, title : 'enr0'}

  2. {num : 1, title : 'enr1'}

  3. {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.

 

Notez que le style flatlistContainer, qui gère la mise en page de la flatliste et dont le code suit, n'affichera dans l'espace disponible, que 11 items pour "aérer" la mise en page.

    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)

 

 

Cliquez sur le bouton pour copier le code
dans le presse papier  
  
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;