My code as of now:
class RoundedRectanglePainter extends CustomPainter {
final Color strokeColorGradientStart;
final Color strokeColorGradientEnd;
final double strokeWidth;
final double borderRadius;
final Color fillColorGradientStart;
final Color fillColorGradientEnd;
final Animation<double> animation;
RoundedRectanglePainter({
required this.strokeColorGradientStart,
required this.strokeColorGradientEnd,
required this.strokeWidth,
required this.borderRadius,
required this.fillColorGradientStart,
required this.fillColorGradientEnd,
required this.animation,
}) : super(repaint: animation);
@override
void paint(Canvas canvas, Size size) {
final strokeGradient = LinearGradient(
colors: [strokeColorGradientStart, strokeColorGradientEnd],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
final fillGradient = LinearGradient(
colors: [fillColorGradientStart, fillColorGradientEnd],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
final strokePaint = Paint()
..shader = strokeGradient
.createShader(Rect.fromLTWH(0, 0, size.width, size.height))
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
final fillPaint = Paint()
..shader = fillGradient
.createShader(Rect.fromLTWH(0, 0, size.width, size.height))
..style = PaintingStyle.fill;
final outerRect = Rect.fromLTWH(0, 0, size.width, size.height);
final outerRRect =
RRect.fromRectAndRadius(outerRect, Radius.circular(borderRadius));
canvas.drawRRect(outerRRect, strokePaint);
final innerWidth = size.width - (strokeWidth * 2) - 10;
final innerHeight = size.height - (strokeWidth * 2) - 10;
final innerRect = Rect.fromLTWH((size.width - innerWidth) / 2,
(size.height - innerHeight) / 2, innerWidth, innerHeight);
final innerRRect =
RRect.fromRectAndRadius(innerRect, Radius.circular(borderRadius));
canvas.drawRRect(innerRRect, fillPaint);
Path outerPath = Path()..addRRect(outerRRect);
PathMetrics pathMetrics = outerPath.computeMetrics();
PathMetric pathMetric = pathMetrics.first;
double currentLength = pathMetric.length * animation.value;
Tangent? tangent = pathMetric.getTangentForOffset(currentLength);
if (tangent != null) {
final movingLinePaint = Paint()
..color = Colors.red
..strokeWidth = 10.0
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
final lineStart = tangent.position;
final lineDirection = tangent.vector;
final lineEnd = lineStart +
Offset(
lineDirection.dx * 30,
lineDirection.dy * 30,
);
canvas.drawLine(lineStart, lineEnd, movingLinePaint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
i want that the line moving should follow the exact path as the outer rectangle is defined. i tried and tested different thing but not able to get the desired behaviour. how can i proceed further to achieve the desired functionality or behaviour or some relevant code snippet which can help me progress in the code.
in the image you can see when it reaches the curved path the line moving is not following the curveness, it simply takes a turn to right. this is the problem.
instead of pathMetric.getTangentForOffset
use pathMetric.extractPath
, notice that it has to be called twice when trying to get a sub-path at the end of the given path
the complete code (note: timeDilation = 5;
is used just for testing, your are free to delete it):
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(MaterialApp(home: Foo()));
class Foo extends StatefulWidget {
@override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> with SingleTickerProviderStateMixin {
late final _controller = AnimationController(vsync: this, duration: Durations.extralong4);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.black,
padding: const EdgeInsets.all(4),
child: CustomPaint(
painter: RoundedRectanglePainter(
strokeColors: [Colors.green.shade900, Colors.green.shade700, Colors.blue.shade900, Colors.grey],
strokeWidth: 12,
borderRadius: 32,
fillColors: [Colors.pink.shade700, Colors.grey.shade600, Colors.indigo.shade900],
padding: 6,
animation: _controller,
),
child: Center(
child: ElevatedButton(onPressed: () => _controller.forward(from: 0), child: const Text('animate')),
),
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class RoundedRectanglePainter extends CustomPainter {
final double strokeWidth;
final double borderRadius;
final double padding;
final Animation<double> animation;
final Gradient strokeGradient;
final Gradient fillGradient;
RoundedRectanglePainter({
required List<Color> strokeColors,
required this.strokeWidth,
required this.borderRadius,
required List<Color> fillColors,
this.padding = 10,
required this.animation,
}) :
strokeGradient = LinearGradient(
colors: strokeColors,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
fillGradient = LinearGradient(
colors: fillColors,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
super(repaint: animation);
@override
void paint(Canvas canvas, Size size) {
// slow motion - just for testing
timeDilation = 5;
// rects
final outerRect = (Offset.zero & size).deflate(strokeWidth / 2);
final innerRect = outerRect.deflate(padding + strokeWidth / 2);
// paints
final strokePaint = Paint()
..shader = strokeGradient.createShader(outerRect)
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
final fillPaint = Paint()
..shader = fillGradient.createShader(innerRect)
..style = PaintingStyle.fill;
final movingLinePaint = Paint()
..color = Colors.white
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
// round rects
final outerRRect = RRect.fromRectAndRadius(outerRect, Radius.circular(borderRadius));
final innerRRect = RRect.fromRectAndRadius(innerRect, Radius.circular(borderRadius - padding - strokeWidth / 2));
// moving sub-path
final outerPath = Path()..addRRect(outerRRect);
final pathMetric = outerPath.computeMetrics().first;
final start = pathMetric.length * animation.value;
final end = start + borderRadius * pi * 0.5;
final path = pathMetric.extractPath(start, end);
if (end > pathMetric.length) {
path.addPath(pathMetric.extractPath(0, end - pathMetric.length), Offset.zero);
}
// draw everything
canvas
..drawRRect(outerRRect, strokePaint)
..drawRRect(innerRRect, fillPaint)
..drawPath(path, movingLinePaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}