Material Slider

Google lançou Material Components 1.3.0-alpha02 em 16 de Julho de 2020. Um componente que tem recebido muito amor neste lançamento é o humilde deslizador. Existem algumas adições legais sobre a funcionalidade básica, e neste post vamos explorá-las um pouco.

Image Source
Licensed under CC 2.0

A Slider é um controle útil para permitir ao usuário especificar valores sem ter que usar o teclado. O caso de uso básico para ele é semelhante ao do Android Framework SeekBar ou dos widgets AppCompatSeekBar. Para estes existem vários usos: tais como scrubbing para um local específico durante a reprodução de áudio ou vídeo; ou especificar um valor dentro de um determinado intervalo. No entanto, existem alguns casos de uso adicionais que Slider nos fornece, como veremos mais adiante neste artigo

Comecemos por criar um controle deslizante muito simples e ver como ele se parece e se comporta.

Comportamento básico

Criar um controle deslizante de material é muito simples:

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.Deslizador
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>

Especificamos um intervalo de 0-100 em ambos os controlos usando os atributos valueFrom e valueTo. Fazendo isso nos dá os seguintes comportamentos (AppCompatSeekBar está em cima, e Slider está em baixo):

>

Embora estes pareçam muito semelhantes, existem algumas diferenças importantes. Primeiro, o Slider é estilizado usando as cores definidas dentro do meu tema, enquanto AppCompatSeekBar está usando as cores padrão da biblioteca AppCompat. Em segundo lugar, o Slider é ligeiramente maior tornando-o um pouco mais claro para o utilizador. Provavelmente a diferença mais importante é a adição de uma etiqueta ao Slider quando o utilizador arrasta a posição. Isto facilita ao usuário entender o valor exato que está selecionando. Embora possamos certamente adicionar uma View separada ao nosso layout para exibir esse valor, a etiqueta da ponta da ferramenta torna a UI menos desordenada. Podemos desabilitar isso se não for necessário. Adicionando app:labelBehaviour="gone" conseguiremos isto.

No entanto existem dois outros comportamentos de etiqueta. O primeiro é o padrão que podemos ver no exemplo anterior – este é app:labelBehaviour="floating" que flutua a etiqueta sobre a vista. A outra opção é app:labelBehaviour="withinBounds". Isto torna o mesmo que floating mas a diferença é como ela afeta o tamanho do Slider. Com floating a altura medida do controle não inclui a etiqueta, enquanto que withinBounds inclui a etiqueta na altura medida. Isto pode ser importante dependendo se queremos garantir que a etiqueta não obscurece nenhum outro controlo dentro do nosso layout.

Discrete Slider

A variante de Slider que vimos até agora é conhecida como um Slider contínuo. Isso significa que o valor que ele retorna pode ser qualquer valor dentro do intervalo. No entanto, há casos em que podemos querer limitar isto. Por exemplo, valores fracionários são retornados por este contínuo Slider, mas podemos exigir que o valor seja limitado a inteiros ou mesmo passos maiores. Para conseguir isto podemos usar um controle deslizante discreto – que retorna um conjunto discreto de valores. Podemos mudar um contínuo Slider para um discreto, adicionando um único atributo:

1
2
3
4
5
6
7
8
9
10
11

<com.google.android.material.slider.Deslizador
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” />

Adicionamento app:stepSize="10" faz com que o Slider só retorne valores que são múltiplos de 10. Isto altera ligeiramente o UX:

Primeiro, existem marcas de seleção que indicam os valores discretos que serão retornados. Estes são sutis, mas podem ser vistos claramente na pista. A segunda diferença é que a seleção não é mais suave, mas salta entre os valores discretos, como podemos ver pelo valor da etiqueta. O usuário tem uma clara indicação de que a seleção é de um conjunto discreto de valores.

RangeSlider

Também podemos usar outra variante de Slider. Até agora temos visto como selecionar um único valor usando um Slider, mas o usuário pode selecionar um intervalo usando um RangeSlider.

Quando configuramos pela primeira vez Slider usamos valueFrom e valueTo para especificar esse intervalo de valores que o deslizador retorna. um RangeSlider permite que o usuário especifique um intervalo. Por exemplo: se estivéssemos fornecendo um recurso de busca dentro de um aplicativo de compras, poderíamos querer que o usuário fosse capaz de especificar um intervalo de preços. Podemos conseguir isto utilizando um RangeSlider que suporta tanto o modo contínuo como o modo 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” />

Há aqui um atributo obrigatório: app:values – sem isto RangeSlider volta ao padrão Slider comportamento. Devemos fornecer um array de valores neste atributo:

res/values/arrays.xml

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

<?xml version=”1.0″ encoding=”utf-8″?>
< recursos>

< nome de exibição=”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>
</ recursos>

Estas matrizes têm dois e três valores, respectivamente, e o contínuo RangeSlider obtém o intervalo com dois valores enquanto o discreto RangeSlider obtém o intervalo com três valores. Isto nos dá alguns novos comportamentos interessantes:

O exemplo superior mostra um simples intervalo de dois valores que permite ao usuário especificar um intervalo com limites superior e inferior. O intervalo geral que definimos para o RangeSlider restringe os valores máximo e mínimo que o usuário pode especificar.

O exemplo inferior é ainda mais interessante. Além de ser um discreto RangeSlider, os três valores no array criam três pontos de polegar no controle. Assim como apenas um limite superior e inferior ao intervalo, o usuário também pode especificar um ponto em algum lugar dentro dos limites superior e inferior. O usuário não pode arrastar o ponto médio do polegar para fora dos limites definidos pelos pontos superior e inferior. A etiqueta do ponto polegar a ser arrastado é sempre desenhada em cima das outras etiquetas que possam sobrepor-se. Isto é importante para que o usuário saiba o valor que está selecionando no momento.

Podemos recuperar os valores selecionados chamando o método getValues() de RangeSlider. Isto retorna um array de valores cujo tamanho é o mesmo que o tamanho do array especificado em app:values.

Label Formatting

O mais observador pode ter notado que há uma diferença na formatação do rótulo do valor duplo e triplo RangeSliders. O duplo valor contínuo RangeSlider utiliza o formato padrão de etiqueta, enquanto o triplo valor discreto tem o prefixo Value:. Isto leva-nos ao facto de que podemos personalizar a etiqueta:

MainActivity.kt

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

classe MainActivity : AppCompatActivity() {
ligação privada em lateinit var: 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 é uma interface SAM com um único método getFormattedValue() que foi convertido para um lambda neste caso. Eu usei um recurso de formatação de string para isto:

res/values/strings.xml

1
2
3
4

<recursos>
< nome da corda=”app_name”>Material Slider</corda>
< nome da corda=”label_format”>Valor: %1$.0f</string>
</recursos>

Devemos fazer isto de forma programática. Tenho quase a certeza que uma string de formato como esta caberia na maioria dos casos de uso, por isso seria bom se pudéssemos especificar um usando um atributo XML. Contudo, aplicar um programmaticamente é bastante trivial.

Conclusion

Slider e RangeSlider são iterações realmente boas em SeekBar. Elas fornecem a mesma funcionalidade e muito mais. Estou particularmente impressionado com a atenção aos detalhes – como o desenho da etiqueta ativa em cima das passivas. Este é um controle bem desenhado e implementado, e eu pretendo usá-lo completamente!