Skip to content

Instantly share code, notes, and snippets.

@hectorAguero
Created November 13, 2024 21:20
Show Gist options
  • Save hectorAguero/fae883c60ea22db3b2f238e85d22b33d to your computer and use it in GitHub Desktop.
Save hectorAguero/fae883c60ea22db3b2f238e85d22b33d to your computer and use it in GitHub Desktop.
Shadcn like RangeSlider
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) => const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(child: ShadSlider(initialValue: RangeValues(1, 10))),
),
);
}
class ShadSliderController extends ValueNotifier<RangeValues> {
ShadSliderController({
required RangeValues initialValue,
}) : super(initialValue);
}
class ShadSlider extends StatefulWidget {
const ShadSlider({
super.key,
this.initialValue,
this.onChanged,
this.enabled = true,
this.mouseCursor,
this.disabledMouseCursor,
this.thumbColor,
this.disabledThumbColor,
this.thumbBorderColor,
this.disabledThumbBorderColor,
this.activeTrackColor,
this.inactiveTrackColor,
this.disabledActiveTrackColor,
this.disabledInactiveTrackColor,
this.trackHeight,
this.thumbRadius,
this.onChangeStart,
this.onChangeEnd,
this.divisions,
this.semanticFormatterCallback,
this.controller,
}) : assert(
(initialValue != null) ^ (controller != null),
'Either initialValue or controller must be specified',
);
final RangeValues? initialValue;
final ValueChanged<RangeValues>? onChanged;
final bool enabled;
final MouseCursor? mouseCursor;
final MouseCursor? disabledMouseCursor;
final Color? thumbColor;
final Color? disabledThumbColor;
final Color? thumbBorderColor;
final Color? disabledThumbBorderColor;
final Color? activeTrackColor;
final Color? inactiveTrackColor;
final Color? disabledActiveTrackColor;
final Color? disabledInactiveTrackColor;
final double? trackHeight;
final double? thumbRadius;
final ValueChanged<double>? onChangeStart;
final ValueChanged<double>? onChangeEnd;
final int? divisions;
final SemanticFormatterCallback? semanticFormatterCallback;
final ShadSliderController? controller;
@override
State<ShadSlider> createState() => _ShadSliderState();
}
class _ShadSliderState extends State<ShadSlider> {
late final controller = widget.controller ??
ShadSliderController(
initialValue: widget.initialValue!,
);
@override
void initState() {
super.initState();
controller.addListener(onChanged);
}
@override
void dispose() {
controller.removeListener(onChanged);
// dispose the internal controller
if (widget.controller == null) {
controller.dispose();
}
super.dispose();
}
void onChanged() {
widget.onChanged?.call(controller.value);
}
@override
Widget build(BuildContext context) {
final mTheme = Theme.of(context);
final effectiveMouseCursor = widget.mouseCursor ?? SystemMouseCursors.click;
final effectiveDisabledMouseCursor =
widget.disabledMouseCursor ?? SystemMouseCursors.forbidden;
final effectiveThumbColor = widget.thumbColor ??
mTheme.sliderTheme.thumbColor ??
mTheme.colorScheme.surface;
final effectiveMin = widget.initialValue?.start ?? 0;
final effectiveMax = widget.initialValue?.end ?? 1;
final effectiveThumbBorderColor =
widget.thumbBorderColor ?? mTheme.colorScheme.primary;
final effectiveDisabledThumbColor = widget.disabledThumbColor ??
mTheme.sliderTheme.disabledThumbColor ??
mTheme.colorScheme.surface;
final effectiveDisabledThumbBorderColor = widget.disabledThumbBorderColor ??
mTheme.colorScheme.primary.withOpacity(.5);
final effectiveActiveTrackColor = widget.activeTrackColor ??
mTheme.sliderTheme.activeTrackColor ??
mTheme.colorScheme.primary;
final effectiveInactiveTrackColor = widget.inactiveTrackColor ??
mTheme.sliderTheme.inactiveTrackColor ??
mTheme.colorScheme.secondary;
final effectiveDisabledActiveTrackColor = widget.disabledActiveTrackColor ??
mTheme.sliderTheme.disabledActiveTrackColor ??
mTheme.colorScheme.primary.withOpacity(.5);
final effectiveDisabledInactiveTrackColor =
widget.disabledInactiveTrackColor ??
mTheme.sliderTheme.disabledInactiveTrackColor ??
mTheme.colorScheme.secondary.withOpacity(.5);
final effectiveTrackHeight =
widget.trackHeight ?? mTheme.sliderTheme.trackHeight ?? 8;
final effectiveThumbRadius = widget.thumbRadius ?? 10.0;
return Theme(
data: mTheme.copyWith(
sliderTheme: mTheme.sliderTheme.copyWith(
trackHeight: effectiveTrackHeight,
rangeThumbShape: ShadSliderThumbShape(
radius: effectiveThumbRadius,
borderColor: effectiveThumbBorderColor,
disabledBorderColor: effectiveDisabledThumbBorderColor,
thumbColor: effectiveThumbColor,
disabledThumbColor: effectiveDisabledThumbColor,
),
overlayShape: SliderComponentShape.noOverlay,
activeTrackColor: effectiveActiveTrackColor,
disabledActiveTrackColor: effectiveDisabledActiveTrackColor,
inactiveTrackColor: effectiveInactiveTrackColor,
disabledInactiveTrackColor: effectiveDisabledInactiveTrackColor,
disabledThumbColor: effectiveDisabledThumbColor,
),
),
child: ValueListenableBuilder(
valueListenable: controller,
builder: (context, value, child) {
print(value);
return RangeSlider(
values: value,
min: effectiveMin,
max: effectiveMax,
mouseCursor: MaterialStateProperty.all(
widget.enabled
? effectiveMouseCursor
: effectiveDisabledMouseCursor,
),
onChanged: widget.enabled
? (v) => () {
controller.value = v;
}
: null,
onChangeStart: (v) => widget.onChangeStart!(v.start),
onChangeEnd: (v) => widget.onChangeEnd!(v.end),
divisions: widget.divisions,
semanticFormatterCallback: widget.semanticFormatterCallback,
);
},
),
);
}
}
class ShadSliderThumbShape extends RangeSliderThumbShape {
const ShadSliderThumbShape({
required this.radius,
required this.borderColor,
required this.disabledBorderColor,
required this.thumbColor,
required this.disabledThumbColor,
});
final double radius;
final Color borderColor;
final Color disabledBorderColor;
final Color thumbColor;
final Color disabledThumbColor;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) =>
Size.fromRadius(radius);
@override
void paint(
PaintingContext context,
Offset center, {
required Animation<double> activationAnimation,
required Animation<double> enableAnimation,
required SliderThemeData sliderTheme,
bool isDiscrete = false,
bool isEnabled = true,
bool isOnTop = false,
bool isPressed = false,
TextDirection textDirection = TextDirection.ltr,
Thumb thumb = Thumb.start,
}) {
final canvas = context.canvas;
final colorTween = ColorTween(
begin: disabledThumbColor,
end: thumbColor,
);
final color = colorTween.evaluate(enableAnimation)!;
canvas.drawCircle(
center,
radius,
Paint()..color = color,
);
final borderColorTween = ColorTween(
begin: disabledBorderColor,
end: borderColor,
);
final effectiveBorderColor = borderColorTween.evaluate(enableAnimation)!;
final paintBorder = Paint()
..color = effectiveBorderColor
..strokeWidth = 2
..style = PaintingStyle.stroke;
canvas.drawCircle(center, radius, paintBorder);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment