infinite_scroller 1.1.0 copy "infinite_scroller: ^1.1.0" to clipboard
infinite_scroller: ^1.1.0 copied to clipboard

A high-performance, infinitely scrolling horizontal grid widget for Flutter. Features synchronized tabs, haptic feedback, controller support, and extensive customization.

Infinite Scroller #

A high-performance, infinitely scrolling horizontal grid widget for Flutter. Perfect for e-commerce apps, media galleries, and any app that needs to display grouped content with smooth navigation.

Features #

  • Infinite Horizontal Scrolling - Seamless looping with no visible reset
  • Synchronized Tabs - Auto-select based on scroll position
  • Controller Support - Programmatic navigation and state access
  • Highly Customizable - Builders and configuration objects for full control
  • Haptic Feedback - Configurable feedback on interactions
  • Performance Optimized - ValueNotifier-based efficient rebuilds
  • Generic Types - Works with any data model
  • Loading & Empty States - Built-in support for common UI states
  • RTL/LTR Support - Full right-to-left and left-to-right language support

Installation #

Add to your pubspec.yaml:

dependencies:
  infinite_scroller: ^1.1.0

Or install via command line:

flutter pub add infinite_scroller

Quick Start #

import 'package:infinite_scroller/infinite_scroller.dart';

InfiniteScroller<Product>(
  sections: [
    ScrollerSection(
      id: 'electronics',
      label: 'Electronics',
      icon: Icons.devices,
      items: [Product('iPhone'), Product('MacBook')],
    ),
    ScrollerSection(
      id: 'clothing',
      label: 'Clothing',
      icon: Icons.checkroom,
      items: [Product('T-Shirt'), Product('Jeans')],
    ),
  ],
  tabBuilder: (context, section, index, isActive) {
    return MyTab(
      label: section.label,
      icon: section.icon,
      isActive: isActive,
    );
  },
  itemBuilder: (context, section, item, itemIndex, globalIndex) {
    return ProductCard(product: item);
  },
)

Configuration #

TabBarConfig #

Control the appearance and behavior of the tab bar:

TabBarConfig(
  height: 110.0,              // Tab bar height
  tabWidth: 100.0,            // Width of each tab
  backgroundColor: Colors.white,
  showShadow: true,
  shadowElevation: 2.0,
  animationDuration: Duration(milliseconds: 400),
  animationCurve: Curves.easeOutCubic,
  centerTabs: false,
)

GridConfig #

Configure the scrolling grid layout:

GridConfig(
  crossAxisCount: 2,          // Number of rows
  columnWidth: 200.0,         // Width per column
  mainAxisSpacing: 12.0,      // Horizontal spacing
  crossAxisSpacing: 12.0,     // Vertical spacing
  childAspectRatio: 1.1,      // Item aspect ratio
  padding: EdgeInsets.all(16),
  scrollDuration: Duration(milliseconds: 600),
  scrollCurve: Curves.easeInOutQuart,
  loopFactor: 10,             // Infinite loop multiplier
)

HapticConfig #

Customize haptic feedback:

HapticConfig(
  enabled: true,
  onSectionChange: HapticType.light,
  onTabTap: HapticType.selection,
)

Available haptic types: light, medium, heavy, selection, none

Controller #

Use ScrollerController for programmatic control:

final controller = ScrollerController();

// Animate to a section
await controller.animateTo(2);

// Jump without animation
controller.jumpTo(0);

// Scroll the grid
controller.scrollBy(100.0);

// Access state
print('Active section: ${controller.activeIndex}');
print('Grid offset: ${controller.gridOffset}');
print('Is attached: ${controller.isAttached}');

Callbacks #

React to user interactions:

InfiniteScroller<Product>(
  // Called when active section changes
  onSectionChanged: (index, sectionId) {
    print('Now viewing: $sectionId');
  },
  
  // Called when an item is tapped
  onItemTap: (item, sectionIndex, itemIndex) {
    Navigator.push(context, ProductDetailPage(item));
  },
  
  // Called during scroll
  onScroll: (offset, maxExtent) {
    print('Scroll: $offset / $maxExtent');
  },
  
  // Called when a tab is double-tapped
  onTabDoubleTap: (section, index) {
    print('Double tapped: ${section.label}');
  },
  
  // Called when a tab is long-pressed
  onTabLongPress: (section, index) {
    showContextMenu(context, section);
  },
  
  // ... other properties
)

