Implementing Draggable Progress Controls with Android SeekBar

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:

  1. onProgressChanged(SeekBar bar, int value, boolean fromUser)
    Fires whenever the numeric value changes. The fromUser boolean distinguishes between programmatic updates (false) and physicla thumb dragging (true).

  2. onStartTrackingTouch(SeekBar bar)
    Invoked once when the user first grabs the thumb.

  3. 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.

Tags: Android SeekBar ProgressBar UI Development kotlin

Posted on Sun, 18 Oct 2026 16:06:35 +0000 by desmortes