I Introduction
Ici un deuxième exemple montrant comment créer son propre composant sous Compose avec Kotlin.
L’idée de ce composant est de pouvoir renseigner rapidement une valeur. Pour cela, ce composant sera composé :
- d’un Slider permettant un choix rapide de la valeur ;
- de 2 boutons sur les côtés du Slider pour ajuster finement la valeur (par incrément ou décrément) ;
- d’un affichage de la valeur au dessus du curseur de sélection.
Il sera également possible de définir une valeur manuellement en cliquant l’affichage de la valeur.
Enfin il sera intégré des contrôles comme :
- valeur min et max ;
- la valeur de l’incrément ;
- affichage en rouge quand la valeur saisie manuellement dépasse les valeurs min et max ;
- le contrôle de la saisie.
II Notre composant
II.1 présentation générale du composant
Ci-dessous l’aspect général du composant et ses principales fonctionnalités :

II.2 Paramètres d’entrée
Ce composant aura les paramètres d’entrée suivants, tous définis par défaut :
/**
* Composant personnalisé utilisant un Slider et bouton + et - pour changer la valeur
* Affichage de la valeur au dessus du curseur et possibilité de modifier la valeur manuellement
*
* @param increment_valeur Tableau de 2 valeurs indiquant plage min et max
* @param valeur_slider valeur par defaut du slider
* @param intervalle_valeur valeur d'incrément/décrément via les boutons + et -
* @param width_slider largeur du composant
* @param callback_retour_valeur fonction de retour une fois que la valeur change
*/
@Composable
fun slider_XH(
intervalle_valeur :ClosedFloatingPointRange<Float> = 0f..25f,
valeur_slider: Float = 1f,
increment_valeur : Float = 0.1f,
width_slider : Dp = 115.dp,
callback_retour_valeur : ( (valeur_str:String)->Unit )?=null
) {
II.3 Structure générale « Compose » du composant
La structure générale du composant sera la suivante :
@Composable
fun slider_XH(
intervalle_valeur :ClosedFloatingPointRange<Float> = 0f..25f,
valeur_slider: Float = 1f,
increment_valeur : Float = 0.1f,
width_slider : Dp = 115.dp,
callback_retour_valeur : ( (valeur_str:String)->Unit )?=null
) {
Column() {
Row( verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Rounded.Remove ,
contentDescription = null,
)
Box() {
BasicTextField(
value = text,
onValueChange = {
},
)
Slider(
// valeur du slider
value = position_slider,
)
}
Icon(
imageVector = Icons.Rounded.Add ,
contentDescription = null,
)
}
}
}
Le « Slider » et le « BasicTextField » sont intégrés dans une « Box ». Ceci pour pouvoir :
- bouger la position du « BasicTextField » plus facilement en horizontal (à chaque que la valeur du Slider change),
- de même pour descendre légèrement le slider en vertical (une fois).
II.4 En détail …
II.4.1 les variables « globales » du composant
Concernant les variables « globales » utilisées par les composants », elles seront les suivantes :
@Composable
fun slider_XH(
intervalle_valeur :ClosedFloatingPointRange<Float> = 0f..25f,
valeur_slider: Float = 1f,
increment_valeur : Float = 0.1f,
width_slider : Dp = 115.dp,
callback_retour_valeur : ( (valeur_str:String)->Unit )?=null
) {
val top_element = 8.dp // hauteur pour laisser de la place entre le texte curseur et le slider + icônes
var position_slider by remember { mutableStateOf(valeur_slider) } // position du slider
var text by remember { mutableStateOf(get_valeur_pour_slider(position_slider.toString(), intervalle_valeur,increment_valeur).toString()) } // texte du BasicTextField (pour modification manuelle)
var is_text_erreur by remember { mutableStateOf(false) } // erreur sur saisie valeur (pour affichage texte en rouge)
var largeur_texte_label: Dp by remember { mutableStateOf(0.dp) } // largeur BasicTextField pour positionnement en vertical
val densite_locale = LocalDensity.current
var activity = LocalContext.current as Activity
// .......
II.4.2 Code du bouton « – »
Nous allons voir la partie du code qui régit le bouton gauche qui permettra de décrémenter la valeur du « Slider »

Ce qui donne :
// .......
Icon(
imageVector = Icons.Rounded.Remove , // image de l'icône
contentDescription = null,
modifier = Modifier
.padding(top = top_element) // ajustement pour se caler avec le Slider
.clickable { // gestion du click sur l'icône
position_slider -= increment_valeur // Décrémente
// vérifie que la valeur ne dépasse les bornes min et max
position_slider = get_valeur_pour_slider(position_slider.toString(),intervalle_valeur,increment_valeur)
// mise en forme du texte qui indiquera la valeur
text = position_slider.toString().removeSuffix(".0")
// indique aux autres composants qu'il y a une erreur de valeur (pour BasicTextField)
is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)
// Renvoi nouvelle valeur (si pas d'erreur)
if (!is_text_erreur) {
if (callback_retour_valeur != null) {
callback_retour_valeur(text)
}
}
cacher_clavier_slider(activity) // Cacher clavier
}
)
Dans le code ci-dessus il est fait appel à trois fonctions.
La première fonction s’assure d’avoir une valeur dans l’intervalle donné ou 0.f dans le pire des cas.
/**
* Vérifie que la valeur à traiter soit bien dans un intervalle donné
*Si non, renvoie dans les limites min ou ax de l'intervalle
* @param valeur Valeur à traiter
* @param intervalle Intervalle dans lequel la valeur doit être située
* @return donnée [Float] qui sera dans le bon intervalle donné par [intervalle]
*/
fun get_valeur_pour_slider(valeur:String, intervalle :ClosedFloatingPointRange<Float>, increment_valeur: Float ) : Float {
var val_retour : Float = 0f
if (valeur.isBlank()) return val_retour
try {
val_retour = valeur.toFloat()
} catch (e : Exception) {
return 0f
}
val valeur_a_traiter = valeur.toFloat()
if (valeur_a_traiter > intervalle.endInclusive) return intervalle.endInclusive
if (valeur_a_traiter<intervalle.start) return intervalle.start
var increment_str = increment_valeur.toString()
increment_str = increment_str.replaceBefore(".","0")
val nouveau_increment = increment_str.toFloat()
var nb_centieme = 1
if (nouveau_increment<1f) nb_centieme=10
if (nouveau_increment<0.1f) nb_centieme=100
if (nouveau_increment<0.01f) nb_centieme=1000
if (nouveau_increment==0f) nb_centieme=1
val_retour= (val_retour*nb_centieme).roundToInt() / (nb_centieme.toFloat())
return val_retour
}
La deuxième fonction indique juste si la valeur est bien dans l’intervalle souhaité :
/**
* Indique si la valeur renseignée est correcte par rapport aux valeurs min max initiales
*/
fun is_valeur_slider_correcte(valeur:String, intervalle :ClosedFloatingPointRange<Float>):Boolean {
var val_retour : Boolean = true
if (valeur.isBlank()) return false
// test si valeur de type float
try {
val test = valeur.toFloat()
} catch (e : Exception) {
return false
}
// test si dans intervalle
val valeur_a_traiter = valeur.toFloat()
if (valeur_a_traiter > intervalle.endInclusive) return false
if (valeur_a_traiter < intervalle.start) return false
return val_retour
}
Enfin la troisième fonction s’occupe de cacher le clavier :
/**
* Permet de cacher le clavier
*/
fun cacher_clavier_slider(activity:Activity) {
val imm: InputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
var view: View? = activity.currentFocus
if (view == null) {
view = View(activity);
}
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
II.4.3 Code du bouton « + »

Le code est quasiment identique à celui du bouton « – » , seule change la ligne qui concerne l’incrément de la valeur …
///////////////////////////////////
// Icône droite (+) clickable
Icon(
imageVector = Icons.Rounded.Add ,
contentDescription = null,
modifier = Modifier
.padding(top = top_element)
.clickable {
position_slider += increment_valeur
position_slider = get_valeur_pour_slider(position_slider.toString(),intervalle_valeur,increment_valeur )
text = position_slider.toString().removeSuffix(".0")
is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)
if (!is_text_erreur) {
if (callback_retour_valeur != null) {
callback_retour_valeur(text)
}
}
cacher_clavier_slider(activity) // Cacher clavier
}
)
II.4.4 Code du « Box » qui va contenir le « BasicTextField » et le Slider
Cela va donc concerner cette zone :

Pour la structure générale qui est la suivante :
Box() {
BasicTextField(
value = text,
onValueChange = {
},
)
Slider(
// valeur du slider
value = position_slider,
)
}
Par défaut les composants « Slider » et « BasicTextField » ne sont pas présentés comme on le voit au dessus mais en superposition comme ci-dessous :

Ainsi, il va falloir:
- décaler le « Slider » vers le bas ainsi que les boutons pour que le tout soit aligné,
- positionner le « BasicTextFied » sur la même position latérale que la puce du « Slider »
Nous aurons donc un début de code dédié à la position du « BasicTextField » comme suivant :
Box() {
// Calcul intermédiaire pour définir offset texte à afficher
// on décale en fonction de la grosseur du curseur
// 10.dp correspond à ThumbRadius dans Slider.kt -> marge entre la box et le track
val decalage_texte_a_afficher = 12.dp
// décalage final pour positionner l'élément texte
val decalage_texte_final_a_afficher = decalage_texte_a_afficher -( largeur_texte_label/2)
// Callage vis à vis du curseur
val ratio_slider : Float = (position_slider-intervalle_valeur.start)/(intervalle_valeur.endInclusive - intervalle_valeur.start)
// offset_slider = taille du slider * ratio séléection du slider
val offset_slider : Dp = (width_slider-20.dp).times(ratio_slider) // pourqoi le -20dp ???
// Callage final
var decalage_slider : Dp = decalage_texte_final_a_afficher + offset_slider
if (decalage_slider<5.dp) {decalage_slider=5.dp}
val offset_x = decalage_slider
//....
II.4.5 Code du « TextBasicField »
Tout avant le bloc du composant, il est défini une variable permettant d’indiquer si le texte doit être affiché en rouge en cas d’erreur détecté lors d’une saisie manuelle :
//////////////////////////////
// Basic TextField
var coul_texte by remember {mutableStateOf( Color.Unspecified) }
if (is_text_erreur) {
coul_texte = Color.Red
} else {
coul_texte = Color.Unspecified
}
Et il vient le code du composant :
BasicTextField(
value = text,
onValueChange = {
text = traite_saisie_slider(text,it) // permet de rendre éditable le composant + contrôle saisie
// met à jour la position du slider en fonction de la taille du texte
position_slider = get_valeur_pour_slider(text, intervalle_valeur,increment_valeur)
// indique si erreur detectée dans la saisie (pour affichage en rouge...)
is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)
// ici pas de renvoi de valeur (on passe par la vailidation du clavier)
},
textStyle = LocalTextStyle.current.copy(color = coul_texte),
modifier = Modifier
.width(IntrinsicSize.Min) // taille minimale de la zone de texte
.padding(start = offset_x) // positionnement du composant en fonction du curseur du Slider (via Offset)
.background(color = Color.Transparent)
.onGloballyPositioned {
// Récupération taille de la zone de texte (pour calcul offset et placement au milieu du curseur du slider
largeur_texte_label = with(densite_locale) { it.size.width.toDp() }
}
,
singleLine = true, // une seule ligne
// Clavier numérique seulement
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number,imeAction = ImeAction.Default),
// Clavier : action si appui sur 'OK' du clavier numérique
keyboardActions = KeyboardActions(
onDone = {
// renvoi valeur si correcte seulement
is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)
if (!is_text_erreur) {
if (callback_retour_valeur != null) {
callback_retour_valeur(text)
}
}
// cacher le clavier
cacher_clavier_slider(activity)
}
)
)
Plusieurs fonctions sont utilisées.
Celle qui permet de vérifier la saisie d’un nombre réel, positif ou négatif :
/**
* vérifie forme texte saisie pour un nombre reel signé ou pas
* prend en compte la saisie caractère par caractère
* "-" --> OK
* "-0" --> OK
* "-0." --> OK
* "-0.1" --> OK
* "-0.1. --> KO
* @param ancien_texte
* @param texte
* @return
*/
fun traite_saisie_slider(ancien_texte:String, texte:String):String {
var texte_traite = texte
// Remplacement caractère
texte_traite = texte_traite.replace(",",".")
// pattern pour test nombre réél au fur et à mesure (ex 10.25 ou -12.78)
var regex = "^((-?)(-?\\d+\\.?\\d*)?)".toRegex() // gère la séquence manuelle de saisie au fur et à mesure
// Si ça ne matche pas alors renvoi ancien texte
if (!texte_traite.matches(regex)) {
if (!texte_traite.equals("")) {
texte_traite = ancien_texte
}
}
return texte_traite
}
Grâce à cette fonction nous pouvons avoir le contrôle utilisateur suivant :

