Material Slider

Google julkaisi Material Components 1.3.0-alpha02:n 16. heinäkuuta 2020. Yksi komponentti, joka on saanut paljon rakkautta tässä julkaisussa, on nöyrä liukusäädin. Perustoiminnallisuuden päälle on tehty muutamia mukavia lisäyksiä, ja tässä postauksessa tutustumme niihin hieman.

Kuvan lähde
Lisensoitu CC 2.0

A Slider on hyödyllinen ohjainelementti, jonka avulla käyttäjä voi määritellä arvoja ilman näppäimistön käyttöä. Sen peruskäyttötilanne on hyvin samanlainen kuin Android Frameworkin SeekBar tai AppCompatSeekBar widgettien. Näille on erilaisia käyttötarkoituksia: esimerkiksi tiettyyn paikkaan siirtyminen äänen tai videon toiston aikana tai arvon määrittäminen tietyllä alueella. On kuitenkin joitakin lisäkäyttökohteita, joita Slider tarjoaa meille, kuten näemme myöhemmin tässä artikkelissa

Aloitamme luomalla hyvin yksinkertaisen liukusäädön ja katsomme, miltä se näyttää ja miten se käyttäytyy.

Peruskäyttäytyminen

Materiaaliliukusäätimen luominen on hyvin suoraviivaista:

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>

Määritämme molemmille ohjauslaitteille vaihteluvälin 0-100 attribuuttien valueFrom ja valueTo avulla. Näin toimimalla saamme seuraavat käyttäytymistavat (AppCompatSeekBar on ylhäällä ja Slider alhaalla):

Näyttävät hyvin samankaltaisilta, mutta niissä on joitakin tärkeitä eroja. Ensinnäkin Slider on muotoiltu käyttämällä teemassani määriteltyjä värejä, kun taas AppCompatSeekBar käyttää AppCompat-kirjaston oletusvärejä. Toiseksi Slider on hieman suurempi, mikä tekee siitä käyttäjälle hieman selkeämmän. Luultavasti tärkein ero on merkinnän lisääminen Slider:ään, kun käyttäjä raahaa paikkaa. Näin käyttäjän on helpompi ymmärtää, mitä tarkkaa arvoa hän on valitsemassa. Vaikka voimme toki lisätä asetteluumme erillisen näkymän näyttämään tämän arvon, tooltip-tarra tekee käyttöliittymästä vähemmän sekavan. Voimme poistaa sen käytöstä, jos sitä ei tarvita. Lisäämällä app:labelBehaviour="gone" saavutamme tämän.

Mutta on olemassa kaksi muuta etiketin käyttäytymistä. Ensimmäinen on oletusarvo, jonka näemme edellisessä esimerkissä – tämä on app:labelBehaviour="floating", joka kelluttaa etiketin näkymän yläpuolelle. Toinen vaihtoehto on app:labelBehaviour="withinBounds". Tämä renderöi saman kuin floating, mutta ero on siinä, miten se vaikuttaa Slider:n kokoon. Kun floating:ssä ohjaimen mitattu korkeus ei sisällä tarraa, kun taas withinBounds:ssä tarra sisältyy mitattuun korkeuteen. Tämä voi olla tärkeää riippuen siitä, haluammeko varmistaa, että etiketti ei peitä muita ohjaimia asettelussamme.

Diskreetti liukusäädin

Tässä vaiheessa tarkastelemamme Slider:n muunnos tunnetaan jatkuvana liukusäätimenä. Se tarkoittaa, että sen palauttama arvo voi olla mikä tahansa arvo alueella. On kuitenkin tapauksia, joissa tätä voidaan haluta rajoittaa. Tämä jatkuva Slider palauttaa esimerkiksi murtolukuja, mutta saatamme vaatia, että arvo rajoitetaan kokonaislukuihin tai jopa suurempiin askeliin. Tämän saavuttamiseksi voimme käyttää diskreettia liukusäädintä, joka palauttaa diskreetin arvojoukon. Voimme muuttaa jatkuvan Slider:n diskreetiksi lisäämällä yhden attribuutin:

1
2
3
4

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

Lisäämällä app:stepSize="10" saadaan Slider palauttamaan vain arvot, jotka ovat 10:n kertalukuja. Tämä muuttaa UX:ää hieman:

