Created
December 18, 2024 01:32
-
-
Save gabbygreat/735b5b91a91938fd4a98b05cc302b4c7 to your computer and use it in GitHub Desktop.
Spin Effect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:demo/spinwheel_widget.dart'; | |
| import 'package:flutter/material.dart'; | |
| import 'package:flutter_web_plugins/url_strategy.dart'; | |
| import 'package:go_router/go_router.dart'; | |
| void main() { | |
| usePathUrlStrategy(); | |
| GoRouter.optionURLReflectsImperativeAPIs = true; | |
| runApp(const MyApp()); | |
| } | |
| // Define GoRouter routes. | |
| class MyApp extends StatelessWidget { | |
| const MyApp({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return const SettingsPage(); | |
| } | |
| } | |
| class SettingsPage extends StatefulWidget { | |
| static const name = 'settings'; | |
| static const path = '/settings'; | |
| const SettingsPage({super.key}); | |
| @override | |
| State<SettingsPage> createState() => _SettingsPageState(); | |
| } | |
| class _SettingsPageState extends State<SettingsPage> { | |
| List<String> data = []; | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| home: Scaffold( | |
| appBar: AppBar( | |
| title: const Text('Settings'), | |
| ), | |
| body: Center( | |
| child: SpinWheelWidget( | |
| preselectedIndex: 4, | |
| onSpinEnd: (p0) { | |
| print(p0); | |
| }, | |
| items: [ | |
| SpinWheelItem( | |
| color: Colors.purple, | |
| label: 'Hello 0', | |
| ), | |
| SpinWheelItem( | |
| color: Colors.blue, | |
| label: 'Hello 1', | |
| ), | |
| SpinWheelItem( | |
| color: Colors.green, | |
| label: 'Hello 2', | |
| ), | |
| SpinWheelItem( | |
| color: Colors.brown, | |
| label: 'Hello 3', | |
| ), | |
| SpinWheelItem( | |
| color: Colors.yellow, | |
| label: 'Hello 4', | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:math' as math; | |
| import 'package:flutter/material.dart'; | |
| extension AngleConversion on num { | |
| /// Converts degrees to radians | |
| double toRadians() => this * (math.pi / 180.0); | |
| } | |
| class SpinWheelWidget extends StatefulWidget { | |
| final List<SpinWheelItem> items; | |
| final Function(SpinWheelItem) onSpinEnd; | |
| final int preselectedIndex; | |
| final double wheelSize; | |
| // final SpinWheelStateProvider spinWheelState; | |
| const SpinWheelWidget({ | |
| super.key, | |
| required this.items, | |
| required this.onSpinEnd, | |
| required this.preselectedIndex, | |
| this.wheelSize = 300, | |
| // required this.spinWheelState, | |
| }) : assert(preselectedIndex < items.length, | |
| 'Preselected index cannot be higher than or equal to the length of items'); | |
| @override | |
| State<SpinWheelWidget> createState() => _SpinWheelWidgetState(); | |
| } | |
| class _SpinWheelWidgetState extends State<SpinWheelWidget> | |
| with SingleTickerProviderStateMixin { | |
| late AnimationController _controller; | |
| late Animation<double> _animation = const AlwaysStoppedAnimation(0.0); | |
| bool isSpinning = false; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _controller = AnimationController( | |
| vsync: this, | |
| duration: const Duration(seconds: 5), | |
| ); | |
| _controller.addStatusListener((status) { | |
| if (status == AnimationStatus.completed) { | |
| setState(() { | |
| isSpinning = false; | |
| }); | |
| final selectedItem = widget.items[widget.preselectedIndex]; | |
| widget.onSpinEnd(selectedItem); | |
| } | |
| }); | |
| } | |
| double get getRandomValue { | |
| int n = widget.items.length; | |
| int desiredValue = widget.preselectedIndex; | |
| if (desiredValue < 0 || desiredValue >= n) { | |
| throw ArgumentError('desiredValue must be between 0 and n-1.'); | |
| } | |
| final random = math.Random(); | |
| final segment = 360 / n; | |
| // Calculate the start and end of the range for the given desired value | |
| final start = (n - desiredValue - 1) * segment + 1; | |
| final end = (n - desiredValue - 1) * segment + segment - 1; | |
| // Generate a random value (decimal) within the range | |
| return start + random.nextDouble() * (end - start); | |
| } | |
| Future<void> startSpin() async { | |
| int speed = math.Random().nextInt(5) + 5; // creates effect of a longer spin | |
| _animation = Tween<double>( | |
| begin: 0, | |
| end: ((getRandomValue + 360 * speed)).toRadians(), | |
| ).animate(CurvedAnimation( | |
| parent: _controller, | |
| curve: Curves.easeOutCubic, | |
| )); | |
| setState(() { | |
| isSpinning = true; | |
| }); | |
| _controller.reset(); | |
| _controller.forward(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return GestureDetector( | |
| onTap: isSpinning ? null : startSpin, | |
| child: Stack( | |
| alignment: Alignment.center, | |
| children: [ | |
| AnimatedBuilder( | |
| animation: _controller, | |
| builder: (context, child) { | |
| return Transform.rotate( | |
| angle: _animation.value, | |
| child: Container( | |
| width: widget.wheelSize, | |
| height: widget.wheelSize, | |
| decoration: BoxDecoration( | |
| shape: BoxShape.circle, | |
| border: Border.all(color: Colors.black, width: 2), | |
| ), | |
| child: CustomPaint( | |
| painter: SpinWheelPainter( | |
| items: widget.items, | |
| ), | |
| ), | |
| ), | |
| ); | |
| }, | |
| ), | |
| const Positioned( | |
| top: -45, | |
| child: Icon( | |
| Icons.arrow_drop_down, | |
| size: 100, | |
| color: Colors.red, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| @override | |
| void dispose() { | |
| _controller.dispose(); | |
| super.dispose(); | |
| } | |
| } | |
| class SpinWheelPainter extends CustomPainter { | |
| final List<SpinWheelItem> items; | |
| SpinWheelPainter({required this.items}); | |
| @override | |
| void paint(Canvas canvas, Size size) { | |
| final center = Offset(size.width / 2, size.height / 2); | |
| final radius = size.width / 2; | |
| final segmentAngle = 2 * math.pi / items.length; | |
| final sections = 360 / items.length; // degrees | |
| double startAngle = 270.0; | |
| for (var i = 0; i < items.length; i++) { | |
| final paint = Paint()..color = items[i].color; | |
| canvas.drawArc( | |
| Rect.fromCircle(center: center, radius: radius), | |
| startAngle.toRadians(), | |
| segmentAngle, | |
| true, | |
| paint, | |
| ); | |
| final textAngle = startAngle.toRadians() + (segmentAngle / 2); | |
| final textRadius = radius * 0.7; | |
| startAngle += sections; | |
| TextPainter textPainter = TextPainter( | |
| text: TextSpan( | |
| text: '${items[i].label} {SEG $i}', | |
| style: const TextStyle( | |
| color: Colors.white, | |
| fontSize: 14, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| textDirection: TextDirection.ltr, | |
| ); | |
| textPainter.layout(); | |
| canvas.save(); | |
| canvas.translate( | |
| center.dx + textRadius * math.cos(textAngle), | |
| center.dy + textRadius * math.sin(textAngle), | |
| ); | |
| canvas.rotate(textAngle + math.pi / 2); | |
| textPainter.paint( | |
| canvas, | |
| Offset(-textPainter.width / 2, -textPainter.height / 2), | |
| ); | |
| canvas.restore(); | |
| } | |
| } | |
| @override | |
| bool shouldRepaint(covariant CustomPainter oldDelegate) => true; | |
| } | |
| class SpinWheelItem { | |
| final String label; | |
| final Color color; | |
| final dynamic data; | |
| SpinWheelItem({ | |
| required this.label, | |
| required this.color, | |
| this.data, | |
| }); | |
| @override | |
| String toString() { | |
| return """ | |
| SpinWheelItem( | |
| label: $label, | |
| color: $color, | |
| data: $data, | |
| )"""; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment