Material Slider

Google vydal 16. července 2020 Material Components 1.3.0-alpha02. Jednou z komponent, které se v tomto vydání dostalo velké lásky, je skromný posuvník. Kromě základních funkcí obsahuje několik příjemných doplňků a v tomto příspěvku je trochu prozkoumáme.

Zdroj obrázku
Licencováno pod CC 2.0

A Sliderje užitečný ovládací prvek, který umožňuje uživateli zadávat hodnoty, aniž by musel používat klávesnici. Základní případ jeho použití je velice podobný widgetům SeekBar frameworku Android nebo AppCompatSeekBar. Pro ně existují různá použití: například posun na určité místo během přehrávání zvuku nebo videa; nebo zadání hodnoty v daném rozsahu. Existují však i další případy použití, které nám Slider poskytuje, jak uvidíme později v tomto článku

Začneme vytvořením velmi jednoduchého ovládacího prvku posuvníku a podíváme se, jak vypadá a jak se chová.

Základní chování

Vytvoření materiálového posuvníku je velmi jednoduché:

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>

U obou ovládacích prvků určíme rozsah 0-100 pomocí atributů valueFrom a valueTo. Tímto postupem získáme následující chování (AppCompatSeekBar je nahoře a Slider dole):

Ačkoli vypadají velmi podobně, jsou zde některé důležité rozdíly. Za prvé, Slider je stylizován pomocí barev definovaných v rámci mého tématu, zatímco AppCompatSeekBar používá výchozí barvy z knihovny AppCompat. Za druhé, Slider je o něco větší, takže je pro uživatele o něco přehlednější. Pravděpodobně nejdůležitějším rozdílem je přidání popisku k Slider, když uživatel přetáhne pozici. Díky tomu uživatel snáze pochopí, jakou přesnou hodnotu vybírá. I když jistě můžeme do našeho rozvržení přidat samostatné Zobrazení pro zobrazení této hodnoty, díky popisku tooltip je uživatelské rozhraní méně nepřehledné. Pokud to není nutné, můžeme jej vypnout. Přidáním app:labelBehaviour="gone" toho dosáhneme.

Existují však ještě dvě další chování popisku. První je výchozí, které můžeme vidět v předchozím příkladu – jedná se o app:labelBehaviour="floating", který štítek nad zobrazením nadnáší. Druhou možností je app:labelBehaviour="withinBounds". Ta se vykresluje stejně jako floating, ale rozdíl je v tom, jak ovlivňuje velikost Slider. U floating měřená výška ovládacího prvku nezahrnuje popisek, zatímco withinBounds popisek do měřené výšky zahrnuje. To může být důležité v závislosti na tom, zda chceme zajistit, aby popisek nezakrýval jiné ovládací prvky v našem rozložení.

Diskrétní posuvník

Varianta Slider, kterou jsme se dosud zabývali, je známá jako spojitý posuvník. To znamená, že hodnota, kterou vrací, může být libovolná hodnota v daném rozsahu. Existují však případy, kdy ji můžeme chtít omezit. Například zlomkové hodnoty vrací tento spojitý Slider, ale můžeme požadovat, aby hodnota byla omezena na celá čísla nebo dokonce na větší kroky. K tomu můžeme použít diskrétní posuvník – který vrací diskrétní množinu hodnot. Spojitý Slider můžeme změnit na diskrétní přidáním jediného atributu:

1
2
3
4
5
6
7
8
9
10
11

<kom.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“ />

Přidáním app:stepSize="10" se Slider vrátí pouze hodnoty, které jsou násobky 10. Tím se mírně změní UX:

Nejprve jsou zde zaškrtávací značky, které označují diskrétní hodnoty, které budou vráceny. Ty jsou sice quire subtilní, ale na trati jsou zřetelně vidět. Druhý rozdíl spočívá v tom, že výběr již není plynulý, ale přeskakuje mezi diskrétními hodnotami, jak můžeme vidět podle hodnoty štítku. Uživatel má jasnou informaci, že výběr je z diskrétní množiny hodnot.