II.4.6 Code du « Slider »
Rien de particulier dans l’écriture du Slider : récupération de la valeur du slider dans variable « globale » pour être utilisée par d’autres composants, définition de la plage min et max et définition de la largeur et position haut du Slider :
Slider(
// valeur du slider
value = position_slider,
// enregistrement de la veleur du slider
onValueChange = {
position_slider = get_valeur_pour_slider(it.toString(), intervalle_valeur,increment_valeur)
text = position_slider.toString().removeSuffix(".0")
is_text_erreur =!is_valeur_slider_correcte(text, intervalle_valeur)
// Renvoi valeur
if (!is_text_erreur) {
if (callback_retour_valeur != null) {
callback_retour_valeur(text)
}
}
// Cacher clavier
cacher_clavier_slider(activity)
},
// Intervalle de valeur min max
valueRange= intervalle_valeur ,
// largeur du slider
modifier = Modifier
.padding(top = top_element)
.width(width_slider)
)
III Code complet
Ci dessous le code complet de l’activité qui permet d’obtenir :
- 2 sliders sur une ligne,
- 1 slider qui prend la place d’une ligne et qui renseigne le texte placé en dessous.
Exemple du rendu de l’activité :

Code complet de l’activité :
package com.example.slider_XH
import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Remove
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.example.textview_test1.ui.theme.Textview_test1Theme
import kotlin.math.roundToInt
class test_SliderActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Textview_test1Theme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Sommaire_slider()
}
}
}
}
}
/**
* Vérifie que la valeur à traiter soit bien dans un intervalle donné
*Si non, renvoie dans les limites min ou ax de l'intervalle
* @param valeur Valeur à traiter
* @param intervalle Intervalle dans lequel la valeur doit être située
* @return donnée [Float] qui sera dans le bon intervalle donné par [intervalle]
*/
fun get_valeur_pour_slider(valeur:String, intervalle :ClosedFloatingPointRange<Float>, increment_valeur: Float ) : Float {
var val_retour : Float = 0f
if (valeur.isBlank()) return val_retour
try {
val_retour = valeur.toFloat()
} catch (e : Exception) {
return 0f
}
val valeur_a_traiter = valeur.toFloat()
if (valeur_a_traiter > intervalle.endInclusive) return intervalle.endInclusive
if (valeur_a_traiter<intervalle.start) return intervalle.start
var increment_str = increment_valeur.toString()
increment_str = increment_str.replaceBefore(".","0")
val nouveau_increment = increment_str.toFloat()
var nb_centieme = 1
if (nouveau_increment<1f) nb_centieme=10
if (nouveau_increment<0.1f) nb_centieme=100
if (nouveau_increment<0.01f) nb_centieme=1000
if (nouveau_increment==0f) nb_centieme=1
val_retour= (val_retour*nb_centieme).roundToInt() / (nb_centieme.toFloat())
return val_retour
}
/**
* Indique si la valeur renseignée est correcte par rapport aux valeurs min max initiales
*/
fun is_valeur_slider_correcte(valeur:String, intervalle :ClosedFloatingPointRange<Float>):Boolean {
var val_retour : Boolean = true
if (valeur.isBlank()) return false
// test si valeur de type float
try {
val test = valeur.toFloat()
} catch (e : Exception) {
return false
}
// test si dans intervalle
val valeur_a_traiter = valeur.toFloat()
if (valeur_a_traiter > intervalle.endInclusive) return false
if (valeur_a_traiter < intervalle.start) return false
return val_retour
}
/**
* Permet de cacher le clavier
*/
fun cacher_clavier_slider(activity:Activity) {
val imm: InputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
var view: View? = activity.currentFocus
if (view == null) {
view = View(activity);
}
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
/**
* vérifie forme texte saisie pour un nombre reel signé ou pas
* prend en compte la saisie caractère par caractère
* "-" --> OK
* "-0" --> OK
* "-0." --> OK
* "-0.1" --> OK
* "-0.1. --> KO
* @param ancien_texte
* @param texte
* @return
*/
fun traite_saisie_slider(ancien_texte:String, texte:String):String {
var texte_traite = texte
// Remplacement caractère
texte_traite = texte_traite.replace(",",".")
// pattern pour test nombre réél au fur et à mesure (ex 10.25 ou -12.78)
var regex = "^((-?)(-?\\d+\\.?\\d*)?)".toRegex() // gère la séquence manuelle de saisie au fur et à mesure
// Si ça ne matche pas alors renvoi ancien texte
if (!texte_traite.matches(regex)) {
if (!texte_traite.equals("")) {
texte_traite = ancien_texte
}
}
return texte_traite
}
/**
* Composant personnalisé utilisant un Slider et bouton + et - pour changer la valeur
* Affichage de la valeur au dessus du curseur et possibilité de modifier la valeur manuellement
*
* @param increment_valeur Tableau de 2 valeurs indiquant plage min et max
* @param valeur_slider valeur par defaut du slider
* @param intervalle_valeur valeur d'incrément/décrément via les boutons + et -
* @param width_slider largeur du composant
* @param callback_retour_valeur fonction de retour une fois que la valeur change
*/
@Composable
fun slider_XH(
intervalle_valeur :ClosedFloatingPointRange<Float> = 0f..25f,
valeur_slider: Float = 1f,
increment_valeur : Float = 0.1f,
width_slider : Dp = 115.dp,
callback_retour_valeur : ( (valeur_str:String)->Unit )?=null
) {
///////////////////////////////////////////////////////////////////////////////////////////
// Restant à faire :
// Affichage décimale entière sans le 0 --> OK mais pas encore au premier affichage
// perte de focus de l'edittext à gérer (disparition clignotement ou puce dans certains cas)
val top_element = 8.dp // hauteur pour laisser de la place entre le texte curseur et le slider + icônes
var position_slider by remember { mutableStateOf(valeur_slider) } // position du slider
var text by remember { mutableStateOf(get_valeur_pour_slider(position_slider.toString(), intervalle_valeur,increment_valeur).toString()) } // texte du BasicTextField (pour modification manuelle)
var is_text_erreur by remember { mutableStateOf(false) } // erreur sur saisie valeur (pour affichage texte en rouge)
var largeur_texte_label: Dp by remember { mutableStateOf(0.dp) } // largeur BasicTextField pour positionnement en vertical
val densite_locale = LocalDensity.current
var activity = LocalContext.current as Activity
// Bonne position en fonction de l'intervalle de données
position_slider = get_valeur_pour_slider(position_slider.toString(), intervalle_valeur,increment_valeur)
Column() {
Row( verticalAlignment = Alignment.CenterVertically) {
////////////////////////////////
// Icône gauche (-) clickable
Icon(
imageVector = Icons.Rounded.Remove , // image de l'icône
contentDescription = null,
modifier = Modifier
.padding(top = top_element) // ajustement pour se caler avec le Slider
.clickable { // gestion du click sur l'icône
position_slider -= increment_valeur // Décrémente
// vérifie que la valeur ne dépasse les bornes min et max
position_slider = get_valeur_pour_slider(position_slider.toString(),intervalle_valeur,increment_valeur)
// mise en forme du texte qui indiquera la valeur
text = position_slider.toString().removeSuffix(".0")
// indique aux autres composants qu'il y a une erreur de valeur (pour BasicTextField)
is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)
// Renvoi nouvelle valeur (si pas d'erreur)
if (!is_text_erreur) {
if (callback_retour_valeur != null) {
callback_retour_valeur(text)
}
}
cacher_clavier_slider(activity) // Cacher clavier
}
)
Box() {
// Calcul intermédiaire pour définir offset texte à afficher
// on décale en fonction de la grosseur du curseur
// 10.dp correspond à ThumbRadius dans Slider.kt -> marge entre la box et le track
val decalage_texte_a_afficher = 12.dp
// décalage final pour positionner l'élément texte
val decalage_texte_final_a_afficher = decalage_texte_a_afficher -( largeur_texte_label/2)
// Callage vis à vis du curseur
val ratio_slider : Float = (position_slider-intervalle_valeur.start)/(intervalle_valeur.endInclusive - intervalle_valeur.start)
// offset_slider = taille du slider * ratio séléection du slider
val offset_slider : Dp = (width_slider-20.dp).times(ratio_slider) // pourqoi le -20dp ???
// Callage final
var decalage_slider : Dp = decalage_texte_final_a_afficher + offset_slider
if (decalage_slider<5.dp) {decalage_slider=5.dp}
val offset_x = decalage_slider
//////////////////////////////
// Basic TextField
var coul_texte by remember {mutableStateOf( Color.Unspecified) }
if (is_text_erreur) {
coul_texte = Color.Red
} else {
coul_texte = Color.Unspecified
}
BasicTextField(
value = text,
onValueChange = {
text = traite_saisie_slider(text,it) // permet de rendre éditable le composant + contrôle saisie
// met à jour la position du slider en fonction de la taille du texte
position_slider = get_valeur_pour_slider(text, intervalle_valeur,increment_valeur)
// indique si erreur detectée dans la saisie (pour affichage en rouge...)
is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)
// ici pas de renvoi de valeur (on passe par la vailidation du clavier)
},
textStyle = LocalTextStyle.current.copy(color = coul_texte),
modifier = Modifier
.width(IntrinsicSize.Min) // taille minimale de la zone de texte
.padding(start = offset_x) // positionnement du composant en fonction du curseur du Slider (via Offset)
.background(color = Color.Transparent)
.onGloballyPositioned {
// Récupération taille de la zone de texte (pour calcul offset et placement au milieu du curseur du slider
largeur_texte_label = with(densite_locale) { it.size.width.toDp() }
}
,
singleLine = true, // une seule ligne
// Clavier numérique seulement
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number,imeAction = ImeAction.Default),
// Clavier : action si appui sur 'OK' du clavier numérique
keyboardActions = KeyboardActions(
onDone = {
// renvoi valeur si correcte seulement
is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)
if (!is_text_erreur) {
if (callback_retour_valeur != null) {
callback_retour_valeur(text)
}
}
// cacher le clavier
cacher_clavier_slider(activity)
}
)
)
//////////////////////////
// le slider
Slider(
// valeur du slider
value = position_slider,
// enregistrement de la veleur du slider
onValueChange = {
position_slider = get_valeur_pour_slider(it.toString(), intervalle_valeur,increment_valeur)
text = position_slider.toString().removeSuffix(".0")
is_text_erreur =!is_valeur_slider_correcte(text, intervalle_valeur)
// Renvoi valeur
if (!is_text_erreur) {
if (callback_retour_valeur != null) {
callback_retour_valeur(text)
}
}
// Cacher clavier
cacher_clavier_slider(activity)
},
// Intervalle de valeur min max
valueRange= intervalle_valeur ,
// largeur du slider
modifier = Modifier
.padding(top = top_element)
.width(width_slider)
)
}
///////////////////////////////////
// Icône droite (+) clickable
Icon(
imageVector = Icons.Rounded.Add ,
contentDescription = null,
modifier = Modifier
.padding(top = top_element)
.clickable {
position_slider += increment_valeur
position_slider = get_valeur_pour_slider(position_slider.toString(),intervalle_valeur,increment_valeur )
text = position_slider.toString().removeSuffix(".0")
is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)
if (!is_text_erreur) {
if (callback_retour_valeur != null) {
callback_retour_valeur(text)
}
}
cacher_clavier_slider(activity) // Cacher clavier
}
)
}
}
}
/**
* Construction générale de l'activité
*/
@Composable
fun Sommaire_slider() {
var valeur_texte_saisie1 by remember { mutableStateOf("") }
Column() {
Text(text = "Test Slider")
Spacer(modifier = Modifier.height(42.dp))
Row (
modifier=Modifier.fillMaxWidth()
) {
Spacer(modifier = Modifier.width(20.dp))
slider_XH(valeur_slider = 0f,increment_valeur = 1f)
Spacer(modifier = Modifier.width(10.dp))
slider_XH(valeur_slider = 20f)
}
Spacer(modifier = Modifier.height(8.dp))
Row (
modifier=Modifier.fillMaxWidth()
) {
Spacer(modifier = Modifier.width(20.dp))
slider_XH(
valeur_slider = 1.9f,
increment_valeur = 0.1f,
width_slider = 300.dp,
intervalle_valeur = -12f..12f) {
valeur_texte_saisie1=it
}
}
Spacer(modifier = Modifier.height(16.dp))
Row() {
Text(text = " Valeur renseignée : $valeur_texte_saisie1")
}
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview2() {
Textview_test1Theme {
Sommaire_slider()
}
}