sliver_fixed_extend_list_with_tabs 1.0.1
sliver_fixed_extend_list_with_tabs: ^1.0.1 copied to clipboard
A sliver with a pinned tab bar over a fixed-extent list. The tab bar follows the scroll, and tapping a tab scrolls to its section.
import 'package:flutter/material.dart';
import 'package:sliver_fixed_extend_list_with_tabs/sliver_fixed_extend_list_with_tabs.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SliverFixedExtendListWithTabs Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'SliverFixedExtendListWithTabs'),
);
}
}
/// A section header. Only the label and whatever the app needs to draw the row.
class Course extends HeaderItem {
const Course({
required super.key,
required this.name,
required super.tabLabel,
});
final String name;
}
class Dish extends ChildItem {
const Dish({required super.key, required this.name, required this.price});
final String name;
final String price;
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const List<String> _courses = <String>[
'Starters',
'Soups',
'Mains',
'Pasta',
'Grill',
'Sides',
'Desserts',
'Drinks',
];
final ScrollController _controller = ScrollController();
late final List<Section> _sections;
int _section = 0;
@override
void initState() {
super.initState();
// No offset arithmetic: the package derives every section's scroll offset
// from the section order and listItemHeight.
_sections = <Section>[
for (int i = 0; i < _courses.length; i++)
Section(
header: Course(
key: ValueKey<int>(i),
name: _courses[i],
tabLabel: _courses[i],
),
children: <ChildItem>[
for (int j = 0; j < 6; j++)
Dish(
key: ValueKey<String>('$i-$j'),
name: '${_courses[i]} item ${j + 1}',
price: '${(j + 1) * 7 + i} zl',
),
],
),
];
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Widget _buildHeader(BuildContext context, HeaderItem item) {
final Course course = item as Course;
return ColoredBox(
color: Theme.of(context).colorScheme.primaryContainer,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
course.name,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
);
}
Widget _buildChild(BuildContext context, ChildItem item) {
final Dish dish = item as Dish;
return ListTile(title: Text(dish.name), trailing: Text(dish.price));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: _controller,
slivers: <Widget>[
SliverAppBar(
pinned: true,
title: Text(widget.title),
actions: <Widget>[
Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text('section $_section'),
),
),
],
),
SliverFixedExtendListWithTabs(
controller: _controller,
listItemHeight: 64,
sections: _sections,
headerBuilder: _buildHeader,
childBuilder: _buildChild,
onSectionChanged: (int index) => setState(() => _section = index),
tabBarIndicator: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context).colorScheme.primary,
),
labelColor: Theme.of(context).colorScheme.onPrimary,
unselectedLabelColor: Theme.of(context).colorScheme.onSurface,
indicatorPadding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 6,
),
customFooterWidget: const Center(child: Text('end of the menu')),
),
],
),
);
}
}