The flutter docs provides a nice example on how to have a floating app bar above a list using CustomScrollView. The code in their example exhibits exactly the behavior I want, that is, for the app bar to only scroll away when there is enough content to scroll. Below is a verbatim copy of the code with the exception of the childCount property of the SliverList set to a small number of items to demonstrate that no scrolling will occur in that case.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const title = 'Floating App Bar';
return MaterialApp(
title: title,
home: Scaffold(
// No appbar provided to the Scaffold, only a body with a
// CustomScrollView.
body: CustomScrollView(
slivers: [
// Add the app bar to the CustomScrollView.
const SliverAppBar(
// Provide a standard title.
title: Text(title),
// Allows the user to reveal the app bar if they begin scrolling
// back up the list of items.
floating: true,
// Display a placeholder widget to visualize the shrinking size.
flexibleSpace: Placeholder(),
// Make the initial height of the SliverAppBar larger than normal.
expandedHeight: 200,
),
// Next, create a SliverList
SliverList(
// Use a delegate to build items as they're scrolled on screen.
delegate: SliverChildBuilderDelegate(
// The builder function returns a ListTile with a title that
// displays the index of the current item.
(context, index) => ListTile(title: Text('Item #$index')),
// Builds 1000 ListTiles
childCount: 3, //<------------- Adjusted to three items
),
),
],
),
),
);
}
}
I would like to mimic exactly this behavior using a TabBar with a corresponding TabBarView. The problem is that the TabBarView scrolls unnecessarily when the content is small and overflows if its content is large. I've tried using a NestedScrollView following the tutorial in the docs but the problem persists. I have currently resorted to the solution mentioned by the OP at the bottom of their question where they suggest to nest the SliverAppBar within a CustomScrollView. This does not work either. Below is a simplified version of the code which illustrates the issue. Note that the contents of tab 1 and tab 2 scrolls unnecessarily while the contents of tab 3 overflows.
This answer demonstrates exactly what I'm after but does not use a TabBarView (which allows swiping to change tabs).
import 'package:flutter/material.dart';
void main() => runApp(const NestedScrollViewExampleApp());
class NestedScrollViewExampleApp extends StatelessWidget {
const NestedScrollViewExampleApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(body: SafeArea(child: NestedScrollViewExample())),
);
}
}
class NestedScrollViewExample extends StatelessWidget {
const NestedScrollViewExample({super.key});
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
title: const Text('Floating Nested SliverAppBar'),
floating: true,
pinned: false,
expandedHeight: 200.0,
forceElevated: false,
bottom: TabBar(
tabs: ['tab1', 'tab2', 'tab3'].map((e) => Text(e)).toList()),
),
const SliverFillRemaining(
child: TabBarView(
children: [Text('content1'), Text('content2'), _OverflowContent()],
),
)
],
),
),
);
}
}
class _OverflowContent extends StatelessWidget {
const _OverflowContent({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [
for (int i = 0; i < 50; i++) ...[Text('Item $i')],
]);
}
}
I think that the CustomScrollView needs to somehow be aware of the size of the TabView's content, but I'm not sure if this is at all possible. My goal is to have a TabBarView with dynamically sized content within a scrolling view.
Any help is much appreciated. Thanks.
Edit Summary:
This is a workaround that will set the scroll property of the NestedScrollView based on the number of items that need to be shown.
I used a NestedScrollView with a TickerProviderStateMixIn
scrollOrNot() determines the scroll physics based on the number of items.
Any list with less than 5 items will not be scrollable.
tabContent() returns a sliverlist for each tab in the TabBarView