🌀 Spinning Wheel Package
A customizable Flutter package for creating visually appealing and interactive spinning wheels — perfect for games, quizzes, random selection tools, and more.
This package supports rich styling, section-specific colors, gradient customization, sound effects, and custom pointer images.
✨ Features
- 🎨 Customizable spinning wheel
- 🌈 Gradient color support per section
- 🔊 Sound playback on spin
- 🪞 Custom pointer image
- ⚙️ Silver and Wooden rim styles
- 🧩 Spin start and result callbacks
🚀 Getting Started
Add the package to your `pubspec.yaml`:
Then, import it:
import 'package:spinning_wheel_package/spinning_wheel_package.dart';
📦 Assets Setup
flutter: assets:
- assets/audios/spin.mp3
- assets/images/pointer.png
🎨 Customization Properties
| Property | Type | Description |
|---|---|---|
sections |
List<WheelItem> |
✅ Required. The list of items to display on the wheel. |
spinDuration |
double |
The duration of the spin animation in seconds. |
onSpinStart |
Function()? |
Callback when the wheel starts spinning — great for playing sound effects. |
onSpinEnd |
Function(int)? |
Callback when the wheel stops, returning the index of the winning slice. |
wheelWidth |
double |
The width of the spinning wheel. |
wheelHeight |
double |
The height of the spinning wheel. |
SpinningWheelController |
SpinningWheelController |
Programmatically control the wheel from outside the widget. |
spinTriggerBuilder |
Widget Function(void Function() trigger)? |
Provide a flexible way to create a custom spin trigger widget. |
rimType |
WheelRimType |
Sets the wheel's rim style: normal, christmas, silver, etc. |
wheelRimColor |
Color |
The color of the rim when rimType is normal. |
wheelRimWidth |
double |
The width (thickness) of the wheel's outer rim. |
customRimWidth |
double |
The width of the image overlay rim (e.g., for silver or wood). |
customRimHeight |
double |
The height of the image overlay rim. |
pegColor |
Color |
The color of the pegs between each wheel section. |
pegSize |
double |
The radius of the pegs. |
pegPosition |
PegPosition |
Sets the position of the pegs: start or center of each slice. |
showChristmasCap |
bool |
If true, shows a Santa cap when rimType is christmas. |
positionChristmasCapVertical |
double |
The vertical position of the Christmas cap overlay. |
positionChristmasCapHorizontal |
double |
The horizontal position of the Christmas cap overlay. |
textOrientation |
WheelTextOrientation |
Sets text direction: horizontal or vertical inside each section. |
itemTextColor |
Color |
The color of the text inside each wheel section. |
itemFontSize |
double |
The font size for the text in each section. |
itemFontWeight |
FontWeight |
The font weight for the text in each section. |
itemFontFamily |
String |
The font family for the text in each section. |
textMargin |
double |
A multiplier for how far from the center the text is placed (e.g., 0.5). |
useCustomPointer |
bool |
If true, enables using a custom image for the pointer. |
pointerImagePath |
String? |
Path to the pointer image asset. Required if useCustomPointer is true. |
pointerType |
PointerType |
Type of default pointer shape: triangle or cone. |
pointerColor |
Color |
The color of the default painted pointer. |
pointerPinColor |
Color |
The color of the small pin on the cone pointer. |
positionCustomPointerVertical |
double |
The vertical position of the custom image pointer. |
positionCustomPointerHorizontal |
double |
The horizontal position of the custom image pointer. |
positionDefaultPointerVertical |
double |
The vertical position of the default painted pointer. |
positionDefaultPointerHorizontal |
double |
The horizontal position of the default painted pointer. |
centerHubWidth |
double |
The width of the central hub of the wheel. |
centerHubHeight |
double |
The height of the central hub of the wheel. |
centerHubColor |
Color |
The background color of the central hub. |
centerHubEdgeColor |
Color |
The border color of the central hub. |
centerHubText |
String |
The text to display inside the central hub. |
centerHubTextSize |
double |
The font size for the hub text. |
centerHubTextColor |
Color |
The color for the hub text. |
showWheelStand |
bool |
If true, displays the stand below the wheel. |
standColor |
Color |
The color of the wheel stand. |
standOffset |
double |
Controls the vertical alignment of the stand. |
showWheelShadow |
bool |
If true, displays a shadow effect around the wheel rim. |
wheelShadowColor |
Color |
The color of the wheel's outer shadow. |
🛠️ Usage Example
import 'package:flutter/material.dart';
import 'package:spinning_wheel_package/spinning_wheel_package.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Spin Package Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Wheel Spin Package'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
//final AudioPlayer _audioPlayer = AudioPlayer();
//bool _isSoundLoading = false;
String _spinResult = "Spin the wheel!";
final SpinningWheelController _wheelController = SpinningWheelController();
final List<WheelItem> _sections = [
WheelItem(text: "Music", color: Colors.green,),
WheelItem(text: "Art & Books", color: Colors.orange,),
WheelItem(text: "Other Sports", color: Colors.purple),
WheelItem(text: "Nutrition", color: Colors.blue),
WheelItem(text: "Entertainment", color: Colors.red),
WheelItem(text: "Current Affairs", color: Colors.yellow),
];
@override
void dispose() {
//_audioPlayer.dispose();
super.dispose();
}
//You can define a method to play sound when the wheel is spun
// Future<void> _playSound() async {
// // If a sound is already being prepared, do nothing
// if (_isSoundLoading) return;
// // Set the flag to block other calls
// _isSoundLoading = true;
//
// try {
// // setAsset implicitly stops any previous sound
// await _audioPlayer.setAsset('assets/audios/spin.mp3');
// // Rewind to the beginning to ensure it plays every time
// await _audioPlayer.seek(Duration.zero);
// _audioPlayer.play();
// } catch (e) {
// print("Error playing sound: $e");
// } finally {
// // Always release the flag when done
// _isSoundLoading = false;
// }
// }
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
_spinResult,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SpinningWheel(
sections: _sections,
controller: _wheelController,
// spinTriggerBuilder: (trigger) {
// return ElevatedButton(
// onPressed: () {
// // You can add your own logic here before spinning.
// print("Custom button tapped!");
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(content: Text('Spinning with the custom button!')),
// );
// // Call the provided trigger function to start the spin.
// trigger();
// },
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.teal,
// foregroundColor: Colors.white,
// padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
// textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
// ),
// child: const Text('Spin with Builder!'),
// );
// },
onSpinStart: (){
//_playSound();
},
onSpinEnd: (index) {
setState(() {
_spinResult = "Result: ${_sections[index].text}";
});
},
rimType: WheelRimType.silver,
standOffset: 0,
centerHubWidth: 50,
centerHubHeight: 50,
centerHubText: 'SPIN',
useCustomPointer: true,
pointerImagePath: 'assets/images/pointer.png',
showWheelStand: true,
pegPosition: PegPosition.center,
),
const SizedBox(height: 30),
// This button uses the controller to spin the wheel
ElevatedButton(
onPressed: () {
print("Controller button tapped!");
_wheelController.spin();
},
child: const Text('Spin with Controller'),
),
],
),
),
);
}
}