how can I rotate the white ball inside the circle after the animation has drawn? Now in order for the first animation to work, I need to pass the tween to the animation, but for the rotation to work, I need to attach a listener to the controller. How to combine these two options? I need the first gradient animation to fire first and then be able to rotate the ball inside the circle. I understand that this requires a gesture detector which I use, but how to properly combine both animations
class _Content extends StatefulWidget {
@override
_ContentState createState() => _ContentState();
}
class _ContentState extends State<_Content> with TickerProviderStateMixin {
late AnimationController _controller;
late AnimationController _rotateController;
late Animation<double> _animation;
late final Animation<double> _fadeAnimation;
final Duration duration = const Duration(seconds: 5);
double _progress = 0.0;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: duration,
)..forward();
_rotateController = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..addListener(() {
setState(() {
_progress = _controller.value;
});
});
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
),
);
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
}
void _onPanUpdate(DragUpdateDetails details, Size size) {
final Offset center = Offset(
size.height / 2,
size.width / 2,
);
final Offset position = details.localPosition - center;
final double angle = math.atan2(position.dy, position.dx);
final double progress = (angle + math.pi / 2) / (2 * math.pi);
setState(() {
_rotateController.value = progress;
if (_progress >= 1.0) {
_rotateController.forward(from: 0.0);
}
});
}
DateTime _calculateCurrentTime(DateTime startTime, DateTime endTime) {
final Duration totalDuration = endTime.difference(startTime);
return startTime.add(totalDuration);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Padding(
padding: const Pad(right: Sizes.s20, top: Sizes.s26),
child: Align(
alignment: Alignment.topRight,
child: IconButton(
onPressed: () {
},
),
),
),
GestureDetector(
onPanUpdate: (DragUpdateDetails details) => _onPanUpdate(details, Size(340, 340)),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget? child) {
return SizedBox(
width: 340,
height: 340,
child: !state.isAlarmToggle ? CustomPaint(
painter: TimerPainter(
progress: _animation.value,
context: context,
startTime: DateTime.now(),
progressBallRotation: _controller.value,
),
child: Center(
child: AnimatedBuilder(
animation: _fadeAnimation,
builder: (BuildContext context, Widget? child) {
return Text(
DateTime.now().toString,
style: TextStyle(fontSize: 64, color: context.colors.white),
);
},
),
),
) : CustomPaint(painter: TimerPainterDisabled(context: context, ) ,)
);
},
),
Also this is my custom painter class for better understanding the isue. For ball rotation I can use position of the ball and using offset6 but since i need to make ball starting position according to the Datetime.now() I ca’t get it how to rotate ball properly
class TimerPainter extends CustomPainter {
TimerPainter({
required this.progress,
required this.context,
required this.startTime,
required this.progressBallRotation,
});
final double progress;
final double progressBallRotation;
final BuildContext context;
final DateTime startTime;
@override
void paint(Canvas canvas, Size size) {
const double strokeWidth = 48;
final double radius = (size.shortestSide - strokeWidth) / 2;
final double startHour = startTime.hour.toDouble() % 12;
final double startAngle = (startHour / 12) * 2 * pi - pi / 2;
//базовый серый круг
final Paint outerCircle = Paint()
..strokeWidth = strokeWidth
..color = context.colors.white
..style = PaintingStyle.stroke;
final double currentAngle = startAngle + 2 * pi * progress;
final Paint gradientCircle = Paint()
..strokeWidth = strokeWidth
..shader = SweepGradient(
tileMode: TileMode.repeated,
transform: GradientRotation(currentAngle),
colors: [
context.colors.tachadjudDarkColor,
context.colors.tachadjudDarkColor,
context.colors.tachadjudLightNavyLighterColor,
context.colors.tachadjudLightNavyDarkenColor,
context.colors.tachadjudLightNavyColor,
],
).createShader(
Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: radius),
)
..style = PaintingStyle.stroke;
canvas.drawArc(
Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: radius),
startAngle,
2 * pi * progress,
false,
gradientCircle,
);
final Paint spotPaint = Paint()
..color = Colors.white
..strokeWidth = 2.0
..style = PaintingStyle.fill;
final double spotX = size.width / 2 + radius * cos(currentAngle);
final double spotY = size.height / 2 + radius * sin(currentAngle);
canvas.drawCircle(Offset(spotX, spotY), strokeWidth / 2, spotPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}