Flutter How to create a Curved Slider? - flutter

This is an image I found on Stackoverflow only and I want a Slider shaped like this. I tried packages like sleek_circular_slider and other packages but I was not able to create this shape.
I can create this shape in CustomPainter and when I tried to use SliderTrackShape to customize the SLider Shape it didn't worked.
class CustomSlider extends SliderTrackShape {
#override
Rect getPreferredRect({
RenderBox? parentBox,
Offset offset = Offset.zero,
SliderThemeData? sliderTheme,
bool? isEnabled,
bool? isDiscrete,
}) {
final double thumbWidth =
sliderTheme!.thumbShape!.getPreferredSize(true, isDiscrete!).width;
final double? trackHeight = sliderTheme.trackHeight;
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, {
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,
);
final Paint fillPaint = Paint()
..color = sliderTheme.activeTrackColor!
..style = PaintingStyle.fill;
final Paint borderPaint = Paint()
..color = Colors.black
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
final pathSegment = Path()
..moveTo(0, 2500)
..quadraticBezierTo(250 / 2, 250, 250, 250 / 2);
context.canvas.drawPath(pathSegment, fillPaint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
This code renders just the thumb shape and no slider when I use this widget inside tracakShape for SliderTheme. But when I use similar code for CustomShape I am able to create this shape successfully like this :
class CustomSlider extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Color(0xff84CADE);
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 4.0;
Path path = Path();
path.moveTo(0, size.height / 2);
path.quadraticBezierTo(size.width / 2, size.height, size.width, size.height / 2);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
So I guess my question is how can I render this shape that I am succesfully rendering when I am extending from SliderTrackShape as I am unable to do so when trying to extend from SliderTrackShape class and create this shape.

Related

I try to make custom progress indicator with Custom painter but painter is not updating the value

I try to make custom progress indicator with Custom painter but painter is not updating the value
help:
can you help me to use custom painter?
also can we change circle pointer to square box with arrow pointing to progress
class MyCsPaint extends CustomPainter {
MyCsPaint({required this.progress});
final double progress;
#override
void paint(Canvas canvas, Size size) {
Paint paintBg = Paint()
..color = Colors.teal.shade100
..strokeWidth = 1
..strokeCap = StrokeCap.round;
Paint paint = Paint()
..color = Colors.teal
..strokeWidth = 5
..strokeCap = StrokeCap.round;
Offset p1bg = Offset(size.width, size.height);
Offset p2bg = Offset(0, size.height);
Offset p2 = Offset(0, size.height);
Offset p1 = Offset(size.width * progress, size.height);
canvas.drawLine(p1bg, p2bg, paintBg);
canvas.drawLine(p1, p2, paint);
Offset pc = Offset(size.width * progress, size.height * .7);
canvas.drawCircle(pc, 10, paint);
final textStyle = ui.TextStyle(
color: Colors.black,
fontSize: 16,
);
final paragraphStyle = ui.ParagraphStyle(
textDirection: TextDirection.ltr,
);
final paragraphBuilder = ui.ParagraphBuilder(paragraphStyle)
..pushStyle(textStyle)
..addText('${progress * 100} %');
final constraints = ui.ParagraphConstraints(width: size.width);
final paragraph = paragraphBuilder.build();
paragraph.layout(constraints);
canvas.drawParagraph(paragraph, pc);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

Draw custom Arc shape flutter

I'm trying to draw this kind of shape with flutter:
Expected result
So far I can draw an arc using drawArc():
class CurvePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Colors.green;
paint.style = PaintingStyle.fill;
paint.strokeWidth = 5;
final rect = Rect.fromLTRB(50, 100, 130, 200);
final startAngle = -pi;
final sweepAngle = pi;
canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
But is that the right way to do it or should I use quadraticBezierTo and drawPath?

Flutter paintZigZag

Help me in drawing the zigZag line/border as show in the image at bottom. I found a zigzag paint function in flutter doc https://api.flutter.dev/flutter/painting/paintZigZag.html but not sure how to use it.
You need to use CustomPaint() Widget.
Create your own CustomerPainter, say MyPainter, and place this in your widget tree:
CustomPaint(
size: MediaQuery.of(context).size,
painter: MyPainter(),
),
The class MyPainter should extend CustomPainter and also should override the paint() method which is where you specify the path to be painted on the canvas.
So you can just put the above 'paintZigZag' code there or call it like below along with appropriate parameters.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class MyPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Colors.blue;
paint.style = PaintingStyle.fill;
paintZigZag(canvas, paint, Offset(0, 100), Offset(200, 100), 100, 5);
}
void paintZigZag(Canvas canvas, Paint paint, Offset start, Offset end,
int zigs, double width) {
assert(zigs.isFinite);
assert(zigs > 0);
canvas.save();
canvas.translate(start.dx, start.dy);
end = end - start;
canvas.rotate(math.atan2(end.dy, end.dx));
final double length = end.distance;
final double spacing = length / (zigs * 2.0);
final Path path = Path()..moveTo(0.0, 0.0);
for (int index = 0; index < zigs; index += 1) {
final double x = (index * 2.0 + 1.0) * spacing;
final double y = width * ((index % 2.0) * 2.0 - 1.0);
path.lineTo(x, y);
}
path.lineTo(length, 0.0);
canvas.drawPath(path, paint);
canvas.restore();
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

Custom discreet Slider - Problem in aligning the thumb to the division's center

Wanted to create a custom discreet slider as shown below:
.
For that, created 2 custom class. For thumbShape: subclassed from SliderComponentShape and for tickMarkShape: subclassed from SliderTickMarkShape.
In both the class, I was just fiddling around the paint object and radius to create the custom shape for my division and my thumb.
In the SliderComponentShape (thumbShape) subclass, I just changed the radius to 20% of its passed size while drawing the circle on the canvas as below.
canvas.drawCircle(center, (radiusTween.evaluate(enableAnimation) * 0.20), Paint()..color = colorTween.evaluate(enableAnimation))
And in the SliderTickMarkShape (tickMarkShape) subclass, I modified the paint object, and changed its style and strokeWidth like so.
final Paint strokePaint = Paint()
..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)
..strokeWidth = 3.0
..style = PaintingStyle.stroke;
if (tickMarkRadius > 0) {
context.canvas.drawCircle(center, tickMarkRadius, strokePaint);
}
This gives the desired result only when the thumb is placed in the center of the slider.
That is, the small circle (the slider's thumb) fits to the center of the ring like design of the divisions, only when the thumb is in the center of the slider.
If it is dragged to other places it's not properly aligned.
I tried to fiddle with the thumb's center.dx value. but that change works only to one of the divisions and rest gets unaligned.
Any Help on how to fix the thumb to the center of the division in all the values?
EDIT - Code Included:
import 'package:flutter/material.dart';
class DonutSliderThumbShape extends SliderComponentShape {
const DonutSliderThumbShape({
this.enabledThumbRadius = 10.0,
this.disabledThumbRadius,
});
final double enabledThumbRadius;
final double disabledThumbRadius;
double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
#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,
}) {
assert(context != null);
assert(center != null);
assert(enableAnimation != null);
assert(sliderTheme != null);
assert(sliderTheme.disabledThumbColor != null);
assert(sliderTheme.thumbColor != null);
final Canvas canvas = context.canvas;
final Tween<double> radiusTween = Tween<double>(
begin: _disabledThumbRadius,
end: enabledThumbRadius,
);
final ColorTween colorTween = ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.thumbColor,
);
final Paint paint = Paint()
..color = colorTween.evaluate(enableAnimation)
..style = PaintingStyle.fill;
canvas.drawCircle(
center, (radiusTween.evaluate(enableAnimation) * 0.20), paint);
}
}
class DonutSliderTickMarkShape extends SliderTickMarkShape {
const DonutSliderTickMarkShape({this.tickMarkRadius});
final double tickMarkRadius;
#override
Size getPreferredSize({
#required SliderThemeData sliderTheme,
bool isEnabled = false,
}) {
assert(sliderTheme != null);
assert(sliderTheme.trackHeight != null);
assert(isEnabled != null);
return Size.fromRadius(tickMarkRadius ?? sliderTheme.trackHeight / 2);
}
#override
void paint(
PaintingContext context,
Offset center, {
#required RenderBox parentBox,
#required SliderThemeData sliderTheme,
#required Animation<double> enableAnimation,
#required TextDirection textDirection,
#required Offset thumbCenter,
bool isEnabled = false,
}) {
assert(context != null);
assert(center != null);
assert(parentBox != null);
assert(sliderTheme != null);
assert(sliderTheme.disabledActiveTickMarkColor != null);
assert(sliderTheme.disabledInactiveTickMarkColor != null);
assert(sliderTheme.activeTickMarkColor != null);
assert(sliderTheme.inactiveTickMarkColor != null);
assert(enableAnimation != null);
assert(textDirection != null);
assert(thumbCenter != null);
assert(isEnabled != null);
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 double tickMarkRadius = getPreferredSize(
isEnabled: isEnabled,
sliderTheme: sliderTheme,
).width /
2;
final Paint strokePaint = Paint()
..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)
..strokeWidth = 3.0
..style = PaintingStyle.stroke;
if (tickMarkRadius > 0) {
context.canvas.drawCircle(center, tickMarkRadius, strokePaint);
}
}
}

Flutter Improve CustomPainter animation performance

I needed a loading widget that draws the moving sine and cosine functions into a canvas. I coded it with no problem using a CustomPaint widget and a CustomPainter, but when I profile it, i Have discovered it runs on about 49fps, and not on 60fps. The UI thread is working good, taking about 6ms for each frame, but the Raster thread is taking longer. I have tried painting less points on the canvas (doing i=i+5 instead of i++ on the for loop), but the result is quite the same.
¿Can somebody suggest me an idea on how could I improve the performance?. The widget code is below, and so is the DevTools screenshot of what the Raster thread is doing in every frame, in case it can be useful.
import 'dart:math';
import 'package:flutter/material.dart';
class LoadingChart extends StatefulWidget{
final Color color1;
final Color color2;
final double lineWidth;
final bool line;
final Size size;
const LoadingChart({
#required this.color1,
#required this.color2,
#required this.size,
#required this.lineWidth,
this.line = true,
Key key
}): super(key: key);
#override
State<StatefulWidget> createState() => _LoadingChartState();
}
class _LoadingChartState extends State<LoadingChart>
with SingleTickerProviderStateMixin{
AnimationController _controller;
double randomHeight(Random random, double max){
return random.nextDouble()*max;
}
#override
void initState() {
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
_controller.addListener(() {setState(() {});});
_controller.repeat();
super.initState();
}
#override
void dispose(){
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: widget.size.height,
width: widget.size.width,
child: CustomPaint(
painter: PathPainter(
color1: widget.color1,
color2: widget.color2,
value: _controller.value,
line: widget.line,
),
)
);
}
}
class PathPainter extends CustomPainter {
final Color color1;
final Color color2;
final double lineWidth;
final bool line;
final double value;
PathPainter({
#required this.value,
this.color1=Colors.red,
this.color2=Colors.green,
this.line = true,
this.lineWidth=4.0,
}): super();
#override
void paint(Canvas canvas, Size size) {
final height = size.height;
final width = size.width;
Paint paint1 = Paint()
..color = color1
..style = PaintingStyle.stroke
..strokeWidth = lineWidth;
Paint paint2 = Paint()
..color = color2
..style = PaintingStyle.stroke
..strokeWidth = lineWidth;
Path path1 = Path();
Path path2 = Path();
/* If line is true, draw sin and cos functions, otherwise, just some points */
for (double i = 0; i < width; i=i+5){
double f = i*2*pi/width + 2*pi*value;
double g = i*2*pi/width - 2*pi*value;
if (i == 0){
path1.moveTo(0, height/2 + height/6*sin(f));
path2.moveTo(0, height/2 + height/6*cos(g));
continue;
}
path1.lineTo(i, height/2 + height/6*sin(f));
path2.lineTo(i, height/2 + height/6*cos(g));
}
/* Draw both lines */
canvas.drawPath(path1, paint1);
canvas.drawPath(path2, paint2);
}
#override
bool shouldRepaint(PathPainter oldDelegate) {
return oldDelegate.value != value || oldDelegate.color1 != color1
|| oldDelegate.color2 != color2 || oldDelegate.line != line
|| oldDelegate.lineWidth != lineWidth;
}
}
PS: I'm running the app on profile mode so that shouldn't be the problem. Also I wanted to mention that it's the only widget being redrawn on the screen.
Thanks a lot!!
CustomPainter can receive a listenable so maybe you can use the animation controller there to update it with every tick
class _LoadingChartState extends State<LoadingChart>
with SingleTickerProviderStateMixin{
AnimationController _controller;
double randomHeight(Random random, double max){
return random.nextDouble()*max;
}
#override
void initState() {
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
//_controller.addListener(() {setState(() {});}); no need to setState
_controller.repeat();
super.initState();
}
#override
void dispose(){
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: widget.size.height,
width: widget.size.width,
child: CustomPaint(
willChange: true, //this can help (Whether the raster cache should be told that this painting is likely)
painter: PathPainter(
color1: widget.color1,
color2: widget.color2,
line: widget.line,
listenable: _controller //pass the controller as it is (An animationController extends a Listenable)
),
)
);
}
}
And in PathPainter you give to the constructor the listenable and pass it to the CustomPainter constructor that accepts a listenable called repaint
class PathPainter extends CustomPainter {
final Animation listenable;
final Color color1;
final Color color2;
final double lineWidth;
final bool line;
PathPainter({
this.listenable,
this.color1=Colors.red,
this.color2=Colors.green,
this.line = true,
this.lineWidth=4.0,
}): super(repaint: listenable); //don't forget calling the CustomPainter constructor with super
#override
void paint(Canvas canvas, Size size) {
double value = listenable.value; // get its value here
final height = size.height;
final width = size.width;
Paint paint1 = Paint()
..color = color1
..style = PaintingStyle.stroke
..strokeWidth = lineWidth;
Paint paint2 = Paint()
..color = color2
..style = PaintingStyle.stroke
..strokeWidth = lineWidth;
Path path1 = Path();
Path path2 = Path();
/* If line is true, draw sin and cos functions, otherwise, just some points */
for (double i = 0; i < width; i=i+5){
double f = i*2*pi/width + 2*pi*value;
double g = i*2*pi/width - 2*pi*value;
if (i == 0){
path1.moveTo(0, height/2 + height/6*sin(f));
path2.moveTo(0, height/2 + height/6*cos(g));
continue;
}
path1.lineTo(i, height/2 + height/6*sin(f));
path2.lineTo(i, height/2 + height/6*cos(g));
}
/* Draw both lines */
canvas.drawPath(path1, paint1);
canvas.drawPath(path2, paint2);
}
#override
bool shouldRepaint(PathPainter oldDelegate) {
//delete the oldDelegate.value, it doesn't exists anymore
return oldDelegate.color1 != color1
|| oldDelegate.color2 != color2 || oldDelegate.line != line
|| oldDelegate.lineWidth != lineWidth;
}
}
I'm in debug mode so I expect you get a better performance in profile mode