# Sticky Tab Bar

This Flutter code demonstrates a sticky tab bar with a list that scrolls independently. It uses a NestedScrollView to create a layout where the tab bar remains pinned at the top while the content below it scrolls.

Sticky Tab Bar

# Requirements

  • lib/theme/colors.dart

# Notes

To modify the appearance of tabs, edit TabBarTheme in files lib/theme/light_theme/tab_bar_theme.dart and lib/theme/dark_theme/tab_bar_theme.dart

To change the alignment and scrollability of the tabs, use the tabAlignment and isScrollable properties inside TabBar widget.

# Code

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../../theme/colors.dart';

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

  
  State<StickyTabScreen> createState() => _StickyTabScreenState();
}

class _StickyTabScreenState extends State<StickyTabScreen> with SingleTickerProviderStateMixin {
  /// Variables
  late TabController _tabController;
  final List<String> _tabs = ["Workout", "Playlist", "Recipe", "Guide"];

  /// Init
  
  void initState() {
    _tabController = TabController(length: _tabs.length, vsync: this);
    super.initState();
  }

  /// Dispose
  
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  /// Widget
  
  Widget build(BuildContext context) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: const SystemUiOverlayStyle(
          statusBarColor: Colors.transparent,
          statusBarBrightness: Brightness.light,
          statusBarIconBrightness: Brightness.dark),
      child: Scaffold(
        appBar: AppBar(
          scrolledUnderElevation: 0,
          title: const Text("StickyTabScreen"),
        ),
        drawer: const Drawer(),
        body: SafeArea(
          child: NestedScrollView(
            headerSliverBuilder: (context, innerBoxIsScrolled) => [
              /// Top Area
              SliverToBoxAdapter(
                child: Container(
                  height: 200,
                  width: double.infinity,
                  alignment: Alignment.center,
                  child: const Text("Top Header Area"),
                ),
              ),

              /// Tabs
              SliverPersistentHeader(
                pinned: true,
                delegate: _SliverAppBarDelegate(
                  TabBar(
                    controller: _tabController,
                    dividerColor: Colors.transparent,
                    tabAlignment: TabAlignment.center,
                    isScrollable: true,
                    tabs: _tabs.map((t) => Tab(text: t)).toList(),
                  ),
                ),
              ),
            ],

            /// Body
            body: TabBarView(
              controller: _tabController,
              children: [
                ListView.builder(
                    padding: const EdgeInsets.all(8),
                    itemCount: 320,
                    itemBuilder: (BuildContext context, int index) {
                      return Container(
                        height: 30,
                        color: index % 2 == 0
                            ? Colors.blue[100]
                            : Colors.green[100],
                        child: Center(child: Text('$index')),
                      );
                    }),
                Container(),
                Container(),
                Container(),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

/// App bar delegate
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate(this._tabBar);

  final TabBar _tabBar;

  
  double get minExtent => _tabBar.preferredSize.height;
  
  double get maxExtent => _tabBar.preferredSize.height;

  
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      color: Theme.of(context).brightness == Brightness.dark
          ? darkPrimaryColor
          : primaryColor,
      child: _tabBar,
    );
  }

  
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return false;
  }
}