{"id":4094,"date":"2022-12-20T17:53:34","date_gmt":"2022-12-20T16:53:34","guid":{"rendered":"http:\/\/blogperso.union31.fr\/?p=4094"},"modified":"2022-12-21T00:30:05","modified_gmt":"2022-12-20T23:30:05","slug":"kotlin-personnaliser-ses-composants-slider","status":"publish","type":"post","link":"https:\/\/blogperso.union31.fr\/?p=4094","title":{"rendered":"KOTLIN : Personnaliser ses composants (Slider)"},"content":{"rendered":"\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Sommaire<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#I_Introduction\" >I Introduction<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II_Notre_composant\" >II Notre composant<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II1_presentation_generale_du_composant\" >II.1 pr\u00e9sentation g\u00e9n\u00e9rale du composant<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II2_Parametres_dentree\" >II.2 Param\u00e8tres d&rsquo;entr\u00e9e<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II3_Structure_generale_%C2%AB_Compose_%C2%BB_du_composant\" >II.3 Structure g\u00e9n\u00e9rale \u00ab\u00a0Compose\u00a0\u00bb du composant<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II4_En_detail_%E2%80%A6\" >II.4 En d\u00e9tail &#8230;<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II41_les_variables_%C2%AB_globales_%C2%BB_du_composant\" >II.4.1 les variables \u00ab\u00a0globales\u00a0\u00bb du composant<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II42_Code_du_bouton_%C2%AB_%E2%80%93_%C2%BB\" >II.4.2 Code du bouton \u00ab\u00a0&#8211;\u00a0\u00bb<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II43_Code_du_bouton_%C2%AB_%C2%BB\" >II.4.3 Code du bouton \u00ab\u00a0+\u00a0\u00bb<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II44_Code_du_%C2%AB_Box_%C2%BB_qui_va_contenir_le_%C2%AB_BasicTextField_%C2%BB_et_le_Slider\" >II.4.4 Code du \u00ab\u00a0Box\u00a0\u00bb qui va contenir le \u00ab\u00a0BasicTextField\u00a0\u00bb et le Slider<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II45_Code_du_%C2%AB_TextBasicField_%C2%BB\" >II.4.5 Code du \u00ab\u00a0TextBasicField\u00a0\u00bb<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#II46_Code_du_%C2%AB_Slider_%C2%BB\" >II.4.6 Code du \u00ab\u00a0Slider\u00a0\u00bb<\/a><\/li><\/ul><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/blogperso.union31.fr\/?p=4094\/#III_Code_complet\" >III Code complet<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"I_Introduction\"><\/span>I Introduction<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Ici un deuxi\u00e8me exemple montrant comment cr\u00e9er son propre composant sous Compose avec Kotlin. <\/p>\n\n\n\n<p>L&rsquo;id\u00e9e de ce composant est de pouvoir renseigner rapidement une valeur. Pour cela, ce composant sera compos\u00e9 :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>d&rsquo;un Slider permettant un choix rapide de la valeur ;<\/li>\n\n\n\n<li>de 2 boutons sur les c\u00f4t\u00e9s du Slider pour ajuster finement la valeur (par incr\u00e9ment ou d\u00e9cr\u00e9ment) ;<\/li>\n\n\n\n<li>d&rsquo;un affichage de la valeur au dessus du curseur de s\u00e9lection.<\/li>\n<\/ul>\n\n\n\n<p>Il sera \u00e9galement possible de d\u00e9finir une valeur manuellement en cliquant l&rsquo;affichage de la valeur.<\/p>\n\n\n\n<p>Enfin il sera int\u00e9gr\u00e9 des contr\u00f4les comme :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>valeur min et max ;<\/li>\n\n\n\n<li>la valeur de l&rsquo;incr\u00e9ment ;<\/li>\n\n\n\n<li>affichage en rouge quand la valeur saisie manuellement d\u00e9passe les valeurs min et max ;<\/li>\n\n\n\n<li>le contr\u00f4le de la saisie.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II_Notre_composant\"><\/span>II Notre composant<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II1_presentation_generale_du_composant\"><\/span>II.1 pr\u00e9sentation g\u00e9n\u00e9rale du composant<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p>Ci-dessous l&rsquo;aspect g\u00e9n\u00e9ral du composant et ses principales fonctionnalit\u00e9s :<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image.png\" alt=\"\" class=\"wp-image-4105\" width=\"642\" height=\"517\" srcset=\"https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image.png 989w, https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-300x242.png 300w, https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-768x619.png 768w\" sizes=\"auto, (max-width: 642px) 100vw, 642px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II2_Parametres_dentree\"><\/span>II.2 Param\u00e8tres d&rsquo;entr\u00e9e<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p>Ce composant aura les param\u00e8tres d&rsquo;entr\u00e9e suivants, tous d\u00e9finis par d\u00e9faut :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">\/**\n * Composant personnalis\u00e9 utilisant un Slider et bouton + et - pour changer la valeur\n *   Affichage de la valeur au dessus du curseur et possibilit\u00e9 de modifier la valeur manuellement\n *\n * @param increment_valeur Tableau de 2 valeurs indiquant plage min et max\n * @param valeur_slider valeur par defaut du slider\n * @param intervalle_valeur valeur d'incr\u00e9ment\/d\u00e9cr\u00e9ment via les boutons + et -\n * @param width_slider largeur du composant\n * @param callback_retour_valeur fonction de retour une fois que la valeur change\n *\/\n@Composable\nfun slider_XH(\n    intervalle_valeur :ClosedFloatingPointRange&lt;Float> = 0f..25f,\n    valeur_slider: Float = 1f,\n    increment_valeur : Float = 0.1f,\n    width_slider : Dp = 115.dp,\n    callback_retour_valeur : ( (valeur_str:String)->Unit )?=null\n) {\n<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II3_Structure_generale_%C2%AB_Compose_%C2%BB_du_composant\"><\/span>II.3 Structure g\u00e9n\u00e9rale \u00ab\u00a0Compose\u00a0\u00bb du composant<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p>La structure g\u00e9n\u00e9rale du composant sera la suivante :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">@Composable\nfun slider_XH(\n    intervalle_valeur :ClosedFloatingPointRange&lt;Float> = 0f..25f,\n    valeur_slider: Float = 1f,\n    increment_valeur : Float = 0.1f,\n    width_slider : Dp = 115.dp,\n    callback_retour_valeur : ( (valeur_str:String)->Unit )?=null\n) {\n\n    Column() {\n        Row( verticalAlignment = Alignment.CenterVertically) {\n\n            Icon(\n                imageVector = Icons.Rounded.Remove ,\n                contentDescription = null,\n                \n            )\n            Box() {\n                BasicTextField(\n                    value = text,\n                    onValueChange = {                      \n                        },\n                )\n                Slider(\n                    \/\/ valeur du slider\n                    value = position_slider,\n                )\n            }\n            Icon(\n                imageVector = Icons.Rounded.Add ,\n                contentDescription = null,         \n            )\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p>Le \u00ab\u00a0Slider\u00a0\u00bb et le \u00ab\u00a0BasicTextField\u00a0\u00bb sont int\u00e9gr\u00e9s dans une \u00ab\u00a0Box\u00a0\u00bb. Ceci pour pouvoir  : <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>bouger la position du \u00ab\u00a0BasicTextField\u00a0\u00bb plus facilement en horizontal (\u00e0 chaque que la valeur du Slider change),<\/li>\n\n\n\n<li>de m\u00eame pour descendre l\u00e9g\u00e8rement le slider en vertical (une fois).<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II4_En_detail_%E2%80%A6\"><\/span>II.4 En d\u00e9tail &#8230;<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<h4 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II41_les_variables_%C2%AB_globales_%C2%BB_du_composant\"><\/span>II.4.1 les variables \u00ab\u00a0globales\u00a0\u00bb du composant<span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n\n<p>Concernant les variables \u00ab\u00a0globales\u00a0\u00bb utilis\u00e9es par les composants\u00a0\u00bb, elles seront les suivantes :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">@Composable\nfun slider_XH(\n    intervalle_valeur :ClosedFloatingPointRange&lt;Float> = 0f..25f,\n    valeur_slider: Float = 1f,\n    increment_valeur : Float = 0.1f,\n    width_slider : Dp = 115.dp,\n    callback_retour_valeur : ( (valeur_str:String)->Unit )?=null\n) {\n    val top_element = 8.dp \/\/ hauteur pour laisser de la place entre le texte curseur et le slider + ic\u00f4nes\n\n    var position_slider by remember { mutableStateOf(valeur_slider) } \/\/ position du slider\n    var text by remember { mutableStateOf(get_valeur_pour_slider(position_slider.toString(), intervalle_valeur,increment_valeur).toString()) } \/\/ texte du BasicTextField (pour modification manuelle)\n    var is_text_erreur by remember { mutableStateOf(false) }   \/\/ erreur sur saisie valeur (pour affichage texte en rouge)\n    var largeur_texte_label: Dp by remember { mutableStateOf(0.dp) } \/\/ largeur BasicTextField pour positionnement en vertical\n\n    val densite_locale = LocalDensity.current\n    var activity = LocalContext.current as Activity\n    \n    \/\/ .......\n<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II42_Code_du_bouton_%C2%AB_%E2%80%93_%C2%BB\"><\/span>II.4.2 Code du bouton \u00ab\u00a0&#8211;\u00a0\u00bb <span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n\n<p>Nous allons voir la partie du code qui r\u00e9git le bouton gauche qui permettra de d\u00e9cr\u00e9menter la valeur du \u00ab\u00a0Slider\u00a0\u00bb<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-1.png\" alt=\"\" class=\"wp-image-4146\" width=\"542\" height=\"123\" srcset=\"https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-1.png 758w, https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-1-300x68.png 300w, https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-1-750x172.png 750w\" sizes=\"auto, (max-width: 542px) 100vw, 542px\" \/><\/figure>\n\n\n\n<p>Ce qui donne :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">           \n            \/\/ .......\n\n            Icon(\n                imageVector = Icons.Rounded.Remove ,                \/\/ image de l'ic\u00f4ne\n                contentDescription = null,\n                modifier = Modifier\n                    .padding(top = top_element)                     \/\/ ajustement pour se caler avec le Slider\n                    .clickable {                                    \/\/ gestion du click sur l'ic\u00f4ne\n                        position_slider -= increment_valeur \/\/ D\u00e9cr\u00e9mente\n                        \/\/ v\u00e9rifie que la valeur ne d\u00e9passe les bornes min et max\n                        position_slider = get_valeur_pour_slider(position_slider.toString(),intervalle_valeur,increment_valeur)\n                        \/\/ mise en forme du texte qui indiquera la valeur\n                        text = position_slider.toString().removeSuffix(\".0\")\n                        \/\/ indique aux autres composants qu'il y a une erreur de valeur (pour BasicTextField)\n                        is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)\n                        \/\/ Renvoi nouvelle valeur (si pas d'erreur)\n                        if (!is_text_erreur) {\n                            if (callback_retour_valeur != null) {\n                                callback_retour_valeur(text)\n                            }\n                        }\n                        cacher_clavier_slider(activity) \/\/ Cacher clavier\n                    }\n            )<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Dans le code ci-dessus il est fait appel \u00e0 trois fonctions.<\/p>\n\n\n\n<p>La premi\u00e8re fonction s&rsquo;assure d&rsquo;avoir une valeur dans l&rsquo;intervalle donn\u00e9 ou 0.f dans le pire des cas.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">\/**\n * V\u00e9rifie que la valeur \u00e0 traiter soit bien dans un intervalle donn\u00e9\n *Si non, renvoie dans les limites min ou ax de l'intervalle\n * @param valeur Valeur \u00e0 traiter\n * @param intervalle Intervalle dans lequel la valeur doit \u00eatre situ\u00e9e\n * @return donn\u00e9e [Float] qui sera dans le bon intervalle donn\u00e9 par [intervalle]\n *\/\nfun get_valeur_pour_slider(valeur:String, intervalle :ClosedFloatingPointRange&lt;Float>, increment_valeur: Float ) : Float {\n    var val_retour : Float = 0f\n\n    if (valeur.isBlank())  return val_retour\n\n    try {\n        val_retour = valeur.toFloat()\n    } catch (e : Exception) {\n        return 0f\n    }\n\n    val valeur_a_traiter = valeur.toFloat()\n    if (valeur_a_traiter > intervalle.endInclusive) return intervalle.endInclusive\n    if (valeur_a_traiter&lt;intervalle.start) return intervalle.start\n    \n    var increment_str = increment_valeur.toString()\n    increment_str = increment_str.replaceBefore(\".\",\"0\")\n    val nouveau_increment = increment_str.toFloat()\n\n    var nb_centieme = 1\n    if (nouveau_increment&lt;1f) nb_centieme=10\n    if (nouveau_increment&lt;0.1f) nb_centieme=100\n    if (nouveau_increment&lt;0.01f) nb_centieme=1000\n    if (nouveau_increment==0f) nb_centieme=1\n    \n    val_retour= (val_retour*nb_centieme).roundToInt() \/ (nb_centieme.toFloat())\n    \n    return val_retour\n}<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>La deuxi\u00e8me fonction indique juste si la valeur est bien dans l&rsquo;intervalle souhait\u00e9 :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">\/**\n * Indique si la valeur renseign\u00e9e est correcte par rapport aux valeurs min max initiales\n *\/\nfun is_valeur_slider_correcte(valeur:String, intervalle :ClosedFloatingPointRange&lt;Float>):Boolean {\n    var val_retour : Boolean = true\n\n    if (valeur.isBlank())  return false\n    \/\/ test si valeur de type float\n    try {\n        val test = valeur.toFloat()\n    } catch (e : Exception) {\n        return false\n    }\n    \/\/ test si dans intervalle\n    val valeur_a_traiter = valeur.toFloat()\n    if (valeur_a_traiter > intervalle.endInclusive) return false\n    if (valeur_a_traiter &lt; intervalle.start) return false\n\n    return val_retour\n}<\/code><\/pre>\n\n\n\n<p>Enfin la troisi\u00e8me fonction s&rsquo;occupe de cacher le clavier :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">\/**\n *  Permet de cacher le clavier\n *\/\nfun cacher_clavier_slider(activity:Activity) {\n    val imm: InputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager\n    var view: View? = activity.currentFocus\n    if (view == null) {\n        view = View(activity);\n    }\n    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);\n}<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II43_Code_du_bouton_%C2%AB_%C2%BB\"><\/span>II.4.3 Code du bouton \u00ab\u00a0+\u00a0\u00bb <span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-2.png\" alt=\"\" class=\"wp-image-4147\" width=\"527\" height=\"101\" srcset=\"https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-2.png 673w, https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-2-300x58.png 300w\" sizes=\"auto, (max-width: 527px) 100vw, 527px\" \/><\/figure>\n\n\n\n<p>Le code est quasiment identique \u00e0 celui du bouton \u00ab\u00a0&#8211;\u00a0\u00bb , seule change la ligne qui concerne l&rsquo;incr\u00e9ment de la valeur &#8230;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">            \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\n            \/\/ Ic\u00f4ne droite (+) clickable\n            Icon(\n                imageVector = Icons.Rounded.Add ,\n                contentDescription = null,\n                modifier = Modifier\n                    .padding(top = top_element)\n                    .clickable {\n                        position_slider += increment_valeur\n                        position_slider = get_valeur_pour_slider(position_slider.toString(),intervalle_valeur,increment_valeur )\n                        text = position_slider.toString().removeSuffix(\".0\")\n                        is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)\n                        if (!is_text_erreur) {\n                            if (callback_retour_valeur != null) {\n                                callback_retour_valeur(text)\n                            }\n                        }\n                        cacher_clavier_slider(activity) \/\/ Cacher clavier\n                    }\n            )<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II44_Code_du_%C2%AB_Box_%C2%BB_qui_va_contenir_le_%C2%AB_BasicTextField_%C2%BB_et_le_Slider\"><\/span>II.4.4 Code du \u00ab\u00a0Box\u00a0\u00bb qui va contenir le \u00ab\u00a0BasicTextField\u00a0\u00bb et le Slider<span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n\n<p>Cela va donc concerner cette zone :<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-3.png\" alt=\"\" class=\"wp-image-4153\" width=\"395\" height=\"92\" srcset=\"https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-3.png 693w, https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-3-300x70.png 300w\" sizes=\"auto, (max-width: 395px) 100vw, 395px\" \/><\/figure>\n\n\n\n<p>Pour la structure g\u00e9n\u00e9rale qui est la suivante : <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">            Box() {\n                BasicTextField(\n                    value = text,\n                    onValueChange = {                      \n                        },\n                )\n                Slider(\n                    \/\/ valeur du slider\n                    value = position_slider,\n                )\n            }<\/code><\/pre>\n\n\n\n<p>Par d\u00e9faut les composants \u00ab\u00a0Slider\u00a0\u00bb et \u00ab\u00a0BasicTextField\u00a0\u00bb ne sont pas pr\u00e9sent\u00e9s comme on le voit au dessus mais en superposition comme ci-dessous :<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-4.png\" alt=\"\" class=\"wp-image-4159\" width=\"396\" height=\"36\" srcset=\"https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-4.png 987w, https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-4-300x28.png 300w, https:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/image-4-768x71.png 768w\" sizes=\"auto, (max-width: 396px) 100vw, 396px\" \/><\/figure>\n\n\n\n<p>Ainsi, il va falloir:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>d\u00e9caler le \u00ab\u00a0Slider\u00a0\u00bb vers le bas ainsi que les boutons pour que le tout soit align\u00e9,<\/li>\n\n\n\n<li>positionner le \u00ab\u00a0BasicTextFied\u00a0\u00bb sur la m\u00eame position lat\u00e9rale que la puce du \u00ab\u00a0Slider\u00a0\u00bb <\/li>\n<\/ul>\n\n\n\n<p>Nous aurons donc un d\u00e9but de code d\u00e9di\u00e9 \u00e0 la position du \u00ab\u00a0BasicTextField\u00a0\u00bb comme suivant :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">            Box() {\n\n                \/\/ Calcul interm\u00e9diaire pour d\u00e9finir offset texte \u00e0 afficher\n\n                \/\/ on d\u00e9cale en fonction de la grosseur du curseur\n                \/\/   10.dp correspond \u00e0 ThumbRadius dans Slider.kt -> marge entre la box et le track\n                val decalage_texte_a_afficher = 12.dp\n                \/\/ d\u00e9calage final pour positionner l'\u00e9l\u00e9ment texte\n                val decalage_texte_final_a_afficher = decalage_texte_a_afficher -( largeur_texte_label\/2)\n                \/\/ Callage vis \u00e0 vis du curseur\n                val ratio_slider : Float    = (position_slider-intervalle_valeur.start)\/(intervalle_valeur.endInclusive - intervalle_valeur.start)\n                \/\/  offset_slider = taille du slider * ratio s\u00e9l\u00e9ection du slider\n                val offset_slider : Dp = (width_slider-20.dp).times(ratio_slider) \/\/ pourqoi le -20dp ???\n                \/\/ Callage final\n                var decalage_slider : Dp = decalage_texte_final_a_afficher + offset_slider\n                if (decalage_slider&lt;5.dp) {decalage_slider=5.dp}\n                val offset_x = decalage_slider\n\n                \/\/....<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II45_Code_du_%C2%AB_TextBasicField_%C2%BB\"><\/span>II.4.5 Code du \u00ab\u00a0TextBasicField\u00a0\u00bb<span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n\n<p>Tout avant le bloc du composant, il est d\u00e9fini une variable permettant d&rsquo;indiquer si le texte doit \u00eatre affich\u00e9 en rouge en cas d&rsquo;erreur d\u00e9tect\u00e9 lors d&rsquo;une saisie manuelle :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">                \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\n                \/\/  Basic TextField\n                var coul_texte by remember {mutableStateOf( Color.Unspecified) }\n                if (is_text_erreur) {\n                    coul_texte = Color.Red\n                } else {\n                    coul_texte = Color.Unspecified\n                }<\/code><\/pre>\n\n\n\n<p>Et il vient le code du composant :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">                BasicTextField(\n                    value = text,\n                    onValueChange = {\n                        text = traite_saisie_slider(text,it)   \/\/ permet de rendre \u00e9ditable le composant + contr\u00f4le saisie\n                        \/\/ met \u00e0 jour la position du slider en fonction de la taille du texte\n                        position_slider = get_valeur_pour_slider(text, intervalle_valeur,increment_valeur)\n                        \/\/ indique si erreur detect\u00e9e dans la saisie (pour affichage en rouge...)\n                        is_text_erreur =  !is_valeur_slider_correcte(text, intervalle_valeur)\n                        \/\/ ici pas de renvoi de valeur (on passe par la vailidation du clavier)\n                        },\n                    textStyle = LocalTextStyle.current.copy(color = coul_texte),\n                    modifier = Modifier\n                        .width(IntrinsicSize.Min)    \/\/ taille minimale de la zone de texte\n                        .padding(start = offset_x)   \/\/ positionnement du composant en fonction du curseur du Slider (via Offset)\n                        .background(color = Color.Transparent)\n                        .onGloballyPositioned {\n                            \/\/ R\u00e9cup\u00e9ration taille de la zone de texte (pour calcul offset et placement au milieu du curseur du slider\n                            largeur_texte_label = with(densite_locale) { it.size.width.toDp() }\n                        }\n                       ,\n                    singleLine = true,  \/\/ une seule ligne\n                    \/\/ Clavier num\u00e9rique seulement\n                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number,imeAction = ImeAction.Default),\n                    \/\/ Clavier : action si appui sur 'OK' du clavier num\u00e9rique\n                    keyboardActions = KeyboardActions(\n                        onDone = {\n                            \/\/ renvoi valeur si correcte seulement\n                            is_text_erreur =  !is_valeur_slider_correcte(text, intervalle_valeur)\n                            if (!is_text_erreur) {\n                                if (callback_retour_valeur != null) {\n                                    callback_retour_valeur(text)\n                                }\n                            }\n                            \/\/ cacher le clavier\n                            cacher_clavier_slider(activity)\n                        }\n                    )\n                )\n<\/code><\/pre>\n\n\n\n<p>Plusieurs fonctions sont utilis\u00e9es.<\/p>\n\n\n\n<p>Celle qui permet de v\u00e9rifier la saisie d&rsquo;un nombre r\u00e9el, positif ou n\u00e9gatif :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">\/**\n * v\u00e9rifie forme texte saisie pour un nombre reel sign\u00e9 ou pas\n *  prend en compte la saisie caract\u00e8re par caract\u00e8re\n *    \"-\"     --> OK\n *    \"-0\"    --> OK\n *    \"-0.\"   --> OK\n *    \"-0.1\"  --> OK\n *    \"-0.1.  --> KO\n * @param ancien_texte\n * @param texte\n * @return\n *\/\nfun traite_saisie_slider(ancien_texte:String, texte:String):String {\n    var texte_traite = texte\n\n    \/\/ Remplacement caract\u00e8re\n    texte_traite = texte_traite.replace(\",\",\".\")\n\n    \/\/ pattern pour test nombre r\u00e9\u00e9l au fur et \u00e0 mesure (ex 10.25 ou -12.78)\n    var regex = \"^((-?)(-?\\\\d+\\\\.?\\\\d*)?)\".toRegex() \/\/ g\u00e8re la s\u00e9quence manuelle de saisie au fur et \u00e0 mesure\n\n    \/\/ Si \u00e7a ne matche pas alors renvoi ancien texte\n    if (!texte_traite.matches(regex)) {\n        if (!texte_traite.equals(\"\")) {\n            texte_traite = ancien_texte\n        }\n    }\n    return texte_traite\n}<\/code><\/pre>\n\n\n\n<p>Gr\u00e2ce \u00e0 cette fonction nous pouvons avoir le contr\u00f4le utilisateur suivant :<\/p>\n\n\n\n<p><\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/Slider_XH_clavier.gif\" alt=\"\" class=\"wp-image-4178\" width=\"291\" height=\"403\"\/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"II46_Code_du_%C2%AB_Slider_%C2%BB\"><\/span>II.4.6 Code du \u00ab\u00a0Slider\u00a0\u00bb<span class=\"ez-toc-section-end\"><\/span><\/h4>\n\n\n\n<p>Rien de particulier dans l&rsquo;\u00e9criture du Slider : r\u00e9cup\u00e9ration de la valeur du slider dans variable \u00ab\u00a0globale\u00a0\u00bb pour \u00eatre utilis\u00e9e par d&rsquo;autres composants, d\u00e9finition de la plage min et max et d\u00e9finition de la largeur et position haut du Slider :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">                Slider(\n                    \/\/ valeur du slider\n                    value = position_slider,\n                    \/\/ enregistrement de la veleur du slider\n                    onValueChange = {\n                        position_slider = get_valeur_pour_slider(it.toString(), intervalle_valeur,increment_valeur)\n                        text = position_slider.toString().removeSuffix(\".0\")\n                        is_text_erreur =!is_valeur_slider_correcte(text, intervalle_valeur)\n\n                        \/\/ Renvoi valeur\n                        if (!is_text_erreur) {\n                            if (callback_retour_valeur != null) {\n                                callback_retour_valeur(text)\n                            }\n                        }\n                        \/\/ Cacher clavier\n                        cacher_clavier_slider(activity)\n                    },\n                    \/\/ Intervalle de valeur min max\n                    valueRange= intervalle_valeur ,\n                    \/\/ largeur du slider\n                    modifier = Modifier\n                        .padding(top = top_element)\n                        .width(width_slider)\n                )<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"III_Code_complet\"><\/span>III Code complet<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Ci dessous le code complet de l&rsquo;activit\u00e9 qui permet d&rsquo;obtenir  :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>2 sliders sur une ligne,<\/li>\n\n\n\n<li>1 slider qui prend la place d&rsquo;une ligne et qui renseigne le texte plac\u00e9 en dessous.<\/li>\n<\/ul>\n\n\n\n<p>Exemple du rendu de l&rsquo;activit\u00e9 :<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogperso.union31.fr\/wp-content\/uploads\/2022\/12\/Slider_XH_activity.gif\" alt=\"\" class=\"wp-image-4186\" width=\"333\" height=\"581\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Code complet de l&rsquo;activit\u00e9 :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"kotlin\" class=\"language-kotlin\">package com.example.slider_XH\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.KeyEvent\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.text.BasicTextField\nimport androidx.compose.foundation.text.KeyboardActions\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material.*\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.rounded.Add\nimport androidx.compose.material.icons.rounded.Remove\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.key.onKeyEvent\nimport androidx.compose.ui.layout.onGloballyPositioned\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalDensity\nimport androidx.compose.ui.text.input.ImeAction\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport com.example.textview_test1.ui.theme.Textview_test1Theme\nimport kotlin.math.roundToInt\n\nclass test_SliderActivity : ComponentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContent {\n            Textview_test1Theme {\n                Surface(\n                    modifier = Modifier.fillMaxSize(),\n                    color = MaterialTheme.colors.background\n                ) {\n                    Sommaire_slider()\n                }\n            }\n        }\n    }\n}\n\n\n\n\/**\n * V\u00e9rifie que la valeur \u00e0 traiter soit bien dans un intervalle donn\u00e9\n *Si non, renvoie dans les limites min ou ax de l'intervalle\n * @param valeur Valeur \u00e0 traiter\n * @param intervalle Intervalle dans lequel la valeur doit \u00eatre situ\u00e9e\n * @return donn\u00e9e [Float] qui sera dans le bon intervalle donn\u00e9 par [intervalle]\n *\/\nfun get_valeur_pour_slider(valeur:String, intervalle :ClosedFloatingPointRange&lt;Float>, increment_valeur: Float ) : Float {\n    var val_retour : Float = 0f\n\n    if (valeur.isBlank())  return val_retour\n\n    try {\n        val_retour = valeur.toFloat()\n    } catch (e : Exception) {\n        return 0f\n    }\n\n    val valeur_a_traiter = valeur.toFloat()\n    if (valeur_a_traiter > intervalle.endInclusive) return intervalle.endInclusive\n    if (valeur_a_traiter&lt;intervalle.start) return intervalle.start\n\n    var increment_str = increment_valeur.toString()\n    increment_str = increment_str.replaceBefore(\".\",\"0\")\n    val nouveau_increment = increment_str.toFloat()\n\n    var nb_centieme = 1\n    if (nouveau_increment&lt;1f) nb_centieme=10\n    if (nouveau_increment&lt;0.1f) nb_centieme=100\n    if (nouveau_increment&lt;0.01f) nb_centieme=1000\n    if (nouveau_increment==0f) nb_centieme=1\n\n    val_retour= (val_retour*nb_centieme).roundToInt() \/ (nb_centieme.toFloat())\n\n    return val_retour\n}\n\n\/**\n * Indique si la valeur renseign\u00e9e est correcte par rapport aux valeurs min max initiales\n *\/\nfun is_valeur_slider_correcte(valeur:String, intervalle :ClosedFloatingPointRange&lt;Float>):Boolean {\n    var val_retour : Boolean = true\n\n    if (valeur.isBlank())  return false\n    \/\/ test si valeur de type float\n    try {\n        val test = valeur.toFloat()\n    } catch (e : Exception) {\n        return false\n    }\n    \/\/ test si dans intervalle\n    val valeur_a_traiter = valeur.toFloat()\n    if (valeur_a_traiter > intervalle.endInclusive) return false\n    if (valeur_a_traiter &lt; intervalle.start) return false\n\n    return val_retour\n}\n\n\/**\n *  Permet de cacher le clavier\n *\/\nfun cacher_clavier_slider(activity:Activity) {\n    val imm: InputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager\n    var view: View? = activity.currentFocus\n    if (view == null) {\n        view = View(activity);\n    }\n    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);\n}\n\n\/**\n * v\u00e9rifie forme texte saisie pour un nombre reel sign\u00e9 ou pas\n *  prend en compte la saisie caract\u00e8re par caract\u00e8re\n *    \"-\"     --> OK\n *    \"-0\"    --> OK\n *    \"-0.\"   --> OK\n *    \"-0.1\"  --> OK\n *    \"-0.1.  --> KO\n * @param ancien_texte\n * @param texte\n * @return\n *\/\nfun traite_saisie_slider(ancien_texte:String, texte:String):String {\n    var texte_traite = texte\n\n    \/\/ Remplacement caract\u00e8re\n    texte_traite = texte_traite.replace(\",\",\".\")\n\n    \/\/ pattern pour test nombre r\u00e9\u00e9l au fur et \u00e0 mesure (ex 10.25 ou -12.78)\n    var regex = \"^((-?)(-?\\\\d+\\\\.?\\\\d*)?)\".toRegex() \/\/ g\u00e8re la s\u00e9quence manuelle de saisie au fur et \u00e0 mesure\n\n    \/\/ Si \u00e7a ne matche pas alors renvoi ancien texte\n    if (!texte_traite.matches(regex)) {\n        if (!texte_traite.equals(\"\")) {\n            texte_traite = ancien_texte\n        }\n    }\n    return texte_traite\n}\n\n\n\/**\n * Composant personnalis\u00e9 utilisant un Slider et bouton + et - pour changer la valeur\n *   Affichage de la valeur au dessus du curseur et possibilit\u00e9 de modifier la valeur manuellement\n *\n * @param increment_valeur Tableau de 2 valeurs indiquant plage min et max\n * @param valeur_slider valeur par defaut du slider\n * @param intervalle_valeur valeur d'incr\u00e9ment\/d\u00e9cr\u00e9ment via les boutons + et -\n * @param width_slider largeur du composant\n * @param callback_retour_valeur fonction de retour une fois que la valeur change\n *\/\n@Composable\nfun slider_XH(\n    intervalle_valeur :ClosedFloatingPointRange&lt;Float> = 0f..25f,\n    valeur_slider: Float = 1f,\n    increment_valeur : Float = 0.1f,\n    width_slider : Dp = 115.dp,\n    callback_retour_valeur : ( (valeur_str:String)->Unit )?=null\n) {\n\n    \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\n    \/\/  Restant \u00e0 faire :\n    \/\/     Affichage d\u00e9cimale enti\u00e8re sans le 0 --> OK mais pas encore au premier affichage\n    \/\/     perte de focus de l'edittext \u00e0 g\u00e9rer (disparition clignotement ou puce dans certains cas)\n\n    val top_element = 8.dp \/\/ hauteur pour laisser de la place entre le texte curseur et le slider + ic\u00f4nes\n\n    var position_slider by remember { mutableStateOf(valeur_slider) } \/\/ position du slider\n    var text by remember { mutableStateOf(get_valeur_pour_slider(position_slider.toString(), intervalle_valeur,increment_valeur).toString()) } \/\/ texte du BasicTextField (pour modification manuelle)\n    var is_text_erreur by remember { mutableStateOf(false) }   \/\/ erreur sur saisie valeur (pour affichage texte en rouge)\n    var largeur_texte_label: Dp by remember { mutableStateOf(0.dp) } \/\/ largeur BasicTextField pour positionnement en vertical\n\n    val densite_locale = LocalDensity.current\n    var activity = LocalContext.current as Activity\n\n    \/\/ Bonne position en fonction de l'intervalle de donn\u00e9es\n    position_slider = get_valeur_pour_slider(position_slider.toString(), intervalle_valeur,increment_valeur)\n\n    Column() {\n        Row( verticalAlignment = Alignment.CenterVertically) {\n\n            \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\n            \/\/ Ic\u00f4ne gauche (-) clickable\n            Icon(\n                imageVector = Icons.Rounded.Remove ,                \/\/ image de l'ic\u00f4ne\n                contentDescription = null,\n                modifier = Modifier\n                    .padding(top = top_element)                     \/\/ ajustement pour se caler avec le Slider\n                    .clickable {                                    \/\/ gestion du click sur l'ic\u00f4ne\n                        position_slider -= increment_valeur \/\/ D\u00e9cr\u00e9mente\n                        \/\/ v\u00e9rifie que la valeur ne d\u00e9passe les bornes min et max\n                        position_slider = get_valeur_pour_slider(position_slider.toString(),intervalle_valeur,increment_valeur)\n                        \/\/ mise en forme du texte qui indiquera la valeur\n                        text = position_slider.toString().removeSuffix(\".0\")\n                        \/\/ indique aux autres composants qu'il y a une erreur de valeur (pour BasicTextField)\n                        is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)\n                        \/\/ Renvoi nouvelle valeur (si pas d'erreur)\n                        if (!is_text_erreur) {\n                            if (callback_retour_valeur != null) {\n                                callback_retour_valeur(text)\n                            }\n                        }\n                        cacher_clavier_slider(activity) \/\/ Cacher clavier\n                    }\n            )\n\n            Box() {\n\n                \/\/ Calcul interm\u00e9diaire pour d\u00e9finir offset texte \u00e0 afficher\n\n                \/\/ on d\u00e9cale en fonction de la grosseur du curseur\n                \/\/   10.dp correspond \u00e0 ThumbRadius dans Slider.kt -> marge entre la box et le track\n                val decalage_texte_a_afficher = 12.dp\n                \/\/ d\u00e9calage final pour positionner l'\u00e9l\u00e9ment texte\n                val decalage_texte_final_a_afficher = decalage_texte_a_afficher -( largeur_texte_label\/2)\n                \/\/ Callage vis \u00e0 vis du curseur\n                val ratio_slider : Float    = (position_slider-intervalle_valeur.start)\/(intervalle_valeur.endInclusive - intervalle_valeur.start)\n                \/\/  offset_slider = taille du slider * ratio s\u00e9l\u00e9ection du slider\n                val offset_slider : Dp = (width_slider-20.dp).times(ratio_slider) \/\/ pourqoi le -20dp ???\n                \/\/ Callage final\n                var decalage_slider : Dp = decalage_texte_final_a_afficher + offset_slider\n                if (decalage_slider&lt;5.dp) {decalage_slider=5.dp}\n                val offset_x = decalage_slider\n\n                \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\n                \/\/  Basic TextField\n                var coul_texte by remember {mutableStateOf( Color.Unspecified) }\n                if (is_text_erreur) {\n                    coul_texte = Color.Red\n                } else {\n                    coul_texte = Color.Unspecified\n                }\n\n                BasicTextField(\n                    value = text,\n                    onValueChange = {\n                        text = traite_saisie_slider(text,it)   \/\/ permet de rendre \u00e9ditable le composant + contr\u00f4le saisie\n                        \/\/ met \u00e0 jour la position du slider en fonction de la taille du texte\n                        position_slider = get_valeur_pour_slider(text, intervalle_valeur,increment_valeur)\n                        \/\/ indique si erreur detect\u00e9e dans la saisie (pour affichage en rouge...)\n                        is_text_erreur =  !is_valeur_slider_correcte(text, intervalle_valeur)\n                        \/\/ ici pas de renvoi de valeur (on passe par la vailidation du clavier)\n                        },\n                    textStyle = LocalTextStyle.current.copy(color = coul_texte),\n                    modifier = Modifier\n                        .width(IntrinsicSize.Min)    \/\/ taille minimale de la zone de texte\n                        .padding(start = offset_x)   \/\/ positionnement du composant en fonction du curseur du Slider (via Offset)\n                        .background(color = Color.Transparent)\n                        .onGloballyPositioned {\n                            \/\/ R\u00e9cup\u00e9ration taille de la zone de texte (pour calcul offset et placement au milieu du curseur du slider\n                            largeur_texte_label = with(densite_locale) { it.size.width.toDp() }\n                        }\n                       ,\n                    singleLine = true,  \/\/ une seule ligne\n                    \/\/ Clavier num\u00e9rique seulement\n                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number,imeAction = ImeAction.Default),\n                    \/\/ Clavier : action si appui sur 'OK' du clavier num\u00e9rique\n                    keyboardActions = KeyboardActions(\n                        onDone = {\n                            \/\/ renvoi valeur si correcte seulement\n                            is_text_erreur =  !is_valeur_slider_correcte(text, intervalle_valeur)\n                            if (!is_text_erreur) {\n                                if (callback_retour_valeur != null) {\n                                    callback_retour_valeur(text)\n                                }\n                            }\n                            \/\/ cacher le clavier\n                            cacher_clavier_slider(activity)\n                        }\n                    )\n                )\n\n                \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\n                \/\/ le slider\n                Slider(\n                    \/\/ valeur du slider\n                    value = position_slider,\n                    \/\/ enregistrement de la veleur du slider\n                    onValueChange = {\n                        position_slider = get_valeur_pour_slider(it.toString(), intervalle_valeur,increment_valeur)\n                        text = position_slider.toString().removeSuffix(\".0\")\n                        is_text_erreur =!is_valeur_slider_correcte(text, intervalle_valeur)\n\n                        \/\/ Renvoi valeur\n                        if (!is_text_erreur) {\n                            if (callback_retour_valeur != null) {\n                                callback_retour_valeur(text)\n                            }\n                        }\n                        \/\/ Cacher clavier\n                        cacher_clavier_slider(activity)\n                    },\n                    \/\/ Intervalle de valeur min max\n                    valueRange= intervalle_valeur ,\n                    \/\/ largeur du slider\n                    modifier = Modifier\n                        .padding(top = top_element)\n                        .width(width_slider)\n                )\n            }\n\n            \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\n            \/\/ Ic\u00f4ne droite (+) clickable\n            Icon(\n                imageVector = Icons.Rounded.Add ,\n                contentDescription = null,\n                modifier = Modifier\n                    .padding(top = top_element)\n                    .clickable {\n                        position_slider += increment_valeur\n                        position_slider = get_valeur_pour_slider(position_slider.toString(),intervalle_valeur,increment_valeur )\n                        text = position_slider.toString().removeSuffix(\".0\")\n                        is_text_erreur = !is_valeur_slider_correcte(text, intervalle_valeur)\n                        if (!is_text_erreur) {\n                            if (callback_retour_valeur != null) {\n                                callback_retour_valeur(text)\n                            }\n                        }\n                        cacher_clavier_slider(activity) \/\/ Cacher clavier\n                    }\n            )\n        }\n    }\n}\n\n\/**\n * Construction g\u00e9n\u00e9rale de l'activit\u00e9\n *\/\n@Composable\nfun Sommaire_slider() {\n\n    var valeur_texte_saisie1 by remember { mutableStateOf(\"\") }\n\n    Column() {\n        Text(text = \"Test Slider\")\n        Spacer(modifier = Modifier.height(42.dp))\n        Row (\n            modifier=Modifier.fillMaxWidth()\n        ) {\n            Spacer(modifier = Modifier.width(20.dp))\n            slider_XH(valeur_slider = 0f,increment_valeur = 1f)\n            Spacer(modifier = Modifier.width(10.dp))\n            slider_XH(valeur_slider = 20f)\n        }\n        Spacer(modifier = Modifier.height(8.dp))\n        Row (\n            modifier=Modifier.fillMaxWidth()\n        ) {\n            Spacer(modifier = Modifier.width(20.dp))\n            slider_XH(\n                valeur_slider = 1.9f,\n                increment_valeur = 0.1f,\n                width_slider = 300.dp,\n                intervalle_valeur = -12f..12f) {\n\n                valeur_texte_saisie1=it\n            }\n        }\n        Spacer(modifier = Modifier.height(16.dp))\n        Row() {\n            Text(text = \" Valeur renseign\u00e9e : $valeur_texte_saisie1\")\n        }\n    }\n}\n\n\n\n@Preview(showBackground = true)\n@Composable\nfun DefaultPreview2() {\n    Textview_test1Theme {\n        Sommaire_slider()\n    }\n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I Introduction Ici un deuxi\u00e8me exemple montrant comment cr\u00e9er son propre composant sous Compose avec Kotlin. L&rsquo;id\u00e9e de ce composant est de pouvoir renseigner rapidement une valeur. Pour cela, ce composant sera compos\u00e9 : Il sera \u00e9galement possible de d\u00e9finir<\/p>\n","protected":false},"author":1,"featured_media":4096,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-4094","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-_dev"],"_links":{"self":[{"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=\/wp\/v2\/posts\/4094","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4094"}],"version-history":[{"count":60,"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=\/wp\/v2\/posts\/4094\/revisions"}],"predecessor-version":[{"id":4193,"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=\/wp\/v2\/posts\/4094\/revisions\/4193"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=\/wp\/v2\/media\/4096"}],"wp:attachment":[{"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4094"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4094"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogperso.union31.fr\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4094"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}