Material Slider

Google a lansat Material Components 1.3.0-alpha02 pe 16 iulie 2020. O componentă care a primit multă dragoste în această versiune este umilul slider. Există câteva adăugiri frumoase pe lângă funcționalitatea de bază, iar în această postare le vom explora puțin.

Sursa imaginii
Licențiat sub CC 2.0

Un Slider este un control util pentru a permite utilizatorului să specifice valori fără a fi nevoie să folosească tastatura. Cazul de utilizare de bază pentru acesta este foarte asemănător cu cadrul Android SeekBar sau cu widget-urile AppCompatSeekBar. Pentru acestea există diverse utilizări: cum ar fi parcurgerea până la o anumită locație în timpul redării audio sau video; sau specificarea unei valori într-un anumit interval. Cu toate acestea, există câteva cazuri de utilizare suplimentare pe care ni le oferă Slider, după cum vom vedea mai târziu în acest articol

Vom începe prin a crea un control slider foarte simplu și vom vedea cum arată și cum se comportă acesta.

Comportamentul de bază

Crearea unui Material Slider este foarte simplă:

res/layout/activity_main.xml

1
2
3
4
5
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:layout_height=”wrap_content”
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>

Specificăm un interval de 0-100 pe ambele controale folosind atributele valueFrom și valueTo. Procedând astfel, obținem următoarele comportamente (AppCompatSeekBar este în partea de sus, iar Slider este în partea de jos):

În timp ce acestea par foarte asemănătoare, există câteva diferențe importante. În primul rând, Slider este stilizat folosind culorile definite în cadrul temei mele, în timp ce AppCompatSeekBar folosește culorile implicite din biblioteca AppCompat. În al doilea rând, Slider este ușor mai mare, ceea ce îl face puțin mai clar pentru utilizator. Probabil cea mai importantă diferență este adăugarea unei etichete la Slider atunci când utilizatorul glisează poziția. Astfel, utilizatorului îi este mai ușor să înțeleagă valoarea exactă pe care o selectează. Deși putem cu siguranță să adăugăm o vizualizare separată la aspectul nostru pentru a afișa această valoare, eticheta tooltip face ca interfața utilizator să fie mai puțin aglomerată. Putem dezactiva acest lucru dacă nu este necesar. Adăugarea app:labelBehaviour="gone" va realiza acest lucru.

Există însă alte două comportamente ale etichetei. Primul este cel implicit pe care îl putem vedea în exemplul anterior – acesta este app:labelBehaviour="floating" care plutește eticheta deasupra vizualizării. Cealaltă opțiune este app:labelBehaviour="withinBounds". Aceasta se redă la fel ca floating, dar diferența constă în modul în care afectează dimensiunea lui Slider. Cu floating, înălțimea măsurată a controlului nu include eticheta, în timp ce withinBounds include eticheta în înălțimea măsurată. Acest lucru poate fi important în funcție de faptul dacă dorim să ne asigurăm că eticheta nu întunecă alte controale din cadrul layout-ului nostru.

Discret Slider

Varianta de Slider pe care am analizat-o până acum este cunoscută sub numele de Slider continuu. Aceasta înseamnă că valoarea pe care o returnează poate fi orice valoare din interval. Cu toate acestea, există cazuri în care am putea dori să limităm acest lucru. De exemplu, valorile fracționare sunt returnate de acest Slider continuu, dar este posibil să solicităm ca valoarea să fie limitată la numere întregi sau chiar la pași mai mari. Pentru a realiza acest lucru, putem utiliza un cursor discret – care returnează un set discret de valori. Putem schimba un Slider continuu într-unul discret prin adăugarea unui singur atribut:

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

Adăugând app:stepSize="10", Slider face ca Slider să returneze numai valori care sunt multipli de 10. Acest lucru schimbă ușor UX-ul:

În primul rând, există semne de bifare care indică valorile discrete care vor fi returnate. Acestea sunt quire subtile, dar pot fi văzute clar pe traseu. A doua diferență este că selecția nu mai este netedă, ci sare între valorile discrete, după cum se poate vedea prin valoarea etichetei. Utilizatorul are o indicație clară că selecția se face dintr-un set discret de valori.

