Skip to main content

awesome_scroll_actions 2.1.0 copy "awesome_scroll_actions: ^2.1.0" to clipboard
awesome_scroll_actions: ^2.1.0 copied to clipboard

Awesome Scroll Actions dial. An arc of action pills you spin with a vertical drag, tuck away with a side pull, and select with a tap.

awesome_scroll_actions #

The Awesome Scroll Actions dial as a Flutter package.

Action pills ride along a large circle whose centre sits off the left edge of the screen. The centred pill turns dark and expands to show its label, a stat card above swaps to that action's figures, and a toast confirms the choice.

Try the live demo — the example app built for web.

The dial at rest The dark palette showGuideLine enabled
At rest AwesomeScrollActionsTheme.dark showGuideLine: true
The horizontal arc

QuickActionsArc.horizontal — the dial mirrored onto the bottom edge.

Gesture Result
Vertical drag Spin the dial; it snaps to the nearest action on release
Horizontal drag Tuck the dial off the right edge, or pull it back
Tap a pill Jump to that action (or open the dial if tucked)
Tap the edge handle Open a tucked dial
Mouse wheel / trackpad Step one action at a time

Needs Flutter 3.38 / Dart 3.10 or newer. Pure Dart: no platform channels, so it runs on Android, iOS, web and desktop.

Install #

flutter pub add awesome_scroll_actions
dependencies:
  awesome_scroll_actions: ^2.1.0

Use #

Scaffold(
  body: AwesomeScrollActions(
    actions: AwesomeScrollDefaults.actions,
    initialActionId: 'scan_pay',
    onActionTapped: (action, index) {
      Navigator.pushNamed(context, '/${action.id}');
    },
  ),
)

onActionTapped fires only when a pill is tapped, so spinning just highlights. onActionSelected fires on every settle, spins included; use it to track the highlighted action, not to navigate.

The widget fills its parent and handles safe-area insets itself, so give it the whole screen.

Your own actions #

const actions = [
  QuickAction(
    id: 'send',
    label: 'Send Money',
    icon: AwesomeScrollIcons.arrowUp,
    stat: QuickActionStat(label: 'Sent this month', value: 'TZS 412,500', caption: '12 transfers'),
  ),
  QuickAction(id: 'loans', label: 'Loans', icon: Icons.savings_outlined),
];

stat is optional; actions without one hide the card. Any IconData works. AwesomeScrollIcons holds the Iconsax glyphs from the design: arrowUp, arrowDown, flash, mobile, scan, moneyChange, barGraph, bankCard, addCircle and add.

Controller #

final controller = QuickActionsController();

AwesomeScrollActions(actions: actions, controller: controller);

controller.selectById('cards'); // opens if tucked, then animates
controller.tuck();
controller.open();
controller.toggle();
controller.selectedAction;      // null while the user is spinning
controller.isOpen;

It is a ChangeNotifier and fires when a selection settles and when the dial finishes opening or tucking.

Options #

Parameter Default
startTucked false Begin with the dial hidden behind the handle
showGuideLine false Draw the circle the pills ride on
title, subtitle design copy Pass null to hide
showStats, onStatTap true, null Stat card and its tap handler
showToast, toastMessageBuilder true, "<label> opened" Confirmation toast
onActionSelected, onActionTapped null Every settle; pill taps only
showBackground true Grey gradient and edge fades; turn off to overlay your own screen
enableHaptics true Selection click on each snap
arc QuickActionsArc() Axis, geometry, item metrics, falloff, drag thresholds
motion QuickActionsMotion() Every duration and curve
theme from ThemeData.extensions Colours, shadows, font, text styles, metrics
pillBuilder and friends null Replace a part's visuals outright

Theming #

Register once on your app theme:

ThemeData(
  extensions: const [
    AwesomeScrollActionsTheme(accentColor: Color(0xFF0B5D3B)),
  ],
)

Text uses the bundled Plus Jakarta Sans. To inherit your app font instead, construct the theme with fontFamily: null, fontPackage: null (not via copyWith, which keeps existing values).

showGuideLine draws the circle the items ride along. It is lit by the items: brightest under an icon, fading out past the outermost one and with the tuck, so it never stops dead at an end. theme.guideColor sets its colour and metrics.guideStroke its width.

Using it as an overlay #

Set showBackground: false and lay the dial over your own content. A scrim fades in behind it as the dial comes out, so the content dims while the actions are showing. While tucked, only the edge handle takes touches, so the page underneath stays usable. While open, a tap outside the dial tucks it:

Stack(children: [
  MyPage(),
  AwesomeScrollActions(actions: ..., showBackground: false),
])

theme.scrimColor sets its colour and theme.metrics.scrimOpacity how far it dims at full pull (0.45 by default; 0 removes it). The scrim sits under the design's own background, so it has no effect while showBackground is on.

Light and dark #

