Skip to main content

Sliders & Value Pickers

Coming from Buttons, let's move from "pick one of a few options" to "pick — or show — a value somewhere along a range."

info

Most controls here are built on ConfinedValueModel, which keeps a value clamped within a range. That's the same model a Behavior styles, so NativeTheme (Web, Desktop)/BasicTheme cover all of them.

You can further restrict values using a Marker and SnappingPolicy.

Which one do you actually want?

If you need to...Reach for
Pick one value along a rangeSlider (bar) or CircularSlider (dial)
Pick a range — two bounding valuesRangeSlider or CircularRangeSlider
Show a value, with no user interactionProgressBar
Pick a value as a star ratingStarRater
Step through a fixed, discrete set of valuesSpinButton

Straight vs. circular is purely presentation — pick whichever reads more naturally for what the value represents (a volume level suits a bar; a compass heading suits a dial).

Slider​

Holds a strongly typed value within a specified range, and lets the user drag to change it — the straight-line version everyone already knows.

Holds a strongly typed value within a specified range, and lets the user drag to change it — the straight-line version everyone already knows. Give it a range by passing a ClosedRange or a ConfinedValueModel in the constructor, and it'll notify you whenever the value or the range itself changes.

Strongly typed, so integers snap for free

Every Slider variant on this page is strongly typed — build an Int slider, and it snaps to whole numbers automatically, with no extra setup. Want something more specific than "every integer" — a slider that only lands on 5, 10, or 25, say? Supply a Marker (the set of values to snap to) and a SnappingPolicy (how the handle attaches to them) to restrict the range further.

Non-linearity​

A Slider's value doesn't have to map to its handle position in a straight line — supply a custom InvertibleFunction to bias the range however your data calls for (logarithmic, say).

package controls import io.nacular.doodle.controls.range.InvertibleFunction import io.nacular.doodle.controls.range.Slider import kotlin.math.log import kotlin.math.pow //sampleStart /** * Logarithmic function and inverse https://www.desmos.com/calculator/qq59ey0bub */ private object LogFunction: InvertibleFunction { override fun invoke (value: Float) = log((10f - 1) * value + 1, 10f) override fun inverse(value: Float) = (10f.pow(value) - 1)/(10 - 1) } val logarithmicSlider = Slider(0.0 .. 1.0, function = LogFunction) //sampleEnd

Animation​

Want the handle to glide to a new value instead of jumping there? AbstractSliderBehavior and AbstractRangeSliderBehavior support handle-movement animations through the animateHandleMove parameter that decides how a handle travels to its new value. They also have hooks that report when the pointer has pressed or released in a way that moves the handles, which provide a clean moment to react visually to a click.

Inspired by MUI Music Player
info

One thing worth knowing up front: only the handle's travel is animated. The slider's value itself still updates instantly.

CircularSlider​

The same idea as Slider, wrapped around a circle instead of a line — a natural fit whenever a dial reads more clearly than a bar.

RangeSlider​

Holds a strongly typed inner range within a specified outer range, and lets the user drag either end to change it — think "select a price range" rather than "pick one number." Same constructor pattern as Slider: pass ClosedRanges or a ConfinedRangeModel, and the same Marker/SnappingPolicy combination works here too, for confining either end to a predefined set of values.

CircularRangeSlider​

RangeSlider, bent into a circle — the range-picking counterpart to CircularSlider.

ProgressBar​

The read-only sibling in this family — shows a value within a range, but nothing the user can drag. Reach for this whenever you're reporting progress rather than asking for input. Same constructor pattern as the rest of the family: a ClosedRange or ConfinedValueModel, with change notifications built in.

ProgressBar is actually a specialization of the more general ProgressIndicator — reach for that one directly if a straight bar isn't the shape you want. A circular presentation is available via basicCircularProgressIndicatorBehavior.

PathProgressIndicatorBehavior draws progress along an arbitrary Path instead — handy if progress should trace something other than a bar or a ring.

tip

Updating a ProgressBar's value by hand jumps abruptly. Drive it through by animate(...) from Animation instead, and each update glides to the new value rather than snapping.

StarRater​

A value picker dressed as star ratings — a highly customizable way to display and collect a value somewhere in [0, n], just with a presentation everyone already recognizes from a hundred other apps.

SpinButton​

Steps through a set of values one at a time — think a "±" number stepper, or cycling through a list of named options. It works well when the options are few, or when the input is naturally incremental, like a quantity to purchase. The set isn't necessarily fixed, either: SpinButton takes a SpinButtonModel that behaves like an Iterator, so it can represent an open-ended list of values that don't all need to be loaded up front.

MutableSpinButton lets you change its values directly, or by updating its underlying model — click into it and hit enter to commit an edit, or click away to cancel it.

Recap​

  • Slider/CircularSlider pick one value; RangeSlider/CircularRangeSlider pick two, bounding a range.
  • ProgressBar is the read-only member of the family — use it for reporting a value, not collecting one.
  • StarRater and SpinButton are both value pickers wearing a more specific presentation — a rating, or a step-through list.
  • Reach for a circular variant when a dial genuinely reads more naturally than a bar; otherwise, the straight-line versions are the simpler default.

Next: on to Selection & Text for labels, text input, and picking from a list.