Tab Gesture Callbacks #

The scroller supports additional gesture callbacks on section tabs for enhanced interactions:

InfiniteScroller<Product>(
  // Double-tap to perform quick actions
  onTabDoubleTap: (section, index) {
    toggleFavorite(section);
  },
  
  // Long-press to show context menu
  onTabLongPress: (section, index) {
    showModalBottomSheet(
      context: context,
      builder: (_) => TabOptionsMenu(section: section),
    );
  },
  // ...
)

Note: Adding onDoubleTap introduces a slight delay before single taps are recognized (to distinguish between single and double taps). Only add this callback if you need the functionality.

ScrollerSection #

Define your sections with full flexibility:

ScrollerSection<Product>(
  id: 'unique-id',           // Optional unique identifier
  label: 'Section Name',     // Display label
  items: [...],              // List of items
  icon: Icons.star,          // Optional icon
  iconWidget: CustomIcon(),  // Or use a custom widget
  data: {'priority': 1},     // Optional arbitrary data
  enabled: true,             // Can be disabled
)

Advanced Usage #

Custom Detection Point #

Control where section detection happens on screen:

InfiniteScroller(
  detectionPoint: 0.4, // 0.0 = left, 0.5 = center, 1.0 = right
  // ...
)

Tab Bar Position #

Place the tab bar at top or bottom:

InfiniteScroller(
  showTabBar: true,
  tabBarPosition: TabBarPosition.bottom,
  // ...
)

Loading & Empty States #

Handle loading and empty data gracefully:

InfiniteScroller(
  isLoading: isDataLoading,
  loadingWidget: CustomLoadingSpinner(),
  emptyWidget: EmptyStateWidget(message: 'No content'),
  // ...
)

Initial Section #

Start at a specific section:

InfiniteScroller(
  initialIndex: 2, // Start at third section
  // ...
)

RTL/LTR Support #

The scroller fully supports right-to-left (RTL) and left-to-right (LTR) text directions. This is essential for apps supporting Arabic, Hebrew, Persian, and other RTL languages.

// Explicit RTL mode
InfiniteScroller(
  textDirection: TextDirection.rtl,
  // ...
)

// Explicit LTR mode
InfiniteScroller(
  textDirection: TextDirection.ltr,
  // ...
)

// Inherit from context (default behavior)
InfiniteScroller(
  // textDirection: null (uses Directionality from context)
  // ...
)

When RTL is enabled:

  • Items scroll from right to left
  • Tabs are arranged right to left
  • Section detection point is automatically mirrored
  • All scroll animations work correctly in the reversed direction

For apps that need dynamic RTL switching:

bool isRtl = false;

InfiniteScroller(
  textDirection: isRtl ? TextDirection.rtl : TextDirection.ltr,
  // ...
)

Best Practices #

Follow these guidelines to get the best performance and user experience from the scroller.

Cache Your Sections List #

Always cache the sections list to prevent unnecessary rebuilds and scroll position resets.

// GOOD: Cache sections as a final field
class _MyScreenState extends State<MyScreen> {
  late final List<ScrollerSection<Product>> _sections;
  
  @override
  void initState() {
    super.initState();
    _sections = _buildSections();
  }
  
  List<ScrollerSection<Product>> _buildSections() => [
    ScrollerSection(label: 'Electronics', items: electronics),
    // ...
  ];
  
  @override
  Widget build(BuildContext context) {
    return InfiniteScroller(sections: _sections, ...);
  }
}

// BAD: Creating new list in build() causes problems
Widget build(BuildContext context) {
  return InfiniteScroller(
    sections: [
      ScrollerSection(...), // New list every build!
    ],
    ...
  );
}

Keep Builders Lightweight #

Tab and item builders are called frequently during scroll. Avoid expensive operations.

// GOOD: Simple widget construction
itemBuilder: (context, section, item, itemIndex, globalIndex) {
  return ProductCard(product: item);
}

// BAD: Expensive operations in builder
itemBuilder: (context, section, item, itemIndex, globalIndex) {
  final processedImage = heavyImageProcessing(item.image); // Slow!
  final analytics = computeAnalytics(item); // Slow!
  return ProductCard(product: item, image: processedImage);
}

