I Introduction

L’idée est de voir comment créer son propre composant à partir de composants classiques de « Compose ».

Pour cela on souhaite :

  • une zone de saisie pour renseigner un chiffre ;
  • un bouton permettant d’incrémenter le chiffre ;
  • un bouton permettant de décrémenter le chiffre ;
  • un contrôle sur la saisie (que des chiffres…).

      L’idée est de voir comment créer son propre composant à partir de composants classiques de « Compose ».

        II Utilisation du Composable « OutlinedTextField »

        Nous allons utiliser un Composable de type « OutlinedTextField » qui peut intégrer des composants dans plusieurs zones, notamment avant et après le texte de saisie, comme on peut le voir dans l’image ci-dessous :

        Source : https://m2.material.io/components/text-fields#anatomy

        A savoir que ce composable propose les paramètres suivants :

        III Notre cas

        III.1 Présentation composant

        Notre composant sera de la forme suivante :

        III.2 Code du composant

        Le code du Composable sera le suivant :

        /**
         * Composant permettant d'incrémenter/décrémenter un chiffre avec possibilité de modifier manuellemet la valeur
         * @param valeur_initiale valeur qu'affichera le composant au premier affichage
         * @param valeur_label libellé de la zone de saisie
         * @param callback_retour_valeur fonction retour qui donnera la valeur du composant
         */
        @Composable
        fun Texte_saisie( valeur_initiale:String = "0",
                          valeur_label:String="",
                          callback_retour_valeur : ( (valeur:Int)->Unit )?=null
                          ) {
        
            // Pour mémoriser la valeur renseignée par l'utilisateur
            var valeur_saisie by remember { mutableStateOf(valeur_initiale)}
        
            OutlinedTextField(
                // Enregistrement changement texte
                value = valeur_saisie,   // valeur en cours
                onValueChange = {
                    valeur_saisie = it      // dès changement, on modifie la valeur en cours
                    valeur_saisie = valeur_saisie.replace("[^0-9,\\.]".toRegex(),"")
                },
                // Fait en sorte que le sorte que le texte soit centré horizontalement
                textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
                // Change la couleur de de fond
                colors = TextFieldDefaults.textFieldColors(backgroundColor = Color(0xBEBCDC97)),
                // on force à une seule ligne
                singleLine = true,
                //Espacement , taille, sortie de focus
                modifier = Modifier
                    .padding(start = 20.dp, end = 10.dp)
                    .defaultMinSize(
                        minWidth = 2.dp,
                        minHeight = 2.dp
                    ) // bof ... prend une autre taille min par defaut
                    .width(160.dp) // influe sur la taille mais fixe les choses ...
                    .onFocusChanged {
                                    if (!it.isFocused) {
                                        // retour valeur
                                        try {
                                            callback_retour_valeur?.invoke(valeur_saisie.toInt())
                                        } catch (e:Exception) {
                                        }
                                    }
                    }
                ,
                // Nom du champ
                //   si valeur non renseignée, affichera dans la zone valeur
                //   si valeur renseignée, cette zone sera placée en haut à gauche en miniature
                //     au dessus de la zone de valeur
                label = {
                    Row() {
                        //Icon(imageVector = Icons.Rounded.Favorite , contentDescription = null )
                        Text(text = valeur_label)
                    }
                },
                // Place quand valeur non renseignée et focus (emplacement de la zone valeur à renseigner)
                //    --> Attention la hauteur prendra la hauteur de ce texte si texte plus long que le label
                placeholder = {
                    //Text(text = "Texte quand valeur non renseignée avec une grande description de la mort qui tue")
                },
                // Place avant le texte de saisie
                leadingIcon = {
                   Icon(
                       imageVector = Icons.Rounded.Remove ,
                       contentDescription = null,
                       modifier = Modifier.clickable {
                           valeur_saisie = Decremente_valeur(valeur_saisie)
                           // renvoie valeur
                           try {
                               callback_retour_valeur?.invoke(valeur_saisie.toInt())
                           } catch (e:Exception) {
                           }
        
                       }
                   )
                },
                // Place après le texte de saisie
                trailingIcon = {
                    Icon(
                        imageVector = Icons.Rounded.Add ,
                        contentDescription = null,
                        modifier = Modifier.clickable {
                            valeur_saisie = Incremente_valeur(valeur_saisie)
                            // renvoie valeur
                            try {
                                callback_retour_valeur?.invoke(valeur_saisie.toInt())
                            } catch (e:Exception) {
                            }
                        }
                    )
                },
        
                // Force la possibilité de ne pas changer la valeur
                //readOnly = true
                // Définit le type de clavier à utiliser (chiffre)
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number,imeAction = ImeAction.Default),
        
            )
        }

        III.3 Compréhension graphique par blocs de code

        Ci-dessous les principaux morceaux de code qui permettent d’obtenir le composant souhaité :

        IV Code complet de l’activité intégrant ce composant personnalisé

        Ainsi le code complet :

        package com.example.textview_test1
        
        import android.os.Bundle
        import androidx.activity.ComponentActivity
        import androidx.activity.compose.setContent
        import androidx.compose.foundation.clickable
        import androidx.compose.foundation.layout.*
        import androidx.compose.foundation.text.KeyboardOptions
        import androidx.compose.material.*
        import androidx.compose.material.icons.Icons
        import androidx.compose.material.icons.rounded.*
        import androidx.compose.runtime.*
        import androidx.compose.ui.Modifier
        import androidx.compose.ui.focus.onFocusChanged
        import androidx.compose.ui.graphics.Color
        import androidx.compose.ui.text.input.ImeAction
        import androidx.compose.ui.text.input.KeyboardType
        import androidx.compose.ui.text.style.TextAlign
        import androidx.compose.ui.tooling.preview.Preview
        import androidx.compose.ui.unit.dp
        import com.example.textview_test1.ui.theme.Textview_test1Theme
        
        class MainActivity : ComponentActivity() {
            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContent {
                    Textview_test1Theme {
                        // A surface container using the 'background' color from the theme
                        Surface(
                            modifier = Modifier.fillMaxSize(),
                            //color = MaterialTheme.colors.error
                        ) {
                            Sommaire()
                        }
                    }
                }
            }
        
        }
        
        /**
         * Composant permettant d'incrémenter/décrémenter un chiffre avec possibilité de modifier manuellemet la valeur
         * @param valeur_initiale valeur qu'affichera le composant au premier affichage
         * @param valeur_label libellé de la zone de saisie
         * @param callback_retour_valeur fonction retour qui donnera la valeur du composant
         */
        @Composable
        fun Texte_saisie( valeur_initiale:String = "0",
                          valeur_label:String="",
                          callback_retour_valeur : ( (valeur:Int)->Unit )?=null
                          ) {
        
            // Pour mémoriser la valeur renseignée par l'utilisateur
            var valeur_saisie by remember { mutableStateOf(valeur_initiale)}
        
            OutlinedTextField(
                // Enregistrement changement texte
                value = valeur_saisie,   // valeur en cours
                onValueChange = {
                    valeur_saisie = it      // dès changement, on modifie la valeur en cours
                    valeur_saisie = valeur_saisie.replace("[^0-9,\\.]".toRegex(),"")
                },
                // Fait en sorte que le sorte que le texte soit centré horizontalement
                textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
                // Change la couleur de de fond
                colors = TextFieldDefaults.textFieldColors(backgroundColor = Color(0xBEBCDC97)),
                // on force à une seule ligne
                singleLine = true,
                //Espacement , taille, sortie de focus
                modifier = Modifier
                    .padding(start = 20.dp, end = 10.dp)
                    .defaultMinSize(
                        minWidth = 2.dp,
                        minHeight = 2.dp
                    ) // bof ... prend une autre taille min par defaut
                    .width(160.dp) // influe sur la taille mais fixe les choses ...
                    .onFocusChanged {
                                    if (!it.isFocused) {
                                        // retour valeur
                                        try {
                                            callback_retour_valeur?.invoke(valeur_saisie.toInt())
                                        } catch (e:Exception) {
                                        }
                                    }
                    }
                ,
                // Nom du champ
                //   si valeur non renseignée, affichera dans la zone valeur
                //   si valeur renseignée, cette zone sera placée en haut à gauche en miniature
                //     au dessus de la zone de valeur
                label = {
                    Row() {
                        //Icon(imageVector = Icons.Rounded.Favorite , contentDescription = null )
                        Text(text = valeur_label)
                    }
                },
                // Place quand valeur non renseignée et focus (emplacement de la zone valeur à renseigner)
                //    --> Attention la hauteur prendra la hauteur de ce texte si texte plus long que le label
                placeholder = {
                    //Text(text = "Texte quand valeur non renseignée avec une grande description de la mort qui tue")
                },
                // Place avant le texte de saisie
                leadingIcon = {
                   Icon(
                       imageVector = Icons.Rounded.Remove ,
                       contentDescription = null,
                       modifier = Modifier.clickable {
                           valeur_saisie = Decremente_valeur(valeur_saisie)
                           // renvoie valeur
                           try {
                               callback_retour_valeur?.invoke(valeur_saisie.toInt())
                           } catch (e:Exception) {
                           }
        
                       }
                   )
                },
                // Place après le texte de saisie
                trailingIcon = {
                    Icon(
                        imageVector = Icons.Rounded.Add ,
                        contentDescription = null,
                        modifier = Modifier.clickable {
                            valeur_saisie = Incremente_valeur(valeur_saisie)
                            // renvoie valeur
                            try {
                                callback_retour_valeur?.invoke(valeur_saisie.toInt())
                            } catch (e:Exception) {
                            }
                        }
                    )
                },
        
                // Force la possibilité de ne pas changer la valeur
                //readOnly = true
                // Définit le type de clavier à utiliser (chiffre)
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number,imeAction = ImeAction.Default),
        
            )
        }
        
        /**
         * Incrémente une valeur de type String
         * @param valeur
         * @return Valeur sous forme de String
         */
        fun Incremente_valeur(valeur:String):String {
            var val_int = 0
        
            try {
                val_int = valeur.toInt()
                val_int++
            } catch (e:Exception) {
        
            }
            return val_int.toString()
        }
        
        /**
         * Décrémente une valeur de type String
         * @param valeur
         * @return Valeur sous forme de String
         */
        fun Decremente_valeur(valeur:String):String {
            var val_int = 0
            try {
                val_int = valeur.toInt()
                val_int--
            } catch (e:Exception) {
        
            }
            return val_int.toString()
        }
        
        
        /**
         * Construction générale de l'activité
         */
        @Composable
        fun Sommaire() {
        
            var valeur_texte_saisie1 by remember { mutableStateOf(0) }
        
        
            Column() {
                Text(text = "Test Textview")
                Spacer(modifier = Modifier.height(20.dp))
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.Center
                ) {
                    Texte_saisie(
                        valeur_label = "Année Min",
                        callback_retour_valeur = {
                            // récupère la valeur saisie (manuellement ou via les boutons)
                            valeur_texte_saisie1 = it
                        }
                    )
                    Spacer(modifier = Modifier.width(10.dp))
                    Texte_saisie(valeur_label = "Année Max")
                }
        
                Spacer(modifier = Modifier.height(8.dp))
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.Center
                ) {
                    Texte_saisie(valeur_label = "Début taux")
                    Spacer(modifier = Modifier.width(10.dp))
                    Texte_saisie(valeur_label = "Fin Taux")
                }
        
                Spacer(modifier = Modifier.height(8.dp)) 
                Row (
                    modifier=Modifier.fillMaxWidth()
                        ) {
                    Text(text = "Valeur : " + valeur_texte_saisie1.toString())
                
                }
            }
        }
        
        @Preview(showBackground = true)
        @Composable
        fun DefaultPreview() {
            Textview_test1Theme {
                Sommaire()
            }
        }

        Aperçu de l’activité :

        KOTLIN : personnaliser ses composants

        Laisser un commentaire

        Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *