Material Slider

Google hat am 16. Juli 2020 Material Components 1.3.0-alpha02 veröffentlicht. Eine Komponente, die in dieser Version viel Liebe erhalten hat, ist der bescheidene Schieberegler. Es gibt einige nette Ergänzungen zur Grundfunktionalität, und in diesem Beitrag werden wir sie ein wenig erkunden.

Bildquelle
Lizenziert unter CC 2.0

Ein Slider ist ein nützliches Steuerelement, mit dem der Benutzer Werte angeben kann, ohne die Tastatur benutzen zu müssen. Der grundlegende Anwendungsfall ist dem Android Framework SeekBar oder den AppCompatSeekBar Widgets sehr ähnlich. Für diese gibt es verschiedene Verwendungszwecke: z. B. das Scrubben zu einer bestimmten Stelle während der Audio- oder Videowiedergabe oder die Angabe eines Wertes innerhalb eines bestimmten Bereichs. Es gibt jedoch einige zusätzliche Anwendungsfälle, die Slider uns bietet, wie wir später in diesem Artikel sehen werden

Wir beginnen mit der Erstellung eines sehr einfachen Schieberegler-Steuerelements und sehen, wie es aussieht und sich verhält.

Grundlegendes Verhalten

Das Erstellen eines Material Sliders ist sehr einfach:

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>

Wir legen für beide Steuerelemente mit den Attributen valueFrom und valueTo einen Bereich von 0-100 fest. Auf diese Weise erhalten wir die folgenden Verhaltensweisen (AppCompatSeekBar steht oben und Slider unten):

Auch wenn diese sehr ähnlich aussehen, gibt es einige wichtige Unterschiede. Erstens wird Slider mit den in meinem Thema definierten Farben gestaltet, während AppCompatSeekBar Standardfarben aus der AppCompat-Bibliothek verwendet. Zweitens ist Slider etwas größer, was es für den Benutzer etwas übersichtlicher macht. Der wahrscheinlich wichtigste Unterschied ist die Hinzufügung einer Beschriftung zu Slider, wenn der Benutzer die Position verschiebt. Dadurch wird es für den Benutzer einfacher, den genauen Wert zu verstehen, den er auswählt. Wir können zwar eine separate Ansicht in unser Layout einfügen, um diesen Wert anzuzeigen, aber die Beschriftung der QuickInfo macht die Benutzeroberfläche weniger unübersichtlich. Wir können diese Funktion deaktivieren, wenn sie nicht benötigt wird. Durch Hinzufügen von app:labelBehaviour="gone" wird dies erreicht.

Es gibt jedoch noch zwei weitere Verhaltensweisen für die Beschriftung. Die erste ist die Standardeinstellung, die wir im vorherigen Beispiel sehen konnten – das ist app:labelBehaviour="floating", die das Etikett über der Ansicht schweben lässt. Die andere Option ist app:labelBehaviour="withinBounds". Diese Option wird genauso gerendert wie floating, aber der Unterschied besteht darin, wie sie die Größe von Slider beeinflusst. Bei floating schließt die gemessene Höhe des Steuerelements die Beschriftung nicht ein, während bei withinBounds die Beschriftung in die gemessene Höhe einbezogen wird. Dies kann wichtig sein, je nachdem, ob wir sicherstellen wollen, dass die Beschriftung keine anderen Steuerelemente in unserem Layout verdeckt.

Diskreter Schieberegler

Die Variante von Slider, die wir uns bisher angesehen haben, ist als kontinuierlicher Schieberegler bekannt. Das bedeutet, dass der Wert, den er zurückgibt, ein beliebiger Wert innerhalb des Bereichs sein kann. Es gibt jedoch Fälle, in denen wir dies einschränken möchten. Zum Beispiel werden von diesem kontinuierlichen Slider gebrochene Werte zurückgegeben, aber wir möchten den Wert vielleicht auf ganze Zahlen oder noch größere Schritte beschränken. Um dies zu erreichen, können wir einen diskreten Schieberegler verwenden, der eine diskrete Menge von Werten zurückgibt. Wir können einen kontinuierlichen Slider in einen diskreten umwandeln, indem wir ein einzelnes Attribut hinzufügen:

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

Durch das Hinzufügen von app:stepSize="10" gibt die Slider nur Werte zurück, die ein Vielfaches von 10 sind. Dies ändert die UX leicht:

Zunächst gibt es Häkchen, die die diskreten Werte anzeigen, die zurückgegeben werden. Diese sind zwar recht subtil, aber auf der Spur deutlich zu sehen. Der zweite Unterschied besteht darin, dass die Auswahl nicht mehr fließend ist, sondern zwischen den diskreten Werten hin und her springt, wie man am Wert der Beschriftung erkennen kann. Der Benutzer hat einen klaren Hinweis darauf, dass die Auswahl aus einer diskreten Menge von Werten erfolgt.

RangeSlider

Wir können auch eine andere Variante von Slider verwenden. Bisher haben wir gesehen, wie man mit Slider einen einzelnen Wert auswählt, aber der Benutzer kann mit RangeSlider einen Bereich auswählen.

Als wir Slider zum ersten Mal konfiguriert haben, haben wir valueFrom und valueTo verwendet, um den Wertebereich anzugeben, den der Schieberegler zurückgibt. RangeSlider ermöglicht es dem Benutzer, einen Bereich anzugeben. Ein Beispiel: Wenn wir eine Suchfunktion in einer Shopping-App anbieten, möchten wir vielleicht, dass der Benutzer eine Preisspanne angeben kann. Dies kann mit einem RangeSlider erreicht werden, der sowohl kontinuierliche als auch diskrete Modi unterstützt:

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

Hier gibt es ein erforderliches Attribut: app:values – ohne dieses fällt RangeSlider auf das Standardverhalten Slider zurück. Wir müssen ein Array von Werten in diesem Attribut angeben:

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>
<Eintrag>60</Eintrag>
</array>
<array name=“triple_slider_values“>
<Eintrag>20</Eintrag>
<Eintrag>60</Eintrag>
<Eintrag>100</Eintrag>
</array>
</resources>

Diese Arrays haben zwei bzw. drei Werte, und das kontinuierliche RangeSlider erhält den Bereich mit zwei Werten, während das diskrete RangeSlider den Bereich mit drei Werten erhält. Daraus ergeben sich einige interessante neue Verhaltensweisen:

Das obere Beispiel zeigt einen einfachen Bereich mit zwei Werten, der es dem Benutzer ermöglicht, einen Bereich mit Ober- und Untergrenzen anzugeben. Der Gesamtbereich, den wir für RangeSlider festlegen, schränkt die Höchst- und Mindestwerte ein, die der Benutzer angeben kann.

Das untere Beispiel ist noch interessanter. Es handelt sich nicht nur um einen diskreten RangeSlider, sondern die drei Werte im Array erzeugen drei Daumenpunkte im Steuerelement. Der Benutzer kann nicht nur eine obere und eine untere Grenze für den Bereich angeben, sondern auch einen Punkt irgendwo innerhalb der oberen und unteren Grenze. Der Benutzer kann den mittleren Daumenpunkt nicht außerhalb der durch den oberen und unteren Punkt festgelegten Grenzen ziehen. Die Beschriftung des Daumenpunkts, der gezogen wird, wird immer über den anderen Beschriftungen gezeichnet, die er überlappen könnte. Dies ist wichtig, damit der Benutzer weiß, welchen Wert er gerade auswählt.

Wir können die ausgewählten Werte abrufen, indem wir die Methode getValues() von RangeSlider aufrufen. Dies gibt ein Array von Werten zurück, dessen Größe der Größe des Arrays entspricht, das wir in app:values angegeben haben.

Etikettenformatierung

Den aufmerksamen Beobachtern ist vielleicht aufgefallen, dass es einen Unterschied in der Etikettenformatierung der doppel- und dreifachwertigen RangeSliders gibt. Der kontinuierliche doppelte Wert RangeSlider verwendet das Standardformat für die Beschriftung, während der diskrete dreifache Wert das Präfix Value: hat. Dies bringt uns zu der Tatsache, dass wir die Bezeichnung anpassen können:

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 ist eine SAM-Schnittstelle mit einer einzigen getFormattedValue()-Methode, die in diesem Fall in ein Lambda umgewandelt wurde. Ich habe dafür eine Formatstring-Ressource verwendet:

res/values/strings.xml

1
2
3
4

<resources>
<string name=“app_name“>Material Slider</string>
<string name=“label_format“>Wert: %1$.0f</string>
</resources>

Wir müssen das programmatisch machen. Ich bin mir ziemlich sicher, dass ein Formatstring wie dieser für die meisten Anwendungsfälle geeignet ist, daher wäre es schön, wenn wir ihn mit einem XML-Attribut angeben könnten. Eine programmatische Anwendung ist jedoch ziemlich trivial.

Schlussfolgerung

Slider und RangeSlider sind wirklich schöne Abwandlungen von SeekBar. Sie bieten die gleiche Funktionalität und noch viel mehr. Besonders beeindruckt bin ich von der Liebe zum Detail – zum Beispiel, dass die aktive Beschriftung über die passive Beschriftung gezeichnet wird. Dies ist ein gut konzipiertes und implementiertes Steuerelement, und ich habe die Absicht, es zu verwenden!