scroll_sync_tabs 0.1.0 copy "scroll_sync_tabs: ^0.1.0" to clipboard
scroll_sync_tabs: ^0.1.0 copied to clipboard

Keep a TabBar and a scrollable list of sections in sync both ways - scrolling selects the tab, tapping a tab scrolls to the section.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:scroll_sync_tabs/scroll_sync_tabs.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'scroll_sync_tabs',
    theme: ThemeData(useMaterial3: true),
    home: const MenuPage(),
  );
}

class MenuSection {
  const MenuSection(this.title, this.dishes);

  final String title;

  final List<String> dishes;
}

class MenuPage extends StatefulWidget {
  const MenuPage({super.key});

  @override
  State<MenuPage> createState() => _MenuPageState();
}

class _MenuPageState extends State<MenuPage> with TickerProviderStateMixin {
  static const double _tabStripHeight = 48;

  static const double _pinnedHeaderExtent = kToolbarHeight + _tabStripHeight;

  late final ScrollTabController<MenuSection> _controller;

  @override
  void initState() {
    super.initState();
    _controller = ScrollTabController<MenuSection>(
      items: const [],
      vsync: this,
      pinnedHeaderExtent: _pinnedHeaderExtent,
    );
    _load();
  }

  Future<void> _load() async {
    await Future<void>.delayed(const Duration(milliseconds: 600));
    if (!mounted) {
      return;
    }
    _controller.updateItems(const [
      MenuSection('Popular', [
        'Chè thái',
        'Chè bưởi',
        'Chè khúc bạch',
        'Chè đậu xanh',
        'Chè bà ba',
      ]),
      MenuSection('Desserts', [
        'Sticky rice',
        'Flan',
        'Mango sago',
        'Banana cake',
        'Coconut pudding',
      ]),
      MenuSection('Drinks', [
        'Iced tea',
        'Coconut coffee',
        'Soda chanh',
        'Sinh tố xoài',
        'Nước mía',
      ]),
      MenuSection('Toppings', [
        'Pearls',
        'Jelly',
        'Coconut milk',
        'Red beans',
        'Grass jelly',
      ]),
      MenuSection('Combos', ['Family set', 'Duo set', 'Party platter']),
    ]);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: AnimatedBuilder(
        animation: _controller,
        builder: (context, _) {
          if (_controller.items.isEmpty) {
            return const Center(child: CircularProgressIndicator());
          }
          return CustomScrollView(
            controller: _controller.scrollController,
            slivers: [
              const SliverAppBar(
                expandedHeight: 180,
                pinned: true,
                flexibleSpace: FlexibleSpaceBar(title: Text("Chè Chang's")),
              ),
              ScrollTabBar.sliver<MenuSection>(
                height: _tabStripHeight,
                controller: _controller,
                isScrollable: true,
                tabAlignment: TabAlignment.start,
                labelBuilder: (context, item, index) => Text(item.title),
              ),
              SliverList.builder(
                itemCount: _controller.items.length,
                itemBuilder: (context, i) {
                  final section = _controller.items[i];
                  return ScrollTabAnchor(
                    controller: _controller,
                    index: i,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Padding(
                          padding: const EdgeInsets.all(16),
                          child: Text(
                            section.title,
                            style: Theme.of(context).textTheme.titleLarge,
                          ),
                        ),
                        ...section.dishes.map(
                          (d) => ListTile(
                            leading: const CircleAvatar(
                              child: Icon(Icons.restaurant),
                            ),
                            title: Text(d),
                          ),
                        ),
                        const SizedBox(height: 24),
                      ],
                    ),
                  );
                },
              ),
            ],
          );
        },
      ),
    );
  }
}
5
likes
160
points
98
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Keep a TabBar and a scrollable list of sections in sync both ways - scrolling selects the tab, tapping a tab scrolls to the section.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on scroll_sync_tabs