RangeSlider

Můžeme také použít jinou variantu Slider. Zatím jsme viděli, jak vybrat jednu hodnotu pomocí Slider, ale uživatel může vybrat rozsah pomocí RangeSlider.

Když jsme poprvé konfigurovali Slider, použili jsme valueFrom a valueTo k určení toho rozsahu hodnot, který posuvník vrací. a RangeSlider umožňuje uživateli určit rozsah. Například: Pokud bychom poskytovali možnost vyhledávání v rámci nákupní aplikace, mohli bychom chtít, aby uživatel mohl zadat cenový rozsah. Toho můžeme dosáhnout pomocí RangeSlider, který podporuje jak spojité, tak diskrétní režimy:

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

Zde je povinný atribut: app:values – bez něj se RangeSlider vrátí ke standardnímu chování Slider. V tomto atributu musíme dodat pole hodnot:

res/values/arrays.xml

1
2
3
4
5
6

.

7
8
9
10
11
12
13

<?xml version=“1.0″ encoding=“utf-8″?>
<resources>
<array name=“double_slider_values“>
<item>20</item>
<item>60</item>
</array>
<array name=“triple_slider_values“>
<položka>20</položka>
<položka>60</položka>
<položka>100</položka>
</array>
</resources>

Tato pole mají dvě, resp. tři hodnoty, a spojité RangeSlider získá rozsah se dvěma hodnotami, zatímco diskrétní RangeSlider získá rozsah se třemi hodnotami. Tím získáme zajímavé nové chování:

Vrchní příklad ukazuje jednoduchý rozsah se dvěma hodnotami, který umožňuje uživateli zadat rozsah s horní a dolní hranicí. Celkový rozsah, který jsme nastavili pro RangeSlider, omezuje maximální a minimální hodnoty, které může uživatel zadat.

Dolní příklad je ještě zajímavější. Kromě toho, že se jedná o diskrétní RangeSlider, vytvářejí tři hodnoty v poli tři body palce v ovládacím prvku. Kromě pouhé horní a dolní hranice rozsahu může uživatel zadat také bod někde uvnitř horní a dolní hranice. Uživatel nemůže přetáhnout prostřední bod palce mimo hranice stanovené horním a dolním bodem. Popisek přetahovaného bodu palce se vždy vykreslí nad ostatními popisky, které by mohl překrývat. To je důležité proto, aby uživatel věděl, jakou hodnotu právě vybírá.

Vybrané hodnoty můžeme získat voláním metody getValues() RangeSlider. Ta vrátí pole hodnot, jehož velikost je stejná jako velikost pole, které jsme zadali v app:values.

Formátování štítku

Pozornější si možná všimli, že je rozdíl ve formátování štítku dvojnásobné a trojnásobné hodnoty RangeSliders. Spojitý dvojhodnotový RangeSlider používá výchozí formát štítku, zatímco diskrétní trojhodnotový má předponu Value:. Tím se pěkně dostáváme k tomu, že si můžeme štítek přizpůsobit:

MainActivity.kt

1
2
3
4
5
6
7

.

8
9
10
11
12
13
14
15

class 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 je rozhraní SAM s jedinou metodou getFormattedValue(), která byla v tomto případě převedena na lambdu. Použil jsem k tomu zdroj formátovacího řetězce:

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>

To musíme udělat programově. Jsem si celkem jistý, že takový formátovací řetězec by vyhovoval většině případů použití, takže by bylo hezké, kdybychom ho mohli zadat pomocí atributu XML. Nicméně použít ho programově je docela triviální.

Závěr

Slider a RangeSlider jsou opravdu pěkné iterace na SeekBar. Poskytují stejnou funkčnost a navíc mnohem více. Obzvlášť mě zaujal smysl pro detail – například kreslení aktivního štítku nad pasivními. Je to dobře navržený a implementovaný ovládací prvek a já ho plně hodlám používat!