I want to create a slider, like a volume in the windows control panel.
I had found the solution by extending the SliderTrackShape Class and using the class in Slider Theme.
class TriangleTrackShape extends SliderTrackShape {
#override
Rect getPreferredRect({
required RenderBox parentBox,
Offset offset = Offset.zero,
SliderThemeData? sliderTheme,
bool? isEnabled,
bool? isDiscrete,
}) {
final double thumbWidth =
sliderTheme!.thumbShape!.getPreferredSize(true, isDiscrete!).width;
const double trackHeight = 2.0;
assert(thumbWidth >= 0);
assert(trackHeight >= 0);
assert(parentBox.size.width >= thumbWidth);
assert(parentBox.size.height >= trackHeight);
final double trackLeft = offset.dx + thumbWidth / 2;
final double trackTop =
offset.dy + (parentBox.size.height - trackHeight) / 2;
final double trackWidth = parentBox.size.width - thumbWidth;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
#override
void paint(
PaintingContext context,
Offset offset, {
required RenderBox parentBox,
SliderThemeData? sliderTheme,
Animation<double>? enableAnimation,
TextDirection? textDirection,
Offset? thumbCenter,
bool? isDiscrete,
bool? isEnabled,
}) {
if (sliderTheme!.trackHeight == 0) {
return;
}
final Rect trackRect = getPreferredRect(
parentBox: parentBox,
offset: offset,
sliderTheme: sliderTheme,
isEnabled: isEnabled,
isDiscrete: isDiscrete,
);
// Paint color for active and inactive segments of the track. We can also define the colors to be gradient here.
final Paint activePaint = Paint()
..color = Colors.orange
..style = PaintingStyle.fill;
final Paint inactivePaint = Paint()
..color = appLineColor
..style = PaintingStyle.fill;
Paint leftTrackPaint = activePaint;
Paint rightTrackPaint = inactivePaint;
// For LTR/RTL support.
switch (textDirection) {
case TextDirection.ltr:
leftTrackPaint = activePaint;
rightTrackPaint = inactivePaint;
break;
case TextDirection.rtl:
leftTrackPaint = inactivePaint;
rightTrackPaint = activePaint;
break;
}
// Since the triangle resembles the right triangle find the y co-ordinate (height with respect to the selection value, so the path of active and inactive segements can be drawn)
const double trackHeightVariation = 12;
const double trackShiftAlongY = 6; // Shift the track so that the thumb will be placed in the center.
final double trackDistance = (trackRect.right - trackRect.left).abs();
final double customTrackHeight =
(trackRect.bottom - (trackRect.top - trackHeightVariation));
final valuePercent = ((thumbCenter!.dx - trackRect.left) / 1).abs();
final yPoint = ((customTrackHeight / trackDistance) * valuePercent);
final activePathSegment = Path()
..moveTo(trackRect.left, trackRect.bottom + trackShiftAlongY)
..lineTo(trackRect.left, trackRect.top + trackShiftAlongY)
..lineTo(thumbCenter.dx, trackRect.top - yPoint + trackShiftAlongY)
..lineTo(thumbCenter.dx, trackRect.bottom + trackShiftAlongY)
..close();
final inactivePathSegment = Path()
..moveTo(thumbCenter.dx, trackRect.bottom + trackShiftAlongY)
..lineTo(thumbCenter.dx, trackRect.top - yPoint + trackShiftAlongY)
..lineTo(trackRect.right,
trackRect.top - trackHeightVariation + trackShiftAlongY)
..lineTo(trackRect.right, trackRect.bottom + trackShiftAlongY)
..close();
context.canvas.drawPath(activePathSegment, leftTrackPaint);
context.canvas.drawPath(inactivePathSegment, rightTrackPaint);
// If we want shadows to the track or active portion we can define that and draw that.
}
}
I am trying to build a pie chart that will look like this:
I've tried both Flutter_Charts and FL_Chart, but it seems none of them support a rounded corner and spaced items in the pie chart.
Does anyone know what is the best way to achieve this design as a pie chart?
Thank you!
A very similar version to your chart can easily be achieved with the CustomPaint widget.
Here is the resulting chart
To achieve this you will just need a very rudimentary CustomPainter that draws arcs across its canvas.
The rounding effect is achieved through the strokeCap attribute of the Paint that is used to draw the stroke. Sadly StrokeCap only supports
round and square stroke endings.
A rounded rectangle effect like the one in your screenshot cannot be achieved through this.
Colors are achieved by using a separate Paint for each stroke.
// this is used to pass data about chart values to the widget
class PieChartData {
const PieChartData(this.color, this.percent);
final Color color;
final double percent;
}
// our pie chart widget
class PieChart extends StatelessWidget {
PieChart({
required this.data,
required this.radius,
this.strokeWidth = 8,
this.child,
Key? key,
}) : // make sure sum of data is never ovr 100 percent
assert(data.fold<double>(0, (sum, data) => sum + data.percent) <= 100),
super(key: key);
final List<PieChartData> data;
// radius of chart
final double radius;
// width of stroke
final double strokeWidth;
// optional child; can be used for text for example
final Widget? child;
#override
Widget build(context) {
return CustomPaint(
painter: _Painter(strokeWidth, data),
size: Size.square(radius),
child: SizedBox.square(
// calc diameter
dimension: radius * 2,
child: Center(
child: child,
),
),
);
}
}
// responsible for painting our chart
class _PainterData {
const _PainterData(this.paint, this.radians);
final Paint paint;
final double radians;
}
class _Painter extends CustomPainter {
_Painter(double strokeWidth, List<PieChartData> data) {
// convert chart data to painter data
dataList = data
.map((e) => _PainterData(
Paint()
..color = e.color
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round,
// remove padding from stroke
(e.percent - _padding) * _percentInRadians,
))
.toList();
}
static const _percentInRadians = 0.062831853071796;
// this is the gap between strokes in percent
static const _padding = 4;
static const _paddingInRadians = _percentInRadians * _padding;
// 0 radians is to the right, but since we want to start from the top
// we'll use -90 degrees in radians
static const _startAngle = -1.570796 + _paddingInRadians / 2;
late final List<_PainterData> dataList;
#override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
// keep track of start angle for next stroke
double startAngle = _startAngle;
for (final data in dataList) {
final path = Path()..addArc(rect, startAngle, data.radians);
startAngle += data.radians + _paddingInRadians;
canvas.drawPath(path, data.paint);
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return oldDelegate != this;
}
}
You can check out the dartpad to experiment with a working example.
I am positive that the same chart you provided in that picture can be
achieved with a CustomPainter but that will be a lot more complex.
I am trying to make custom tickMarkShape in my flutter project. From docs, I read that it is possible to control tick mark shape with SliderTickMarkShape. But it gives me only one option SliderTickMarkShape.noTickMark which removes the tick mark shape, but I would like to make it like a stick. So how can I set custom tick mark shape in flutter?. Thanks in advance.
In order to customize the tick as the following image:
You need to extend SliderTickMarkShape. In this case I called LineSliderTickMarkShape. Below is the source code but it is basically a copy of RoundSliderTickMarkShape, it just changes the context.canvas.drawLine line to customize the shape of the mark. RoundSliderTickMarkShape is the default one.
class LineSliderTickMarkShape extends SliderTickMarkShape {
const LineSliderTickMarkShape({
this.tickMarkRadius,
});
final double? tickMarkRadius;
#override
Size getPreferredSize({
required SliderThemeData sliderTheme,
required bool isEnabled,
}) {
assert(sliderTheme != null);
assert(sliderTheme.trackHeight != null);
assert(isEnabled != null);
return Size.fromRadius(tickMarkRadius ?? sliderTheme.trackHeight! / 4);
}
#override
void paint(
PaintingContext context,
Offset center, {
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required Animation<double> enableAnimation,
required TextDirection textDirection,
required Offset thumbCenter,
required bool isEnabled,
}) {
Color? begin;
Color? end;
switch (textDirection) {
case TextDirection.ltr:
final bool isTickMarkRightOfThumb = center.dx > thumbCenter.dx;
begin = isTickMarkRightOfThumb ? sliderTheme.disabledInactiveTickMarkColor : sliderTheme.disabledActiveTickMarkColor;
end = isTickMarkRightOfThumb ? sliderTheme.inactiveTickMarkColor : sliderTheme.activeTickMarkColor;
break;
case TextDirection.rtl:
final bool isTickMarkLeftOfThumb = center.dx < thumbCenter.dx;
begin = isTickMarkLeftOfThumb ? sliderTheme.disabledInactiveTickMarkColor : sliderTheme.disabledActiveTickMarkColor;
end = isTickMarkLeftOfThumb ? sliderTheme.inactiveTickMarkColor : sliderTheme.activeTickMarkColor;
break;
}
final Paint paint = Paint()..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!;
final double tickMarkRadius = getPreferredSize(
isEnabled: isEnabled,
sliderTheme: sliderTheme,
).width / 2;
if (tickMarkRadius > 0) {
context.canvas.drawLine(Offset(center.dx - 5, center.dy - 5), Offset(center.dx + 5, center.dy + 5), paint);
}
}
}
Once LineSliderTickMarkShape is defined, you can use it to customize your Slider like this:
#override
Widget build(BuildContext context) {
return SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 20,
tickMarkShape: const LineSliderTickMarkShape()
),
child: Slider(
...
I am trying to implement a custom progress indicator for flutter slider. As for now, I have the given code that draws the indicator.
class InsideSliderValueIndicator extends SliderComponentShape {
#override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.zero;
}
#override
void paint(
PaintingContext context,
Offset center, {
Animation<double> activationAnimation,
Animation<double> enableAnimation,
bool isDiscrete,
TextPainter labelPainter,
RenderBox parentBox,
SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
}) {
final sliderWidth = parentBox.size.width;
final sliderBoxOffset = parentBox.localToGlobal(Offset.zero);
labelPainter.paint(
context.canvas,
Offset(sliderBoxOffset.dx + sliderWidth / 2 - labelPainter.width / 2,
center.dy - labelPainter.height / 2));
}
}
I have also customized the slider to remove the thumb. Here's what it looks like:
As you can see, the text is not centered in the slider.
The problem with this code is that sliderBoxOffset doesn't return the box of the "slider", but of the parent card, and center is actually the position of your thumb on the slider relative to the parent.
So my question is: How can I get the center of the slider from the given parameters ?
Thank you for your help
I found a weird solution that works for my use-case
final sliderWidth = parentBox.size.width;
final halfSliderWidth = sliderWidth / 2;
final translateX = value < 0.5 ? halfSliderWidth - (value * sliderWidth) : halfSliderWidth - (value * sliderWidth);
labelPainter.paint(context.canvas, center.translate(translateX - labelPainter.width / 2, -labelPainter.height / 2));
I'm however pretty sure this won't work if the slider is not centered in its parent. I'm still wondering if there is a less dirty way to do this.
I'm using the basic slider, and I found how to update just the parts of the slider theme data that I want to change like trackHeight, but unfortunately I'm not sure how to update the field for "trackShape". Here is what I do in the main app to update track height for example:
final SliderThemeData tweakedSliderTheme = Theme.of(context).sliderTheme.copyWith(
trackHeight: 22.0,
//trackShape: RectangularSliderTrackShape(), // How do I update this??
);
I did try using ClipRRect() around the slider widget and that had no effect.
Here is a simple page for one slider:
import 'package:flutter/material.dart';
class RoomControl extends StatefulWidget {
#override
_RoomControlState createState() => _RoomControlState();
}
class _RoomControlState extends State<RoomControl> {
double _value = 0.0;
void _setvalue(double value) => setState(() => _value = value);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Name here'),
),
//hit Ctrl+space in intellij to know what are the options you can use in flutter widgets
body: Container(
padding: EdgeInsets.all(32.0),
child: Center(
child: Column(
children: <Widget>[
Text('Value: ${(_value * 100).round()}'),
ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child:Slider(
value: _value,
onChanged: _setvalue,
divisions: 10,
)
)
],
),
),
),
);
}
}
Here is what that slider looks like:
UPDATE:
After getting the answer, I was able to create something like this easily by updating the tick mark shape, and the thumb shape:
final SliderThemeData tweakedSliderTheme = Theme.of(context).sliderTheme.copyWith(
trackHeight: 20.0,
thumbShape: MyRoundSliderThumbShape(enabledThumbRadius: 13.0, disabledThumbRadius: 13.0),
trackShape: MyRoundSliderTrackShape(), // TODO: this is hard coded right now for 20 track height
inactiveTrackColor: lightGreySliderColor,
activeTickMarkColor: Color(blackPrimaryValue),
inactiveTickMarkColor: colorizedMenuColor,
tickMarkShape: MyRectSliderTickMarkShape(tickMarkRadius: 4.0),
);
There was a bit of a trick to the tick mark shape. If you make it too big it just skips painting it! Probably makes sense but I didn't know much about paint/rendering so it took me a while to learn how to get the tick marks (Rect) to show up properly
I've copied the base code from RectangularSliderTrackShape into a new class called RoundSliderTrackShape.
round_slider_track_shape.dart
import 'dart:math';
import 'package:flutter/material.dart';
class RoundSliderTrackShape extends SliderTrackShape {
/// Create a slider track that draws 2 rectangles.
const RoundSliderTrackShape({ this.disabledThumbGapWidth = 2.0 });
/// Horizontal spacing, or gap, between the disabled thumb and the track.
///
/// This is only used when the slider is disabled. There is no gap around
/// the thumb and any part of the track when the slider is enabled. The
/// Material spec defaults this gap width 2, which is half of the disabled
/// thumb radius.
final double disabledThumbGapWidth;
#override
Rect getPreferredRect({
RenderBox parentBox,
Offset offset = Offset.zero,
SliderThemeData sliderTheme,
bool isEnabled,
bool isDiscrete,
}) {
final double overlayWidth = sliderTheme.overlayShape.getPreferredSize(isEnabled, isDiscrete).width;
final double trackHeight = sliderTheme.trackHeight;
assert(overlayWidth >= 0);
assert(trackHeight >= 0);
assert(parentBox.size.width >= overlayWidth);
assert(parentBox.size.height >= trackHeight);
final double trackLeft = offset.dx + overlayWidth / 2;
final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
// TODO(clocksmith): Although this works for a material, perhaps the default
// rectangular track should be padded not just by the overlay, but by the
// max of the thumb and the overlay, in case there is no overlay.
final double trackWidth = parentBox.size.width - overlayWidth;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
#override
void paint(
PaintingContext context,
Offset offset, {
RenderBox parentBox,
SliderThemeData sliderTheme,
Animation<double> enableAnimation,
TextDirection textDirection,
Offset thumbCenter,
bool isDiscrete,
bool isEnabled,
}) {
// If the slider track height is 0, then it makes no difference whether the
// track is painted or not, therefore the painting can be a no-op.
if (sliderTheme.trackHeight == 0) {
return;
}
// Assign the track segment paints, which are left: active, right: inactive,
// but reversed for right to left text.
final ColorTween activeTrackColorTween = ColorTween(begin: sliderTheme.disabledActiveTrackColor , end: sliderTheme.activeTrackColor);
final ColorTween inactiveTrackColorTween = ColorTween(begin: sliderTheme.disabledInactiveTrackColor , end: sliderTheme.inactiveTrackColor);
final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation);
final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation);
Paint leftTrackPaint;
Paint rightTrackPaint;
switch (textDirection) {
case TextDirection.ltr:
leftTrackPaint = activePaint;
rightTrackPaint = inactivePaint;
break;
case TextDirection.rtl:
leftTrackPaint = inactivePaint;
rightTrackPaint = activePaint;
break;
}
// Used to create a gap around the thumb iff the slider is disabled.
// If the slider is enabled, the track can be drawn beneath the thumb
// without a gap. But when the slider is disabled, the track is shortened
// and this gap helps determine how much shorter it should be.
// TODO(clocksmith): The new Material spec has a gray circle in place of this gap.
double horizontalAdjustment = 0.0;
if (!isEnabled) {
final double disabledThumbRadius = sliderTheme.thumbShape.getPreferredSize(false, isDiscrete).width / 2.0;
final double gap = disabledThumbGapWidth * (1.0 - enableAnimation.value);
horizontalAdjustment = disabledThumbRadius + gap;
}
final Rect trackRect = getPreferredRect(
parentBox: parentBox,
offset: offset,
sliderTheme: sliderTheme,
isEnabled: isEnabled,
isDiscrete: isDiscrete,
);
final Rect leftTrackSegment = Rect.fromLTRB(trackRect.left, trackRect.top, thumbCenter.dx - horizontalAdjustment, trackRect.bottom);
// Left Arc
context.canvas.drawArc(
Rect.fromCircle(center: Offset(trackRect.left, trackRect.top + 11.0), radius: 11.0),
-pi * 3 / 2, // -270 degrees
pi, // 180 degrees
false,
trackRect.left - thumbCenter.dx == 0.0 ? rightTrackPaint : leftTrackPaint
);
// Right Arc
context.canvas.drawArc(
Rect.fromCircle(center: Offset(trackRect.right, trackRect.top + 11.0), radius: 11.0),
-pi / 2, // -90 degrees
pi, // 180 degrees
false,
trackRect.right - thumbCenter.dx == 0.0 ? leftTrackPaint : rightTrackPaint
);
context.canvas.drawRect(leftTrackSegment, leftTrackPaint);
final Rect rightTrackSegment = Rect.fromLTRB(thumbCenter.dx + horizontalAdjustment, trackRect.top, trackRect.right, trackRect.bottom);
context.canvas.drawRect(rightTrackSegment, rightTrackPaint);
}
}
The following is how the SliderTheme is setup.
import 'package:flutter_stackoverflow/round_slider_track_shape.dart';
...
sliderTheme: Theme.of(context).sliderTheme.copyWith(
trackHeight: 22.0,
trackShape: RoundSliderTrackShape(),
activeTrackColor: Colors.green,
// trackShape: RectangularSliderTrackShape(),
),
What was added was two circle arcs to the side of the SliderTrack
// Left Arc
context.canvas.drawArc(
Rect.fromCircle(center: Offset(trackRect.left, trackRect.top + 11.0), radius: 11.0),
-pi * 3 / 2, // -270 degrees
pi, // 180 degrees
false,
trackRect.left - thumbCenter.dx == 0.0 ? rightTrackPaint : leftTrackPaint
);
// Right Arc
context.canvas.drawArc(
Rect.fromCircle(center: Offset(trackRect.right, trackRect.top + 11.0), radius: 11.0),
-pi / 2, // -90 degrees
pi, // 180 degrees
false,
trackRect.right - thumbCenter.dx == 0.0 ? leftTrackPaint : rightTrackPaint
);
Answer by #Jun Xiang seems to work but I did a minor change in the following code:
// Left Arc
context.canvas.drawArc(
Rect.fromCircle(center: Offset(trackRect.left, trackRect.top + 11.0), radius: 11.0),
-pi * 3 / 2, // -270 degrees
pi, // 180 degrees
false,
trackRect.left - thumbCenter.dx == 0.0 ? rightTrackPaint : leftTrackPaint
);
// Right Arc
context.canvas.drawArc(
Rect.fromCircle(center: Offset(trackRect.right, trackRect.top + 11.0), radius: 11.0),
-pi / 2, // -90 degrees
pi, // 180 degrees
false,
trackRect.right - thumbCenter.dx == 0.0 ? leftTrackPaint : rightTrackPaint
);
Instead of using 11.0 I used sliderTheme.trackHeight * 1/2 and seems to work for all track heights entered (not only 22).
PS: I couldn't comment so I posted an answer.
I have a better solution
import 'package:flutter/material.dart';
class RoundSliderTrackShape extends SliderTrackShape {
const RoundSliderTrackShape({this.disabledThumbGapWidth = 2.0, this.radius = 0});
final double disabledThumbGapWidth;
final double radius;
#override
Rect getPreferredRect({
RenderBox parentBox,
Offset offset = Offset.zero,
SliderThemeData sliderTheme,
bool isEnabled,
bool isDiscrete,
}) {
final double overlayWidth = sliderTheme.overlayShape.getPreferredSize(isEnabled, isDiscrete).width;
final double trackHeight = sliderTheme.trackHeight;
assert(overlayWidth >= 0);
assert(trackHeight >= 0);
assert(parentBox.size.width >= overlayWidth);
assert(parentBox.size.height >= trackHeight);
final double trackLeft = offset.dx + overlayWidth / 2;
final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
final double trackWidth = parentBox.size.width - overlayWidth;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
#override
void paint(
PaintingContext context,
Offset offset, {
RenderBox parentBox,
SliderThemeData sliderTheme,
Animation<double> enableAnimation,
TextDirection textDirection,
Offset thumbCenter,
bool isDiscrete,
bool isEnabled,
}) {
if (sliderTheme.trackHeight == 0) {
return;
}
final ColorTween activeTrackColorTween =
ColorTween(begin: sliderTheme.disabledActiveTrackColor, end: sliderTheme.activeTrackColor);
final ColorTween inactiveTrackColorTween =
ColorTween(begin: sliderTheme.disabledInactiveTrackColor, end: sliderTheme.inactiveTrackColor);
final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation);
final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation);
Paint leftTrackPaint;
Paint rightTrackPaint;
switch (textDirection) {
case TextDirection.ltr:
leftTrackPaint = activePaint;
rightTrackPaint = inactivePaint;
break;
case TextDirection.rtl:
leftTrackPaint = inactivePaint;
rightTrackPaint = activePaint;
break;
}
double horizontalAdjustment = 0.0;
if (!isEnabled) {
final double disabledThumbRadius =
sliderTheme.thumbShape.getPreferredSize(false, isDiscrete).width / 2.0;
final double gap = disabledThumbGapWidth * (1.0 - enableAnimation.value);
horizontalAdjustment = disabledThumbRadius + gap;
}
final Rect trackRect = getPreferredRect(
parentBox: parentBox,
offset: offset,
sliderTheme: sliderTheme,
isEnabled: isEnabled,
isDiscrete: isDiscrete,
);
//Modify this side
final RRect leftTrackSegment = RRect.fromLTRBR(trackRect.left, trackRect.top,
thumbCenter.dx - horizontalAdjustment, trackRect.bottom, Radius.circular(radius));
context.canvas.drawRRect(leftTrackSegment, leftTrackPaint);
final RRect rightTrackSegment = RRect.fromLTRBR(thumbCenter.dx + horizontalAdjustment, trackRect.top,
trackRect.right, trackRect.bottom, Radius.circular(radius));
context.canvas.drawRRect(rightTrackSegment, rightTrackPaint);
}
}
use:
trackShape: RoundSliderTrackShape(radius: 8)
The default trackShape for slider is now a RoundedRectSliderTrackShape in master branch:
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/slider_theme.dart#L462
Does that work for you?
RectangularSliderTrackShape can be preserved by overriding the trackShape in the SliderThemeData.
Create custom shape like this, In this i am drawing 2 circles on thumb
class SliderThumbShape extends SliderComponentShape {
/// Create a slider thumb that draws a circle.
const SliderThumbShape({
this.enabledThumbRadius = 10.0,
this.disabledThumbRadius,
this.elevation = 1.0,
this.pressedElevation = 6.0,
});
/// The preferred radius of the round thumb shape when the slider is enabled.
///
/// If it is not provided, then the material default of 10 is used.
final double enabledThumbRadius;
/// The preferred radius of the round thumb shape when the slider is disabled.
///
/// If no disabledRadius is provided, then it is equal to the
/// [enabledThumbRadius]
final double disabledThumbRadius;
double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
/// The resting elevation adds shadow to the unpressed thumb.
///
/// The default is 1.
///
/// Use 0 for no shadow. The higher the value, the larger the shadow. For
/// example, a value of 12 will create a very large shadow.
///
final double elevation;
/// The pressed elevation adds shadow to the pressed thumb.
///
/// The default is 6.
///
/// Use 0 for no shadow. The higher the value, the larger the shadow. For
/// example, a value of 12 will create a very large shadow.
final double pressedElevation;
#override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.fromRadius(isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
}
#override
void paint(
PaintingContext context,
Offset center, {
Animation<double> activationAnimation,
#required Animation<double> enableAnimation,
bool isDiscrete,
TextPainter labelPainter,
RenderBox parentBox,
#required SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
double textScaleFactor,
Size sizeWithOverflow,
}) {
assert(context != null);
assert(center != null);
assert(enableAnimation != null);
assert(sliderTheme != null);
assert(sliderTheme.disabledThumbColor != null);
assert(sliderTheme.thumbColor != null);
assert(!sizeWithOverflow.isEmpty);
final Canvas canvas = context.canvas;
final Tween<double> radiusTween = Tween<double>(
begin: _disabledThumbRadius,
end: enabledThumbRadius,
);
final double radius = radiusTween.evaluate(enableAnimation);
final Tween<double> elevationTween = Tween<double>(
begin: elevation,
end: pressedElevation,
);
final double evaluatedElevation = elevationTween.evaluate(activationAnimation);
{
final Path path = Path()
..addArc(Rect.fromCenter(center: center, width: 1 * radius, height: 1 * radius), 0, math.pi * 2);
Paint paint = Paint()..color = Colors.black;
paint.strokeWidth = 15;
paint.style = PaintingStyle.stroke;
canvas.drawCircle(
center,
radius,
paint,
);
{
Paint paint = Paint()..color = Colors.white;
paint.style = PaintingStyle.fill;
canvas.drawCircle(
center,
radius,
paint,
);
}
}
}
}
Then use it in your widget tree like this
SliderTheme(
data: SliderThemeData(
activeTrackColor: Colors.blue,
inactiveTrackColor: Color(0xffd0d2d3),
trackHeight: 2,
thumbShape: SliderThumbShape(),
),
child: Slider(
onChanged: (value) {},
value: 40.5,
max: 100,
min: 0,
),
),
The other option is to draw a thick line with a round StrokeCap. The cap radius at the two ends is in addition to the line width.
#override
void paint(PaintingContext context, Offset offset,
{required RenderBox parentBox,
required SliderThemeData sliderTheme,
required Animation<double> enableAnimation,
required Offset thumbCenter,
bool isEnabled = true,
bool isDiscrete = true,
required TextDirection textDirection}) {
final canvas = context.canvas;
final width = 200;
final height = 16;
canvas.drawLine(
Offset(0, height / 2),
Offset(width, height / 2),
Paint()
..strokeCap = StrokeCap.round
..strokeWidth = height);
}