SeekBar is an interactive extension of ProgressBar that adds a draggable thumb for manual value adjustment. While ProgressBar only displays completion status visually, SeekBar captures touch events, enabling users to scrub through media timelines, adjust system brightness, or configure any continuous setting. Because it inherits from ProgressBar, every attribute and method available to the parent is also available to SeekBar.
Core Attributes
SeekBar reuses all standard ProgressBar attributes and introduces several specific to touch interaction:
android:max— Upper bound of the range. Defaults to 100.android:progress— Current value.android:secondaryProgress— Secondary buffer value, commonly used for streamed content buffering indicators.android:thumb— Drawable representing the draggable handle.android:progressDrawable— Layer-list drawable defining the appearance of both the filled and unfillde portions of the track.
The following layout places a SeekBar inside a ConstraintLayout and applies a custom track:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp">
<SeekBar
android:id="@+id/mediaScrubber"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:max="100"
android:progress="35"
android:progressDrawable="@drawable/custom_track"
android:thumb="@drawable/ic_slider_thumb"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/percentLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mediaScrubber" />
</androidx.constraintlayout.widget.ConstraintLayout>
The custom_track.xml drawable in res/drawable separates the background track from the active progression using layer-list IDs:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="#D6D6D6" />
<corners android:radius="4dp" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<solid android:color="#2196F3" />
<corners android:radius="4dp" />
</shape>
</clip>
</item>
</layer-list>
Programmatic Control
Common runtime methods include:
getMax()/setMax(int)— Read or modify the upper limit.getProgress()/setProgress(int)— Read or update the current value.incrementProgressBy(int)— Shift the current value by a relative delta.
Tracking User Interaction
To respond to gestures, attach an OnSeekBarChangeListener. It exposes three callbacks:
-
onProgressChanged(SeekBar bar, int value, boolean fromUser)
Fires whenever the numeric value changes. ThefromUserboolean distinguishes between programmatic updates (false) and physicla thumb dragging (true). -
onStartTrackingTouch(SeekBar bar)
Invoked once when the user first grabs the thumb. -
onStopTrackingTouch(SeekBar bar)
Invoked once when the user releases the thumb.
Complete Example
The following Kotlin activity wires the SeekBar to a TextView. It updates the label in real time while the thumb moves and differentiates user-driven changes from code-driven changes:
package com.example.seekbar.demo
import android.os.Bundle
import android.widget.SeekBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class ScrubberActivity : AppCompatActivity() {
private lateinit var mediaScrubber: SeekBar
private lateinit var percentLabel: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scrubber)
mediaScrubber = findViewById(R.id.mediaScrubber)
percentLabel = findViewById(R.id.percentLabel)
mediaScrubber.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(bar: SeekBar?, value: Int, triggeredByUser: Boolean) {
if (triggeredByUser) {
percentLabel.text = "User selected: $value%"
}
}
override fun onStartTrackingTouch(bar: SeekBar?) {
percentLabel.text = "Tracking touch..."
}
override fun onStopTrackingTouch(bar: SeekBar?) {
percentLabel.text = "Final position: ${bar?.progress}%"
}
})
}
}
In this flow, the onProgressChanged callback refreshes the label only when triggeredByUser is true, preventing UI flicker during background progress updates. When the gesture ends, onStopTrackingTouch captures the committed value.