Skip to Content
ComponentsPrimitiveSlider

PrimitiveSlider

A slider component for selecting a value from a range. It demonstrates how to handle continuous drag gestures and map them to a value range, rendering the result with custom painting.

Overview

PrimitiveSlider provides a range selection interface using only primitive Flutter components. It tracks gestures to update a value and repaints the slider track and thumb accordingly.

Primitives Used: CustomPaint, Canvas, GestureDetector

Primitive Slider ShowcaseOpen in new tab ↗

Basic Usage

import 'package:primitive_ui/primitive_ui.dart'; double _value = 0.5; PrimitiveSlider( value: _value, onChanged: (newValue) { setState(() { _value = newValue; }); }, )

API Reference

Constructor

PrimitiveSlider({ Key? key, required double value, ValueChanged<double>? onChanged, double min = 0.0, double max = 1.0, Color activeColor = const Color(0xFF2196F3), Color inactiveColor = const Color(0xFFE0E0E0), Color thumbColor = const Color(0xFFFFFFFF), double thumbRadius = 10.0, double trackHeight = 4.0, ValueChanged<double>? onChangeStart, ValueChanged<double>? onChangeEnd, String? semanticsLabel, double? semanticsStep, Duration duration = const Duration(milliseconds: 200), Curve curve = Curves.easeInOut, })

Parameters

ParameterTypeRequiredDefaultDescription
valuedoubleThe currently selected value for this slider
onChangedValueChanged<double>?nullCalled during a drag when the user is selecting a new value
mindouble0.0The minimum value the user can select
maxdouble1.0The maximum value the user can select
activeColorColorColor(0xFF2196F3)The color of the track up to the thumb
inactiveColorColorColor(0xFFE0E0E0)The color of the track after the thumb
thumbColorColorColor(0xFFFFFFFF)The color of the thumb (circle)
thumbRadiusdouble10.0The radius of the thumb shape
trackHeightdouble4.0The thickness of the track
onChangeStartValueChanged<double>?nullCalled when the user starts selecting a new value
onChangeEndValueChanged<double>?nullCalled when the user is done selecting a new value
semanticsLabelString?nullAccessibility label (defaults to "Slider")
semanticsStepdouble?nullValue step for keyboard actions (defaults to 10% of range)
durationDurationDuration(milliseconds: 200)Animation duration for programmatic value changes
curveCurveCurves.easeInOutAnimation curve for programmatic value changes

Examples

Custom Colors and Thumb

PrimitiveSlider( value: _value, onChanged: (v) => setState(() => _value = v), )

Handling Drag Events

PrimitiveSlider( value: _sliderValue, onChanged: (value) { setState(() => _sliderValue = value); }, onChangeStart: (value) { print('Started dragging at $value'); }, onChangeEnd: (value) { print('Finished dragging at $value'); }, )

Implementation Details

The PrimitiveSlider is implemented using:

  1. GestureDetector: Specifically onHorizontalDragUpdate, onHorizontalDragStart, onHorizontalDragEnd, and onTapDown. These callbacks provide the global position of the interaction.
  2. Coordinate Mapping: The component converts the global touch position to a local position using RenderBox.globalToLocal. It then calculates the percentage of the drag along the track width.
  3. CustomPaint: A CustomPainter draws two lines (active and inactive track) and a circle (thumb) based on the current value.
  4. Shadows: The thumb has a subtle shadow drawn using BoxShadow painting logic on the canvas.

Implicit Animations

The slider supports implicit animations for value changes:

  • When value changes programmatically (not via drag), it animates smoothly to the new position using TweenAnimationBuilder.
  • During drag interactions, the animation duration is set to zero to ensure immediate responsiveness.

This allows for smooth “seek” behavior when tapping the track or updating the value from an external source, without sacrificing the direct feel of dragging.

This component demonstrates the core concept of “unidirectional data flow” in UI: the gesture updates the state, which triggers a rebuild, which passes the new value to the painter.

Limitations

Known Limitations:

  • Width must be bounded by parent (e.g., inside a SizedBox or constrained column) or it defaults to 200.0.
  • No support for divisions or discrete values yet.

Source Code

View the complete implementation in the GitHub repository .

Last updated on