Ensiksi on rastit, jotka ilmaisevat diskreetit arvot, jotka palautetaan. Nämä ovat quire hienovaraisia, mutta näkyvät selvästi radalla. Toinen ero on se, että valinta ei ole enää tasainen, vaan hyppii diskreettien arvojen välillä, kuten näemme label-arvosta. Käyttäjä saa selvän merkin siitä, että valinta tapahtuu diskreetistä arvojoukosta.

RangeSlider

Voidaan käyttää myös toista muunnelmaa Slider. Tähän mennessä olemme nähneet, miten yksittäinen arvo valitaan Slider:n avulla, mutta käyttäjä voi valita arvoalueen RangeSlider:n avulla.

Kun määrittelimme Slider:n ensimmäisen kerran, käytimme valueFrom:ää ja valueTo:a määrittääksemme sen arvoalueen, jonka liukusäädin palauttaa. a RangeSlider:n avulla käyttäjä voi määrittää arvoalueen. Esimerkiksi: Jos tarjoaisimme hakumahdollisuuden ostosovelluksessa, voisimme haluta, että käyttäjä voi määrittää hinta-alueen. Voimme toteuttaa tämän käyttämällä a RangeSlider:tä, joka tukee sekä jatkuvia että diskreettejä tiloja:

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

Tässä on pakollinen määrite: app:values – ilman tätä RangeSlider palaa normaaliin Slider käyttäytymiseen. Tässä attribuutissa on annettava joukko arvoja:

res/values/arrays.xml

1
2
3
4
5
6

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>

Näillä arrayillä on kaksi ja kolme arvoa, ja jatkuva RangeSlider saa alueen, jossa on kaksi arvoa, kun taas diskreetti RangeSlider saa alueen, jossa on kolme arvoa. Tämä antaa meille joitakin mielenkiintoisia uusia käyttäytymismalleja:

Ylimmässä esimerkissä on yksinkertainen kahden arvon alue, jonka avulla käyttäjä voi määrittää alueen, jolla on ylä- ja alarajat. Yleinen alue, jonka asetamme RangeSlider:lle, rajoittaa maksimi- ja minimiarvoja, jotka käyttäjä voi määrittää.

Alempi esimerkki on vielä mielenkiintoisempi. Sen lisäksi, että se on diskreetti RangeSlider, joukon kolme arvoa luovat kolme peukalopistettä ohjaimeen. Pelkän ylä- ja alarajan lisäksi käyttäjä voi myös määrittää pisteen jonnekin ylä- ja alarajan sisäpuolelle. Käyttäjä ei voi vetää keskimmäistä peukalopistettä ylä- ja alapisteiden asettamien rajojen ulkopuolelle. Vedettävän peukalopisteen merkintä piirretään aina muiden mahdollisesti päällekkäisten merkintöjen päälle. Tämä on tärkeää, jotta käyttäjä tietää, mitä arvoa hän on parhaillaan valitsemassa.

Voidaan hakea valitut arvot kutsumalla RangeSlider:n getValues()-metodia. Tämä palauttaa arvojen joukon, jonka koko on sama kuin kohdassa app:values määrittelemämme joukon koko.

Tarran muotoilu

Tarkkaavaisemmat ovat ehkä huomanneet, että kaksois- ja kolmoisarvojen RangeSliders tarran muotoilussa on ero. Jatkuva kaksoisarvoinen RangeSlider käyttää oletusarvoista etikettimuotoa, kun taas diskreetti kolmoisarvoinen käyttää etuliitettä Value:. Tästä pääsemmekin mukavasti siihen, että voimme muokata etikettiä:

MainActivity.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 on SAM-rajapinta, jossa on yksi getFormattedValue()-metodi, joka on tässä tapauksessa muutettu lambdaksi. Olen käyttänyt tähän format string -resurssia:

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>

Meidän on tehtävä tämä ohjelmallisesti. Olen melko varma, että tällainen muotoilujono sopisi suurimpaan osaan käyttötapauksista, joten olisi mukavaa, jos voisimme määrittää sellaisen XML-attribuutin avulla. Sellaisen soveltaminen ohjelmallisesti on kuitenkin melko triviaalia.

Conclusion

Slider ja RangeSlider ovat todella mukavia iteraatioita SeekBar:lle. Ne tarjoavat saman toiminnallisuuden ja paljon muuta. Minuun tekee erityisen vaikutuksen yksityiskohtien huomioiminen – kuten aktiivisen etiketin piirtäminen passiivisten etikettien päälle. Tämä on hyvin suunniteltu ja toteutettu ohjaus, ja aion täysin käyttää sitä!