Use ValueNotifier for Scroll Callbacks #

The onScroll callback is called on every frame. Never use setState() here.

// GOOD: Use ValueNotifier
final _scrollProgress = ValueNotifier<double>(0);

InfiniteScroller(
  onScroll: (offset, maxExtent) {
    _scrollProgress.value = offset / maxExtent;
  },
  ...
)

// In your UI:
ValueListenableBuilder<double>(
  valueListenable: _scrollProgress,
  builder: (context, progress, _) => LinearProgressIndicator(value: progress),
)

// BAD: setState on every scroll frame causes jank
onScroll: (offset, maxExtent) {
  setState(() => _progress = offset / maxExtent); // Don't do this!
}

Dispose Controllers #

Always dispose the ScrollerController to prevent memory leaks.

class _MyScreenState extends State<MyScreen> {
  late final ScrollerController _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = ScrollerController();
  }
  
  @override
  void dispose() {
    _controller.dispose(); // Important!
    super.dispose();
  }
}

Choose Appropriate Loop Factor #

Balance memory usage and scroll smoothness.

GridConfig(
  loopFactor: 6,  // Default - good for most cases
  // loopFactor: 4,  // Lower memory, may flicker on very fast scroll
  // loopFactor: 10, // Smoother on fast scroll, uses more memory
)

Use Const Constructors #

Use const for static configuration objects.

// GOOD: const for better performance
InfiniteScroller(
  tabBarConfig: const TabBarConfig(height: 100, tabWidth: 90),
  gridConfig: const GridConfig(crossAxisCount: 2, columnWidth: 180),
  hapticConfig: const HapticConfig(enabled: true),
  ...
)

What to Avoid #

Common pitfalls that can cause performance issues or unexpected behavior.

Don't Create Sections in Build #

Creating new section lists in build() causes the scroller to reinitialize, losing scroll position.

// BAD: This resets scroll position on every rebuild
Widget build(BuildContext context) {
  final sections = [
    ScrollerSection(label: 'A', items: items),
  ];
  return InfiniteScroller(sections: sections, ...);
}

Don't Use setState in onScroll #

This causes the entire widget tree to rebuild on every scroll frame, destroying performance.

// BAD: Causes severe scroll jank
onScroll: (offset, maxExtent) {
  setState(() {
    _offset = offset;
  });
}

Don't Ignore Controller Lifecycle #

Forgetting to dispose controllers causes memory leaks.

// BAD: Memory leak!
class _MyScreenState extends State<MyScreen> {
  final controller = ScrollerController();
  
  @override
  void dispose() {
    // Forgot to dispose controller!
    super.dispose();
  }
}

Don't Use Heavy Widgets in Builders #

Complex widgets in builders slow down scrolling.

// BAD: Heavy computation in builder
itemBuilder: (context, section, item, itemIndex, globalIndex) {
  // Avoid these in builders:
  return FutureBuilder(...); // Async in builder
  return StreamBuilder(...); // Stream in builder
  return BlocBuilder(...);   // Consider pre-computing state
}

// GOOD: Pre-compute and pass data
itemBuilder: (context, section, item, itemIndex, globalIndex) {
  return ProductCard(product: item); // Simple widget creation
}

Don't Set Very Low Loop Factor #

Values below 4 may cause visible content jumping on fast scrolls.

// BAD: May cause visual glitches
GridConfig(loopFactor: 2)

// GOOD: Safe minimum
GridConfig(loopFactor: 4)

// RECOMMENDED: Default
GridConfig(loopFactor: 6)

Don't Wrap Items in RepaintBoundary #

The grid already adds repaint boundaries. Adding more creates overhead.

// BAD: Redundant repaint boundary
itemBuilder: (context, section, item, itemIndex, globalIndex) {
  return RepaintBoundary( // Already handled by grid
    child: ProductCard(product: item),
  );
}

// GOOD: Just return the widget
itemBuilder: (context, section, item, itemIndex, globalIndex) {
  return ProductCard(product: item);
}

Don't Use BouncingScrollPhysics for Grid #

Bouncy physics can cause visual artifacts at loop boundaries.

