circular_animated_carousel 0.1.0+1 copy "circular_animated_carousel: ^0.1.0+1" to clipboard
circular_animated_carousel: ^0.1.0+1 copied to clipboard

A circular, gesture-driven carousel with garland tilt, side-lift arc, focus halo, and optional entrance + nudge animations. Ideal for coupon, ticket, and card showcases.

circular_animated_carousel #

A circular, gesture-driven carousel for any widget — cards, images, tickets, custom painters. Side items lean inward like a garland (or outward like a bowl), the focused item stays visually emphasised through a focusWeight you can wire into halos, gradients, scale, or opacity, and the entrance animation streams items in from the right.

pub package License: MIT

ArcDirection.up (default) ArcDirection.down
ArcDirection.up demo ArcDirection.down demo
Side items rise above, tilt inward (smile). Side items drop below, tilt outward (bowl).

Install #

flutter pub add circular_animated_carousel
import 'package:circular_animated_carousel/circular_animated_carousel.dart';

Features #

  • Content-agnosticitemBuilder returns any Widget. The package never assumes you're rendering cards.
  • Garland tilt + side-lift arc — neighbouring items tilt and rise (or drop) along a cosine arc; choose ArcDirection.up (smile, default) or ArcDirection.down (bowl).
  • Two entrance modes — quick displacement slide (default) or full-duration position stream (set entranceStartOffset: -9.0).
  • Responsive by default — pixel dimensions are scaled to a configurable referenceWidth (default 360), so a layout tuned on a Figma artboard looks the same on every phone.
  • Circular wrap (circular: true) — last item loops back to first; info.index is auto-wrapped so builders can index data arrays directly.
  • Programmatic controlCircularAnimatedCarouselController for next / previous / animateTo / jumpTo, with current-index/position getters.
  • Autoplay with pauseOnInteraction so the timer never fights the user's finger.
  • Snap mode toggle (enableSnap: false → free-scroll gallery; default snaps to nearest item).
  • onTap with tap-to-focus — tapping a side item auto-animates focus to it.
  • One-shot nudge bob to hint at vertical drag interactions after the entrance settles.
  • entranceDelay so the slide doesn't fire while a host bottom-sheet / modal is still opening.
  • Distance-sorted Z order — focused item always paints on top.
  • RepaintBoundary per item — every card is rasterised once and cached as a GPU layer.
  • Stable keys so the distance sort never reorders Element identities.

Quickstart #

CircularAnimatedCarousel(
  itemCount: photos.length,
  circular: true,
  itemBuilder: (context, info) => Image.network(photos[info.index]),
)

That's it — sensible defaults for everything else. Drag, flick, snap-to-index, garland tilt, responsive sizing, all on by default.

Usage #

Cards #

CircularAnimatedCarousel(
  itemCount: coupons.length,
  itemWidth: 200,
  itemHeight: 280,
  itemSpacing: 255,
  circular: true,
  entranceStartOffset: -9.0,            // cards visibly stream from the right
  entranceDelay: Duration(milliseconds: 180),
  entranceDuration: Duration(milliseconds: 2400),
  onIndexChanged: (i) => print('focused $i'),
  itemBuilder: (context, info) => MyCouponCard(
    coupon: coupons[info.index],
    showHalo: info.focusWeight > 0.5,
  ),
)

Images #

CircularAnimatedCarousel(
  itemCount: photos.length,
  itemWidth: 240,
  itemHeight: 320,
  itemBuilder: (context, info) => ClipRRect(
    borderRadius: BorderRadius.circular(16),
    child: Image.network(
      photos[info.index],
      fit: BoxFit.cover,
      // Dim non-focused photos so the centred one pops.
      color: Colors.black.withValues(alpha: 0.6 * (1 - info.focusWeight)),
      colorBlendMode: BlendMode.darken,
    ),
  ),
)

Any custom widget #