RangeSlider

Pot fi folosită și o altă variantă a lui Slider. Până acum am văzut cum să selectăm o singură valoare folosind un Slider, dar utilizatorul poate selecta un interval folosind un RangeSlider.

Când am configurat pentru prima dată Slider am folosit valueFrom și valueTo pentru a specifica acel interval de valori pe care glisorul îl returnează. un RangeSlider permite utilizatorului să specifice un interval. De exemplu: dacă am furniza o facilitate de căutare în cadrul unei aplicații de cumpărături, am putea dori ca utilizatorul să poată specifica un interval de preț. Putem realiza acest lucru folosind un RangeSlider care acceptă atât modul continuu, cât și cel 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: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: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” />

Există un atribut obligatoriu aici: app:values – fără acesta RangeSlider revine la comportamentul standard Slider. Trebuie să furnizăm o matrice de valori în acest atribut:

res/values/arrays.xml

1
2
3
4
5
6
4
5
6

.

7
8
9
10
11
12
13

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

Aceste array-uri au două și, respectiv, trei valori, iar RangeSlider continuu primește intervalul cu două valori, în timp ce RangeSlider discret primește intervalul cu trei valori. Acest lucru ne oferă câteva comportamente noi și interesante:

Exemplul de sus prezintă un interval simplu cu două valori care permite utilizatorului să specifice un interval cu limite superioare și inferioare. Intervalul general pe care îl stabilim pentru RangeSlider constrânge valorile maxime și minime pe care utilizatorul le poate specifica.

Exemplul de jos este și mai interesant. Pe lângă faptul că este un RangeSlider discret, cele trei valori din matrice creează trei puncte de degetul mare în control. Pe lângă faptul că este vorba doar de o limită superioară și una inferioară a intervalului, utilizatorul poate specifica și un punct undeva în limitele superioară și inferioară. Utilizatorul nu poate trage punctul degetului mare din mijloc în afara limitelor stabilite de punctele superioare și inferioare. Eticheta punctului degetului mare trasat este întotdeauna desenată deasupra celorlalte etichete pe care le-ar putea suprapune. Acest lucru este important pentru ca utilizatorul să știe care este valoarea pe care o selectează în prezent.

Potem prelua valorile selectate prin apelarea metodei getValues() din RangeSlider. Aceasta returnează un tablou de valori a cărui dimensiune este aceeași cu dimensiunea tabloului pe care am specificat-o în app:values.

Formatul etichetelor

Cei mai atenți poate au observat că există o diferență în formatul etichetelor cu valoare dublă și triplă RangeSliders. RangeSlider cu valoare dublă continuă utilizează formatul implicit al etichetei, în timp ce cea cu valoare triplă discretă are prefixul Value:. Acest lucru ne aduce cu bine la faptul că putem personaliza eticheta:

MainActivity.kt

1
2
3
4
5
6
7

.

8
9
10
11
12
13
14
15

clasa 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 este o interfață SAM cu o singură metodă getFormattedValue() care a fost convertită în acest caz într-un lambda. Am folosit pentru aceasta o resursă de șir de formate:

res/values/strings.xml

1
2
3
4

. <resurse>
<string name=”app_name”>Material Slider</string>
<string name=”label_format”>Valoare: %1$.0f</string>
</resurse>

Trebuie să facem acest lucru în mod programatic. Sunt destul de sigur că un șir de format ca acesta s-ar potrivi majorității cazurilor de utilizare, așa că ar fi bine dacă am putea specifica unul folosind un atribut XML. Cu toate acestea, aplicarea unuia în mod programatic este destul de trivială.

Concluzie

Slider și RangeSlider sunt iterații foarte frumoase pe SeekBar. Ele oferă aceeași funcționalitate plus mult mai mult. Sunt deosebit de impresionat de atenția acordată detaliilor – cum ar fi desenarea etichetei active deasupra celor pasive. Acesta este un control bine conceput și implementat și intenționez pe deplin să îl folosesc!

.