Material Slider

Google a publié Material Components 1.3.0-alpha02 le 16 juillet 2020. Un composant qui a reçu beaucoup d’amour dans cette version est l’humble curseur. Il y a quelques ajouts agréables en plus de la fonctionnalité de base, et dans ce post nous allons les explorer un peu.

Image Source
Licensed under CC 2.0

Un Slider est un contrôle utile pour permettre à l’utilisateur de spécifier des valeurs sans avoir à utiliser le clavier. Son cas d’utilisation de base est très similaire à celui de l’Android Framework SeekBar ou des widgets AppCompatSeekBar. Pour ceux-ci, il existe diverses utilisations : comme le scrubbing à un endroit spécifique pendant la lecture audio ou vidéo ; ou la spécification d’une valeur dans une plage donnée. Cependant, il y a quelques cas d’utilisation supplémentaires que Slider nous fournit, comme nous le verrons plus tard dans cet article

Nous allons commencer par créer un contrôle de curseur très simple et voir comment il se présente et se comporte.

Comportement de base

La création d’un curseur matériel est très simple :

res/layout/activity_main.xml

1
2
3
4
5

.

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

< ?xml version= »1.0″ encoding= »utf-8″ ?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android= »http://schemas.android.com/apk/res/android »
xmlns:app= »http://schemas.android.com/apk/res-auto »
xmlns:tools= »http://schemas.android.com/tools »
android:layout_width= »match_parent »
android:layout_height= »match_parent »
android:padding= »32dp »
tools:context= ».MainActivity »>
<androidx.appcompat.widget.AppCompatSeekBar
android:id= »@+id/seek_bar »
android:layout_width= »0dp »
android:layout_height= »wrap_content »
android:valueFrom= »0″
android:valueTo= »100″
app :layout_constraintBottom_toTopOf= »@id/discrete_slider »
app:layout_constraintEnd_toEndOf= »parent »
app:layout_constraintStart_toStartOf= »parent »
app:layout_constraintTop_toTopOf= »parent » />
<com.google.android.material.slider.Slider
android:id= »@+id/continuous_slider »
android:layout_width= »0dp »
android:layout_height= »wrap_content »
android :valueFrom= »0″
android:valueTo= »100″
app:layout_constraintBottom_toTopOf= »@id/discrete_slider »
app :layout_constraintEnd_toEndOf= »@id/seek_bar »
app:layout_constraintStart_toStartOf= »@id/seek_bar »
app :layout_constraintTop_toBottomOf= »@id/seek_bar » />
</ConstraintLayout>

Nous spécifions une plage de 0-100 sur les deux contrôles en utilisant les attributs valueFrom et valueTo. En faisant cela, nous obtenons les comportements suivants (AppCompatSeekBar est en haut, et Slider est en bas):

Bien que ceux-ci semblent très similaires, il y a quelques différences importantes. Tout d’abord, le Slider est stylé à l’aide des couleurs définies dans mon thème alors que le AppCompatSeekBar utilise les couleurs par défaut de la bibliothèque AppCompat. Deuxièmement, le Slider est légèrement plus grand, ce qui le rend un peu plus clair pour l’utilisateur. La différence la plus importante est probablement l’ajout d’une étiquette à la Slider lorsque l’utilisateur fait glisser la position. Cela permet à l’utilisateur de comprendre plus facilement la valeur précise qu’il sélectionne. Alors que nous pouvons certainement ajouter une vue séparée à notre mise en page pour afficher cette valeur, l’étiquette de l’infobulle rend l’interface utilisateur moins encombrée. Nous pouvons la désactiver si elle n’est pas nécessaire. L’ajout de app:labelBehaviour="gone" permettra d’y parvenir.