CircularAnimatedCarousel(
  itemCount: 5,
  itemWidth: 160,
  itemHeight: 160,
  itemBuilder: (context, info) => Transform.scale(
    // Side items shrink slightly; focused one is full size.
    scale: 0.85 + 0.15 * info.focusWeight,
    child: CustomPaint(painter: SpinningRingPainter(index: info.index)),
  ),
)

CarouselItemInfo #

Every itemBuilder call receives this struct. Use it to vary visuals based on how close the item is to focus.

Field Type Meaning
index int This item's index. In circular mode, pre-wrapped to [0, itemCount).
offset double Signed distance from focus. 0.0 = centered; negative = left; positive = right.
distance double offset.abs() — when you only care about how far.
focusWeight double 1.0 when exactly focused, falls smoothly to 0.0 at the neighbouring slot. Wire into halos, opacity, scale, glow alpha.

Programmatic control #

final controller = CircularAnimatedCarouselController();

CircularAnimatedCarousel(
  controller: controller,
  itemCount: 5,
  itemBuilder: (c, info) => MyCard(),
);

// Drive it from buttons / tabs / deep links:
controller.next();
controller.previous();
controller.animateTo(3);
controller.jumpTo(0);
print(controller.currentIndex);     // wrapped in circular mode
print(controller.currentPosition);  // fractional, live

Note: the verbose name avoids a collision with Flutter material's own CarouselController (added in 3.16 for CarouselView). Remember to dispose() it on State teardown.

Arc direction #

See the demos at the top of this README.

arcDirection: ArcDirection.up    // default (smile)
arcDirection: ArcDirection.down  // bowl

Both lift and tilt flip together so the geometry stays consistent — the package never produces a half-mirrored arc.

Responsive sizing #

referenceWidth: 360.0  // default — values are designed for a 360 px viewport

The package scales itemWidth, itemHeight, itemSpacing, sideLift, and nudgeAmplitude by actualViewport / referenceWidth. A layout tuned at 360 px renders ~14 % larger on a 411 px Pixel and ~11 % smaller on a 320 px Android Go device — proportions stay identical.

Pass null to opt out and use raw logical pixels.

Spacing #

Two ways to control the gap between adjacent items — pick one:

viewportFraction: 0.65    // gap = viewport * 0.65 (proportional, default)

// OR

itemSpacing: 255          // absolute pixels (scaled by referenceWidth)
                          // wins over viewportFraction when set

Autoplay + free-scroll + tap #

CircularAnimatedCarousel(
  autoplay: true,
  autoplayInterval: Duration(seconds: 3),
  pauseOnInteraction: true,        // resumes when the user lets go
  enableSnap: false,               // free-scroll gallery (no snap to nearest)
  onTap: (i) => print('tapped $i'), // non-focused taps also animate focus to that item
  itemBuilder: ...,
)

Continuous interpolation across focus #

onPositionChanged fires on every drag tick, snap tick, and position-stream entrance tick — use it when you need an effect that blends between items mid-drag (e.g. a rim glow that interpolates between two adjacent palettes).

onPositionChanged: (pos) {
  final lower = palette[pos.floor() % palette.length];
  final upper = palette[pos.ceil() % palette.length];
  glow.value = Color.lerp(lower, upper, pos - pos.floor())!;
},

For coarser "the user landed on index 3" reactions, use onIndexChanged instead — it only fires on settled snaps.

Example #

A runnable demo lives in example/ — five gradient quote cards on a dark canvas, position-stream entrance, circular wrap. Run it:

cd example && flutter run

Roadmap #

  • axis: Axis for vertical carousels.
  • Configurable spring physics on snap.
  • Vertical drag callbacks for "drag to claim" patterns.
  • Accessibility: semantics labels, screen reader announcements, keyboard navigation.
  • Golden tests.

License #

MIT — see LICENSE.

8
likes
160
points
55
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A circular, gesture-driven carousel with garland tilt, side-lift arc, focus halo, and optional entrance + nudge animations. Ideal for coupon, ticket, and card showcases.

Repository (GitHub)
View/report issues

Topics

#carousel #animation #widget #slider #cards

License

MIT (license)

Dependencies

flutter

More

Packages that depend on circular_animated_carousel