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.
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ă:
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:
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:
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:
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!
.