Material Slider

A Google 2020. július 16-án kiadta a Material Components 1.3.0-alpha02 verziót. Az egyik komponens, ami ebben a kiadásban sok szeretetet kapott, az a szerény csúszka. Az alapvető funkcionalitáson felül van néhány szép kiegészítés, és ebben a bejegyzésben ezeket vizsgáljuk meg egy kicsit.

Image Source
Licensed under CC 2.0

A Slider egy hasznos vezérlőelem, amely lehetővé teszi a felhasználó számára, hogy értékeket adjon meg anélkül, hogy a billentyűzetet kellene használnia. Az alapvető felhasználási módja nagyon hasonló az Android Framework SeekBar vagy a AppCompatSeekBar widgetekhez. Ezeknek különböző felhasználási módjai vannak: például egy adott helyre való súrolás hang- vagy videolejátszás közben; vagy egy adott tartományon belüli érték megadása. Van azonban néhány további felhasználási lehetőség, amelyet a Slider biztosít számunkra, amint azt a cikk későbbi részében látni fogjuk

Egy nagyon egyszerű csúszkavezérlő létrehozásával kezdjük, és megnézzük, hogyan néz ki és hogyan viselkedik.

Az alapvető viselkedés

A Material Slider létrehozása nagyon egyszerű:

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>

Az valueFrom és valueTo attribútumok segítségével mindkét vezérlőn 0-100 közötti tartományt adunk meg. Ezzel a következő viselkedéseket kapjuk (a AppCompatSeekBar van felül, a Slider pedig alul):

Míg ezek nagyon hasonlónak tűnnek, van néhány fontos különbség. Először is, a Slider a témámban meghatározott színeket használja, míg a AppCompatSeekBar az AppCompat könyvtárból származó alapértelmezett színeket. Másodszor, a Slider kissé nagyobb, így a felhasználó számára kicsit áttekinthetőbb. Valószínűleg a legfontosabb különbség a Slider címke hozzáadása, amikor a felhasználó áthúzza a pozíciót. Ez megkönnyíti a felhasználó számára, hogy megértse, pontosan milyen értéket választ ki. Bár természetesen hozzáadhatunk egy külön View-t az elrendezésünkhöz, hogy megjelenítsük ezt az értéket, a tooltip címke kevésbé zsúfolttá teszi a felhasználói felületet. Ezt kikapcsolhatjuk, ha nincs rá szükség. A app:labelBehaviour="gone" hozzáadásával ezt elérhetjük.

Mégis van két másik címke viselkedés. Az első az alapértelmezett, amelyet az előző példában láthatunk – ez a app:labelBehaviour="floating", amely a címkét a nézet fölé lebegtet. A másik lehetőség a app:labelBehaviour="withinBounds". Ez ugyanazt rendereli, mint a floating, de a különbség az, hogy hogyan befolyásolja a Slider méretét. A floating esetén a vezérlő mért magassága nem tartalmazza a címkét, míg a withinBounds esetén a címke szerepel a mért magasságban. Ez attól függően lehet fontos, hogy szeretnénk-e biztosítani, hogy a címke ne takarjon el más vezérlőelemeket az elrendezésünkben.

Diszkrét csúszka

A Slider eddig vizsgált változatát folyamatos csúszkának nevezzük. Ez azt jelenti, hogy az általa visszaadott érték a tartományon belül bármilyen érték lehet. Vannak azonban olyan esetek, amikor ezt korlátozni szeretnénk. Például tört értékeket ad vissza ez a folytonos Slider, de lehet, hogy igényünk van arra, hogy az értéket egész számokra vagy még nagyobb lépésekre korlátozzuk. Ennek eléréséhez használhatunk diszkrét csúszkát – amely diszkrét értékkészletet ad vissza. A folyamatos Slider-t egyetlen attribútum hozzáadásával diszkrétre változtathatjuk:

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:

A app:stepSize="10" hozzáadásával a Slider csak olyan értékeket ad vissza, amelyek 10 többszörösei. Ez kissé megváltoztatja a UX-et:

