Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save huycozy/eb365f31868d9841451f51446bf69d11 to your computer and use it in GitHub Desktop.

Select an option

Save huycozy/eb365f31868d9841451f51446bf69d11 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  double scrollOffset = 0;
  double maxScrollExtent = 0;

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 20,
      child: Scaffold(
        appBar: AppBar(
          bottom: PreferredSize(
            preferredSize: const Size.fromHeight(56.0),
            child: NotificationListener<ScrollMetricsNotification>(
              onNotification: (ScrollMetricsNotification notification) {
                setState(() {
                  scrollOffset = notification.metrics.pixels;
                  maxScrollExtent = notification.metrics.maxScrollExtent;
                });
                return false;
              },
              child: Stack(
                children: [
                  TabBar(
                    isScrollable: true,
                    tabs: List<Widget>.generate(
                      20,
                      (int index) => Tab(text: 'Tab $index'),
                    ),
                  ),
                  // When the tab list is not the begining or end (indicating tabbar is scrollable),
                  // add gradient mask to left or right.
                  // Left mask: shows when NOT at the beginning.
                  if (scrollOffset > 0)
                    Positioned(
                      left: 0,
                      top: 0,
                      bottom: 0,
                      width: 50,
                      child: IgnorePointer(
                        child: ClipRect(
                          child: BackdropFilter(
                            filter: ColorFilter.mode(
                              Colors.black.withValues(alpha: 0.05),
                              BlendMode.srcOver,
                            ),
                            child: Container(
                              decoration: BoxDecoration(
                                gradient: LinearGradient(
                                  begin: Alignment.centerLeft,
                                  end: Alignment.centerRight,
                                  colors: [
                                    Colors.white.withValues(alpha: 0.8),
                                    Colors.white.withValues(alpha: 0.0),
                                  ],
                                ),
                              ),
                              child: const Align(
                                alignment: Alignment.centerLeft,
                                child: Padding(
                                  padding: EdgeInsets.only(left: 4),
                                  child: Icon(
                                    Icons.chevron_left,
                                    color: Colors.black54,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  // Right mask: shows when NOT at the end
                  if (scrollOffset < maxScrollExtent)
                    Positioned(
                      right: 0,
                      top: 0,
                      bottom: 0,
                      width: 50,
                      child: IgnorePointer(
                        child: ClipRect(
                          child: BackdropFilter(
                            filter: ColorFilter.mode(
                              Colors.black.withValues(alpha: 0.05),
                              BlendMode.srcOver,
                            ),
                            child: Container(
                              decoration: BoxDecoration(
                                gradient: LinearGradient(
                                  begin: Alignment.centerRight,
                                  end: Alignment.centerLeft,
                                  colors: [
                                    Colors.white.withValues(alpha: 0.8),
                                    Colors.white.withValues(alpha: 0.0),
                                  ],
                                ),
                              ),
                              child: const Align(
                                alignment: Alignment.centerRight,
                                child: Padding(
                                  padding: EdgeInsets.only(right: 4),
                                  child: Icon(
                                    Icons.chevron_right,
                                    color: Colors.black54,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Demo:

Screen Recording 2026-01-08 at 14 18 05

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment