wheel_choice 1.2.0 copy "wheel_choice: ^1.2.0" to clipboard
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.

example/lib/main.dart

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,
            ),
          ],
        ),
      ),
    );
  }
}
6
likes
160
points
17
downloads

Publisher

verified publisherwidgetarian.com

Weekly Downloads

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.

Homepage
Repository (GitHub)
View/report issues

Topics

#wheel #scroll #choice #picker

Documentation

API reference

Funding

Consider supporting this project:

buymeacoffee.com
ko-fi.com

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on wheel_choice