Először is vannak jelölések, amelyek a visszaadandó diszkrét értékeket jelzik. Ezek quire finom, de jól láthatóak a pályán. A második különbség, hogy a kijelölés már nem sima, hanem ugrál a diszkrét értékek között, ahogy azt a címke értékénél láthatjuk. A felhasználónak egyértelműen jelzi, hogy a kiválasztás egy diszkrét értékkészletből történik.

RangeSlider

A Slider egy másik változatát is használhatjuk. Eddig láttuk, hogyan lehet egyetlen értéket kiválasztani egy Slider segítségével, de a felhasználó egy tartományt is kiválaszthat egy RangeSlider segítségével.

Az Slider első konfigurálásakor a valueFrom és valueTo értékeket használtuk annak az értéktartománynak a megadására, amelyet a csúszka visszaad. egy RangeSlider lehetővé teszi a felhasználó számára egy tartomány megadását. Például: ha keresési lehetőséget biztosítanánk egy vásárlási alkalmazáson belül, akkor azt szeretnénk, ha a felhasználó meg tudna adni egy ártartományt. Ezt a RangeSlider használatával érhetjük el, amely folyamatos és diszkrét módot egyaránt támogat:

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

Itt egy kötelező attribútum van: app:values – e nélkül a RangeSlider visszaesik a szokásos Slider viselkedésre. Ebben az attribútumban egy értékekből álló tömböt kell megadnunk:

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

<item>20</item>
<item>60</item>
<item>100</item>
</array>
</resources>

Ezeknek a tömböknek két, illetve három értéke van, és a folyamatos RangeSlider a két értékkel rendelkező tartományt kapja, míg a diszkrét RangeSlider a három értékkel rendelkező tartományt. Ez néhány érdekes új viselkedést eredményez:

A felső példa egy egyszerű kétértékű tartományt mutat, amely lehetővé teszi a felhasználó számára, hogy felső és alsó határokkal rendelkező tartományt adjon meg. A RangeSlider számára beállított teljes tartomány korlátozza a felhasználó által megadható maximális és minimális értékeket.

Az alsó példa még érdekesebb. Amellett, hogy diszkrét RangeSlider, a tömb három értéke három hüvelykujjpontot hoz létre a vezérlőben. Amellett, hogy a tartománynak csak egy felső és egy alsó határa van, a felhasználó egy pontot is megadhat valahol a felső és az alsó határon belül. A felhasználó nem húzhatja a középső hüvelykujjpontot a felső és alsó pontok által meghatározott határokon kívülre. A húzott hüvelykujjpont címkéje mindig a többi, esetleg átfedő címke fölé rajzolódik. Ez azért fontos, hogy a felhasználó tudja, hogy éppen melyik értéket választja ki.

A kiválasztott értékeket a RangeSlider getValues() metódusának meghívásával tudjuk lekérdezni. Ez egy értékekből álló tömböt ad vissza, amelynek mérete megegyezik a app:values-ben megadott tömb méretével.

Címkeformázás

A figyelmesebbek talán észrevették, hogy a dupla és a tripla értékű RangeSliders címkeformázása között különbség van. A folytonos kettős értékű RangeSlider az alapértelmezett címkeformátumot használja, míg a diszkrét hármas értékű Value: előtaggal rendelkezik. Ezzel szépen rátérünk arra a tényre, hogy testre szabhatjuk a címkét:

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 egy SAM interfész egyetlen getFormattedValue() metódussal, amelyet ebben az esetben lambdává alakítottunk. Ehhez egy formázott string erőforrást használtam:

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>

Ezt programozottan kell megtennünk. Biztos vagyok benne, hogy egy ilyen formátumú karakterlánc a felhasználási esetek többségének megfelelne, ezért jó lenne, ha egy XML-attribútummal megadhatnánk. Egy ilyen programozott alkalmazása azonban elég triviális.

Következtetés

Slider és RangeSlider igazán szép iterációk a SeekBar-on. Ugyanazt a funkcionalitást és még sokkal többet nyújtanak. Különösen lenyűgözött a részletekre való odafigyelés – például az aktív címke rajzolása a passzív címkék tetejére. Ez egy jól megtervezett és megvalósított vezérlő, és teljes mértékben használni szándékozom!