// BAD: Can cause issues at loop boundaries
GridConfig(physics: BouncingScrollPhysics())

// GOOD: Use clamping for smooth infinite scroll
GridConfig(physics: ClampingScrollPhysics()) // Default

Configuration Options Reference #

All TabBarConfig Options #

Option Type Default Description
height double 110.0 Tab bar height in pixels
tabWidth double 100.0 Width of each tab
backgroundColor Color? scaffold bg Tab bar background
border BoxBorder? bottom border Tab bar border
padding EdgeInsetsGeometry zero Padding around tabs
showShadow bool false Show shadow below
shadowElevation double 2.0 Shadow blur radius
physics ScrollPhysics? clamping Tab scroll physics
centerTabs bool false Center tabs (reserved)
animationDuration Duration 400ms Tab animation duration
animationCurve Curve easeOutCubic Tab animation curve

All GridConfig Options #

Option Type Default Description
crossAxisCount int 2 Number of rows
columnWidth double 200.0 Width per column
mainAxisSpacing double 12.0 Horizontal gap
crossAxisSpacing double 12.0 Vertical gap
childAspectRatio double 1.1 Item width/height ratio
padding EdgeInsetsGeometry all(16) Grid padding
physics ScrollPhysics? clamping Grid scroll physics
scrollDuration Duration 600ms Programmatic scroll duration
scrollCurve Curve easeInOutQuart Programmatic scroll curve
loopFactor int 6 Infinite loop multiplier

All HapticConfig Options #

Option Type Default Description
enabled bool true Master haptic switch
onSectionChange HapticType light Haptic on section change
onTabTap HapticType selection Haptic on tab tap

HapticType Values #

Value Description iOS Equivalent
light Subtle quick tap UIImpactFeedbackGenerator(.light)
medium Moderate tap UIImpactFeedbackGenerator(.medium)
heavy Strong noticeable tap UIImpactFeedbackGenerator(.heavy)
selection UI selection tick UISelectionFeedbackGenerator
none No haptic feedback -

API Reference #

InfiniteScroller #

Property Type Default Description
sections List<ScrollerSection<T>> required List of sections
tabBuilder TabBuilder<T> required Builder for tabs
itemBuilder ItemBuilder<T> required Builder for items
tabBarConfig TabBarConfig TabBarConfig() Tab bar configuration
gridConfig GridConfig GridConfig() Grid configuration
hapticConfig HapticConfig HapticConfig() Haptic configuration
controller ScrollerController? null Optional controller
onSectionChanged OnSectionChanged? null Section change callback
onItemTap OnItemTap<T>? null Item tap callback
onScroll OnScroll? null Scroll callback
onTabDoubleTap OnTabDoubleTap<T>? null Tab double-tap callback
onTabLongPress OnTabLongPress<T>? null Tab long-press callback
initialIndex int 0 Starting section
showTabBar bool true Show/hide tab bar
tabBarPosition TabBarPosition top Tab bar position
isLoading bool false Loading state
loadingWidget Widget? null Custom loading widget
emptyWidget Widget? null Custom empty widget
detectionPoint double 0.4 Detection point (0.0-1.0)
textDirection TextDirection? null Text direction (RTL/LTR)

ScrollerSection #

Property Type Default Description
id String? null Unique identifier
label String required Display label
items List<T> required Items in section
icon IconData? null Section icon
iconWidget Widget? null Custom icon widget
data dynamic null Arbitrary data
enabled bool true Whether selectable

ScrollerController #

Method/Property Type Description
activeIndex int Current active section index
isAttached bool Whether attached to scroller
gridOffset double Current grid scroll offset
tabOffset double Current tab scroll offset
animateTo(index) Future<void> Animate to section
jumpTo(index) void Jump to section
scrollBy(offset) void Scroll grid by offset
scrollTo(offset) Future<void> Animate grid to offset

License #

MIT License - see LICENSE file for details.

0
likes
160
points
622
downloads

Publisher

unverified uploader

Weekly Downloads

A high-performance, infinitely scrolling horizontal grid widget for Flutter. Features synchronized tabs, haptic feedback, controller support, and extensive customization.

Homepage
Repository (GitHub)
View/report issues

Topics

#infinite-scroll #grid #horizontal-scroll #widget #tabs

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on infinite_scroller