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