wheel_choice 1.2.0
wheel_choice: ^1.2.0 copied to clipboard
Wheel-shaped picker widget for Flutter. Scroll and select items with customizable header, overlays, magnifier and 3D effects, disabled items, looping, and custom item builders.
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:wheel_choice/wheel_choice.dart';
void main() => runApp(const WheelChoiceExampleApp());
class WheelChoiceExampleApp extends StatelessWidget {
const WheelChoiceExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'wheel_choice example',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
PointerDeviceKind.stylus,
PointerDeviceKind.trackpad,
},
),
home: const ExampleHomePage(),
);
}
}
class ExampleHomePage extends StatefulWidget {
const ExampleHomePage({super.key});
@override
State<ExampleHomePage> createState() => _ExampleHomePageState();
}
class _ExampleHomePageState extends State<ExampleHomePage> {
static const _days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
static const _months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
static final _hours = List<int>.generate(12, (i) => i + 1);
static final _minutes = List<int>.generate(60, (i) => i);
static const _periods = ['AM', 'PM'];
static const _fruits = [
'Apple',
'Banana',
'Cat',
'Cherry',
'Dog',
'Durian',
'Grape',
'Jackfruit',
'Kiwi',
'Mango',
'Papaya',
'Rambutan',
];
String _day = 'Wed';
String _month = 'Jan';
int _hour = 10;
int _minute = 30;
String _period = 'AM';
String _fruit = 'Durian';
final _monthFocus = FocusNode();
late final _hourController = WheelController<int>(
options: _hours,
value: _hour,
onChanged: (v) => setState(() => _hour = v),
animationDuration: const Duration(milliseconds: 300),
animationCurve: Curves.easeOutCubic,
);
late final _minuteController = WheelController<int>(
options: _minutes,
value: _minute,
onChanged: (v) => setState(() => _minute = v),
animationDuration: const Duration(milliseconds: 300),
animationCurve: Curves.easeOutCubic,
);
late final _periodController = WheelController<String>(
options: _periods,
value: _period,
onChanged: (v) => setState(() => _period = v),
animationDuration: const Duration(milliseconds: 300),
animationCurve: Curves.easeOutCubic,
);
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_monthFocus.requestFocus();
});
}
@override
void dispose() {
_monthFocus.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final section1 = _Section(
title: 'Basic',
subtitle: 'Pick month and day',
children: [
Row(
spacing: 12,
children: [
Expanded(
child: WheelChoice<String>(
options: _months,
value: _month,
onChanged: (v) => setState(() => _month = v),
itemVisible: 5,
overlay: WheelOverlay.outlined(inset: 12),
effect: const WheelEffect(
useMagnifier: true,
magnification: 1.05,
),
header: const WheelHeader(child: Text('Month')),
focusNode: _monthFocus,
keyboard: true,
haptics: true,
loop: true,
),
),
Expanded(
child: WheelChoice<String>(
options: _days,
value: _day,
onChanged: (v) => setState(() => _day = v),
itemBuilder: WheelItem.delegate(
padding: const EdgeInsets.symmetric(horizontal: 16),
selectedStyle: const TextStyle(fontSize: 18),
),
itemVisible: 5,
overlay: WheelOverlay.outlined(inset: 12),
effect: const WheelEffect(
useMagnifier: true,
magnification: 1.05,
),
header: const WheelHeader(child: Text('Day')),
),
),
],
),
Text('Selected: $_month $_day'),
],
);
final section2 = _Section(
title: 'Time picker',
subtitle: 'Pick hour, minute, and AM/PM',
children: [
SizedBox(
height: 300,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: WheelChoice<int>.raw(
controller: _hourController,
itemLabel: (v) => v.toString().padLeft(2, '0'),
overlay: WheelOverlay.outlined(inset: 0),
effect: WheelEffect.flat(),
header: const WheelHeader(child: Text('Hour')),
expanded: true,
// loop: true,
),
),
Expanded(
child: WheelChoice<int>.raw(
controller: _minuteController,
itemLabel: (v) => v.toString().padLeft(2, '0'),
overlay: WheelOverlay.outlined(inset: 0),
effect: WheelEffect.flat(),
header: const WheelHeader(child: Text('Minute')),
expanded: true,
// loop: true,
),
),
Expanded(
child: WheelChoice<String>.raw(
controller: _periodController,
overlay: WheelOverlay.outlined(inset: 0),
effect: WheelEffect.flat(),
header: const WheelHeader(child: Text('AM/PM')),
expanded: true,
),
),
],
),
),
Text(
'Selected: ${_hour.toString().padLeft(2, '0')}:${_minute.toString().padLeft(2, '0')} $_period',
),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: () async {
await Future.wait([
_hourController.animateToValue(7),
_minuteController.animateToValue(45),
_periodController.animateToValue('PM'),
]);
},
child: const Text('Animate to 07:45 PM'),
),
ElevatedButton(
onPressed: () {
_hourController.jumpToValue(2);
_minuteController.jumpToValue(11);
_periodController.jumpToValue('AM');
},
child: const Text('Jump to 02:11 AM'),
),
],
),
],
);
final section3 = _Section(
title: 'Custom layout',
subtitle: 'Custom item builder wheel',
children: [
SizedBox(
height: 180,
child: WheelChoice<String>(
options: _fruits,
value: _fruit,
onChanged: (v) => setState(() => _fruit = v),
itemDisabled: (v) => ['Cat', 'Dog'].contains(v),
itemBuilder: (context, item) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(
Icons.circle,
size: 10,
color: item.selected
? theme.colorScheme.primary
: item.disabled
? theme.disabledColor
: theme.unselectedWidgetColor,
),
const SizedBox(width: 8),
Text(
item.label,
style: TextStyle(
color: item.disabled ? theme.disabledColor : null,
fontWeight: item.selected ? FontWeight.bold : null,
),
),
],
),
if (item.selected) const Icon(Icons.check_circle, size: 18),
],
),
);
},
itemVisible: 5,
overlay: WheelOverlay.outlined(inset: 8),
effect: const WheelEffect(diameterRatio: 2.2, perspective: 0.003),
expanded: true,
),
),
Text('Selected: $_fruit'),
],
);
return Scaffold(
appBar: AppBar(title: const Text('wheel_choice example')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(spacing: 16, children: [section1, section2, section3]),
),
);
}
}
class _Section extends StatelessWidget {
const _Section({
required this.title,
required this.subtitle,
required this.children,
});
final String title;
final String subtitle;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
spacing: 12,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text(title),
subtitle: Text(subtitle),
contentPadding: EdgeInsets.zero,
minVerticalPadding: 0,
minTileHeight: 0,
),
Column(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
),
],
),
),
);
}
}