Cependant, il existe deux autres comportements d’étiquette. Le premier est le défaut que nous pouvons voir dans l’exemple précédent – c’est app:labelBehaviour="floating" qui fait flotter l’étiquette au-dessus de la vue. L’autre option est app:labelBehaviour="withinBounds". Le rendu est identique à celui de floating, mais la différence réside dans la façon dont elle affecte la taille de Slider. Avec floating, la hauteur mesurée du contrôle n’inclut pas l’étiquette, alors que withinBounds inclut l’étiquette dans la hauteur mesurée. Cela peut être important selon que nous voulons nous assurer que l’étiquette ne masque pas d’autres contrôles dans notre mise en page.

Curseur discret

La variante de Slider que nous avons examinée jusqu’à présent est connue comme un curseur continu. Cela signifie que la valeur qu’il renvoie peut être n’importe quelle valeur dans la plage. Cependant, il y a des cas où nous pouvons vouloir limiter cela. Par exemple, les valeurs fractionnaires sont renvoyées par ce Slider continu, mais nous pouvons avoir besoin que la valeur soit limitée à des entiers ou même à des pas plus grands. Pour y parvenir, nous pouvons utiliser un curseur discret – qui renvoie un ensemble discret de valeurs. Nous pouvons transformer un Slider continu en un discret en ajoutant un seul attribut :

1
2
3
4
5
6
7
8
9
10
11

<com.google.android.material.slider.Slider
android:id= »@+id/continuous_slider »
android:layout_width= »0dp »
android:layout_height= »wrap_content »
android :stepSize= »10″
android:valueFrom= »0″
android:valueTo= »100″
app:layout_constraintBottom_toTopOf= »@id/discrete_slider »
app :layout_constraintEnd_toEndOf= »@id/seek_bar »
app:layout_constraintStart_toStartOf= »@id/seek_bar »
app :layout_constraintTop_toBottomOf= »@id/seek_bar » />

Ajoutant app:stepSize="10", le Slider ne renvoie que des valeurs multiples de 10. Cela change légèrement l’UX:

Premièrement, il y a des marques de coche qui indiquent les valeurs discrètes qui seront retournées. Ceux-ci sont quire subtils, mais peuvent être vus clairement sur la piste. La deuxième différence est que la sélection n’est plus lisse, mais saute entre les valeurs discrètes comme nous pouvons le voir par la valeur de l’étiquette. L’utilisateur a une indication claire que la sélection est parmi un ensemble discret de valeurs.

RangeSlider

Nous pouvons également utiliser une autre variante de Slider. Jusqu’à présent, nous avons vu comment sélectionner une seule valeur en utilisant un Slider, mais l’utilisateur peut sélectionner une plage en utilisant un RangeSlider.

Lorsque nous avons configuré Slider pour la première fois, nous avons utilisé valueFrom et valueTo pour spécifier cette plage de valeurs que le curseur renvoie. un RangeSlider permet à l’utilisateur de spécifier une plage. Par exemple : si nous fournissions une fonction de recherche dans une application de shopping, nous pourrions souhaiter que l’utilisateur puisse spécifier une fourchette de prix. Nous pouvons y parvenir en utilisant un RangeSlider qui prend en charge les modes continu et discret :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

<com.google.android.material.slider.RangeSlider
android:id= »@+id/continuous_range »
android:layout_width= »0dp »
android:layout_height= »wrap_content »
android :valueFrom= »0″
android:valueTo= »100″
app:layout_constraintBottom_toTopOf= »@id/discrete_range »
app :layout_constraintEnd_toEndOf= »@id/continuous_slider »
app:layout_constraintStart_toStartOf= »@id/continuous_slider »
app :layout_constraintTop_toBottomOf= »@id/discrete_slider »
app:values= »@array/double_slider_values »/>
<com.google.android.material.slider.RangeSlider
android:id= »@+id/discrete_range »
android:layout_width= »0dp »
android:layout_height= »wrap_content »
android :stepSize= »10″
android:valueFrom= »0″
android:valueTo= »100″
app:layout_constraintBottom_toBottomOf= »parent »
app :layout_constraintEnd_toEndOf= »@id/continuous_slider »
app:layout_constraintStart_toStartOf= »@id/continuous_slider »
app :layout_constraintTop_toBottomOf= »@id/continuous_range »
app:values= »@array/triple_slider_values » />