AwesomeScrollActionsTheme.dark is the design inverted for a dark surface: the resting pill and background go dark, the selected pill goes light. Register both and the dial follows the system setting:

MaterialApp(
  theme: ThemeData(extensions: const [AwesomeScrollActionsTheme()]),
  darkTheme: ThemeData(
    brightness: Brightness.dark,
    extensions: const [AwesomeScrollActionsTheme.dark],
  ),
)

Both share the same metrics, motion and font, and lerp into each other, so the switch animates. Adjust either with copyWith.

You only need to register them to customise the palette. With no extension registered the dial picks the shipped light or dark design off the ambient Theme.of(context).brightness, so it follows the system setting out of the box. Resolution order is theme: on the widget, then the registered extension, then the brightness.

The system "reduce motion" setting is honoured: animations become instant.

Everything else #

Every number the design hard-coded is a field, and every field defaults to the design, so a bare config changes nothing.

Text — theme.text overrides one role at a time. Styles are merged over the design's own, so setting only a size keeps the shipped weight, colour and tracking:

AwesomeScrollActionsTheme(
  text: QuickActionsTextStyles(
    title: TextStyle(fontSize: 24),
    pillLabel: TextStyle(letterSpacing: 0.2),
  ),
)

Sizes and spacing — theme.metrics holds the radii, paddings and the placement of the header, stat card, pull handle and toast:

AwesomeScrollActionsTheme(
  metrics: QuickActionsMetrics(pillRadius: 16, statTop: 150, handleWidth: 28),
)

Motion — QuickActionsMotion holds every duration and curve. scaled() stretches them together, which is handy for inspecting the animations:

AwesomeScrollActions(
  actions: actions,
  motion: const QuickActionsMotion(
    snap: Duration(milliseconds: 220),
    snapCurve: Curves.easeOutBack,
    toastDwell: Duration(seconds: 3),
  ).scaled(1.0),
)

Geometry, falloff and gestures — on QuickActionsArc: item sizes (itemSlot, iconBoxSize, iconSize, labelMaxWidth), how fast neighbours fade and shrink (opacityFalloff, scaleFalloff, minScale), and the drag thresholds (dragSlop, flingVelocity, flingProjection, pullCommit). Turn on showGuideLine while tuning.

Axis and rotation #

The dial runs vertically down the right edge by default. axis mirrors it onto the bottom edge: a horizontal drag spins, a vertical drag tucks, the handle moves to the bottom, and each pill becomes an icon circle captioned underneath instead of a wide pill with the label inside.

AwesomeScrollActions(
  actions: actions,
  arc: QuickActionsArc.horizontal,
)

Use the QuickActionsArc.horizontal preset rather than QuickActionsArc(axis: Axis.horizontal). The bare constructor keeps the radius and spacing tuned for the tall vertical arc, which across a phone's width fits only the selected item and one neighbour, with almost no curve.

Both axes handle rotation. adaptRadius (on by default) scales the radius by the real screen extent along the axis, measured against referenceExtent. At the reference extent the radius is exactly radius, so this changes nothing on the size the design was drawn for, and a rotated phone keeps the same spread instead of collapsing to three items. Set adaptRadius: false to pin the radius in logical pixels.

On a screen too short for the wide pill — a phone in landscape — a vertical dial also drops to an icon circle with the title beside it, and widens its spacing so the icons keep clear of each other. Both thresholds are fields: compactExtent (set it to 0 to always keep the wide pill) and minItemGap. In portrait the design's wide pill is untouched.

A horizontal dial also moves its stat card into the strip between the header and the icons, and scales it down when that strip is short, so the card never lands on the icons or the header in landscape. Tune with metrics.statTopHorizontal and metrics.statGapAboveDial.

Structure — when a token is not enough, replace a part outright. Taps, scaling, timing and semantics stay handled for you:

AwesomeScrollActions(
  actions: actions,
  pillBuilder: (context, action, active) => MyPill(action, active: active),
  statBuilder: (context, action, stat) => MyStatCard(stat),
  headerBuilder: (context, title, subtitle) => MyHeader(title),
  toastBuilder: (context, message) => MyToast(message),
  handleBuilder: (context, open) => MyHandle(onTap: open),
)

Example and tests #

cd example
flutter create . --platforms=android,ios   # generates the platform folders
flutter run

cd ..
flutter test

The web build behind the live demo is published from example/ by the demo GitHub Actions workflow on every push to main:

cd example
flutter build web --release --base-href /awesome_scroll-actions/

example/lib/main_shots.dart renders one dial configuration per ?shot= value; the README images are captured from it, so they can be regenerated after a visual change:

cd example
flutter build web --release -t lib/main_shots.dart --output build/shots
# serve build/shots, then screenshot ?shot=open|dark|guide|horizontal
1
likes
150
points
596
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Awesome Scroll Actions dial. An arc of action pills you spin with a vertical drag, tuck away with a side pull, and select with a tap.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on awesome_scroll_actions