Material Slider

Google ha rilasciato Material Components 1.3.0-alpha02 il 16 luglio 2020. Un componente che ha ricevuto molto amore in questa release è l’umile slider. Ci sono alcune belle aggiunte oltre alla funzionalità di base, e in questo post le esploreremo un po’.

Image Source
Licensed under CC 2.0

Un Slider è un controllo utile per permettere all’utente di specificare valori senza dover usare la tastiera. Il suo caso d’uso di base è molto simile a quello del Framework Android SeekBar o dei widget AppCompatSeekBar. Per questi ci sono vari usi: come lo scrubbing verso una posizione specifica durante la riproduzione audio o video; o specificare un valore all’interno di un dato intervallo. Tuttavia, ci sono alcuni casi d’uso aggiuntivi che Slider ci fornisce, come vedremo più avanti in questo articolo

Inizieremo creando un controllo slider molto semplice e vedremo come appare e si comporta.

Comportamento di base

Creare un Material Slider è molto semplice:

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>

Specchiamo un intervallo di 0-100 su entrambi i controlli usando gli attributi valueFrom e valueTo. Facendo questo, otteniamo i seguenti comportamenti (AppCompatSeekBar è in alto, e Slider è in basso):

Mentre questi sembrano molto simili, ci sono alcune importanti differenze. In primo luogo, il Slider è stilizzato utilizzando i colori definiti all’interno del mio tema, mentre il AppCompatSeekBar utilizza i colori predefiniti dalla libreria AppCompat. In secondo luogo, il Slider è leggermente più grande, il che lo rende un po’ più chiaro per l’utente. Probabilmente la differenza più importante è l’aggiunta di un’etichetta al Slider quando l’utente trascina la posizione. Questo rende più facile per l’utente capire il valore preciso che sta selezionando. Mentre possiamo certamente aggiungere una View separata al nostro layout per visualizzare questo valore, l’etichetta tooltip rende l’UI meno ingombrante. Possiamo disabilitarla se non è necessaria. Aggiungendo app:labelBehaviour="gone" otterremo questo risultato.

Tuttavia ci sono altri due comportamenti dell’etichetta. Il primo è quello predefinito che possiamo vedere nell’esempio precedente – questo è app:labelBehaviour="floating" che fa galleggiare l’etichetta sopra la vista. L’altra opzione è app:labelBehaviour="withinBounds". Questo rende la stessa cosa di floating ma la differenza è come influisce sulla dimensione di Slider. Con floating l’altezza misurata del controllo non include l’etichetta, mentre withinBounds include l’etichetta nell’altezza misurata. Questo può essere importante se vogliamo assicurarci che l’etichetta non oscuri altri controlli nel nostro layout.

Slider discreto

La variante di Slider che abbiamo visto finora è conosciuta come uno Slider continuo. Ciò significa che il valore che restituisce può essere qualsiasi valore all’interno dell’intervallo. Tuttavia ci sono casi in cui potremmo volerlo limitare. Per esempio, i valori frazionari sono restituiti da questo continuo Slider, ma potremmo richiedere che il valore sia limitato a numeri interi o a passi più grandi. Per ottenere questo possiamo usare un cursore discreto – che restituisce un insieme discreto di valori. Possiamo cambiare un Slider continuo in uno discreto aggiungendo un singolo attributo:

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” />

L’aggiunta di app:stepSize="10" fa sì che Slider restituisca solo valori che sono multipli di 10. Questo cambia leggermente l’UX:

In primo luogo ci sono dei segni di spunta che indicano i valori discreti che saranno restituiti. Questi sono molto sottili, ma possono essere visti chiaramente sulla traccia. La seconda differenza è che la selezione non è più liscia, ma salta tra i valori discreti come possiamo vedere dal valore dell’etichetta. L’utente ha una chiara indicazione che la selezione è da un insieme discreto di valori.

RangeSlider

Possiamo anche usare un’altra variante di Slider. Finora abbiamo visto come selezionare un singolo valore usando un Slider, ma l’utente può selezionare un intervallo usando un RangeSlider.

Quando abbiamo configurato per la prima volta Slider abbiamo usato valueFrom e valueTo per specificare l’intervallo di valori che lo slider restituisce. Per esempio: se stessimo fornendo una funzione di ricerca all’interno di un’app per lo shopping, potremmo volere che l’utente sia in grado di specificare una gamma di prezzi. Possiamo ottenere questo utilizzando un RangeSlider che supporta sia il modo continuo che quello discreto:

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” />

Qui c’è un attributo obbligatorio: app:values – senza questo RangeSlider ritorna al comportamento standard Slider. Dobbiamo fornire un array di valori in questo attributo:

res/values/arrays.xml

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

<?xml version=”1.0″ encoding=”utf-8″?>
<risorse>
<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>
</array>
</resources>

Questi array hanno rispettivamente due e tre valori, e il continuo RangeSlider ottiene l’intervallo con due valori mentre il discreto RangeSlider ottiene l’intervallo con tre valori. Questo ci dà alcuni nuovi comportamenti interessanti:

L’esempio superiore mostra un semplice intervallo di due valori che permette all’utente di specificare un intervallo con limiti superiori e inferiori. L’intervallo complessivo che abbiamo impostato per il RangeSlider vincola i valori massimi e minimi che l’utente può specificare.

L’esempio inferiore è ancora più interessante. Oltre ad essere un RangeSlider discreto, i tre valori nell’array creano tre punti di pollice nel controllo. Oltre a un limite superiore e inferiore dell’intervallo, l’utente può anche specificare un punto da qualche parte all’interno dei limiti superiore e inferiore. L’utente non può trascinare il punto centrale del pollice al di fuori dei limiti stabiliti dai punti superiore e inferiore. L’etichetta del punto del pollice che viene trascinato è sempre disegnata sopra le altre etichette che potrebbero sovrapporsi. Questo è importante affinché l’utente sappia il valore che sta selezionando.

Possiamo recuperare i valori selezionati chiamando il metodo getValues() di RangeSlider. Questo restituisce un array di valori la cui dimensione è uguale alla dimensione dell’array che abbiamo specificato in app:values.

Formattazione dell’etichetta

I più attenti potrebbero aver notato che c’è una differenza nella formattazione dell’etichetta del doppio e del triplo valore RangeSliders. Il valore doppio continuo RangeSlider usa il formato predefinito dell’etichetta, mentre il valore triplo discreto ha il prefisso Value:. Questo ci porta al fatto che possiamo personalizzare l’etichetta:

MainActivity.kt

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

classe MainActivity : AppCompatActivity() {
private 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 è un’interfaccia SAM con un singolo metodo getFormattedValue() che è stato convertito in una lambda in questo caso. Ho usato una risorsa format string per questo:

res/values/strings.xml

1
2
3
4

<resources>
<string name=”app_name”>Material Slider</string>
<string name=”label_format”>Valore: %1$.0f</stringa>
</resources>

Dobbiamo farlo programmaticamente. Sono abbastanza sicuro che una stringa di formato come questa sarebbe adatta alla maggior parte dei casi d’uso, quindi sarebbe bello se potessimo specificarne una usando un attributo XML. Tuttavia, applicarne uno programmaticamente è abbastanza banale.

Conclusione

Slider e RangeSlider sono davvero delle belle iterazioni su SeekBar. Forniscono la stessa funzionalità e molto di più. Sono particolarmente colpito dall’attenzione ai dettagli – come disegnare l’etichetta attiva sopra quelle passive. Questo è un controllo ben progettato e implementato, e ho intenzione di usarlo!