Il y a un attribut obligatoire ici : app:values – sans cela RangeSlider retombe dans le comportement standard Slider. Nous devons fournir un tableau de valeurs dans cet attribut:

res/values/arrays.xml

1
2
3
4
5
6

.

7
8
9
10
11
12
13

< ?xml version= »1.0″ encoding= »utf-8″?>
<ressources>
<array name= »double_slider_values »>
<item>20</item>.
<item>60</item>
</array>
<array name= »triple_slider_values »>
. <item>20</item>
<item>60</item>
<item>100</item>
. </réseau>
</ressources>

Ces tableaux ont respectivement deux et trois valeurs, et le continu RangeSlider obtient la plage avec deux valeurs alors que le discret RangeSlider obtient la plage avec trois valeurs. Cela nous donne quelques nouveaux comportements intéressants:

L’exemple du haut montre une simple plage à deux valeurs qui permet à l’utilisateur de spécifier une plage avec des limites supérieure et inférieure. La plage globale que nous définissons pour le RangeSlider contraint les valeurs maximale et minimale que l’utilisateur peut spécifier.

L’exemple du bas est encore plus intéressant. En plus d’être un RangeSlider discret, les trois valeurs du tableau créent trois points de pouce dans le contrôle. En plus d’une simple limite supérieure et inférieure de la plage, l’utilisateur peut également spécifier un point quelque part dans les limites supérieure et inférieure. L’utilisateur ne peut pas faire glisser le point de pouce du milieu en dehors des limites définies par les points supérieur et inférieur. L’étiquette du point du pouce que l’on fait glisser est toujours dessinée au-dessus des autres étiquettes qu’elle pourrait recouvrir. Ceci est important pour que l’utilisateur sache quelle valeur il est en train de sélectionner.

Nous pouvons récupérer les valeurs sélectionnées en appelant la méthode getValues() de RangeSlider. Cela renvoie un tableau de valeurs dont la taille est la même que celle du tableau que nous avons spécifié dans app:values.

Formatage de l’étiquette

Les plus observateurs auront peut-être remarqué qu’il y a une différence dans le formatage de l’étiquette de la double et triple valeur RangeSliders. La valeur double continue RangeSlider utilise le format d’étiquette par défaut alors que la valeur triple discrète a le préfixe Value:. Cela nous amène joliment au fait que nous pouvons personnaliser l’étiquette:

MainActivity.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class MainActivity : AppCompatActivity() {
privé lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState : Bundle ?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.discreteRange.setLabelFormatter {
getString(R.string.label_format, it)
}
}
}

LabelFormatter est une interface SAM avec une seule méthode getFormattedValue() qui a été convertie en lambda dans ce cas. J’ai utilisé une ressource de chaîne de format pour cela :

res/values/strings.xml

1
2
3
4

. <resources>
<string name= »app_name »>Material Slider</string>
<string name= »label_format »>Value : %1$.0f</string>
</resources>

Nous devons faire cela de manière programmatique. Je suis à peu près sûr qu’une chaîne de format comme celle-ci conviendrait à la majorité des cas d’utilisation, donc ce serait bien si nous pouvions en spécifier une en utilisant un attribut XML. Cependant, en appliquer une par programme est assez trivial.

Conclusion

Slider et RangeSlider sont des itérations vraiment sympathiques de SeekBar. Ils fournissent la même fonctionnalité plus beaucoup plus. Je suis particulièrement impressionné par l’attention portée aux détails – comme le fait de dessiner l’étiquette active par-dessus les étiquettes passives. C’est une commande bien conçue et mise en œuvre, et j’ai bien l’intention de l’utiliser !

.