Reputation: 813
I've got a screen having some content on top of a TabBar
.
Both the content above TabBar
and in TabBarView
can be of dynamic height.
My use case is that the upper content should only be scrollable when all of the content is not visible and only up to the point that all of it becomes visible and not beyond that. So in the following example, only Tab 1
should be scrollable.
Setting the scrollphysics to NeverScrollableScrollPhysics
wouldn't work since I can't determine the scroll behavior beforehand because of the dynamic height of the contents. Using SliverAppBar
also doesn't work for the same reason.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
final length = 5;
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final List<String> _tabs = <String>['Tab 1', 'Tab 2', 'Tab 3'];
return DefaultTabController(
length: _tabs.length,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverToBoxAdapter(
child: Column(
children: [
Container(
width: double.infinity,
alignment: Alignment.center,
child: Column(
children: [
const Text('Upper Content'),
ListView.builder(
shrinkWrap: true,
itemCount: length,
itemBuilder: (_, __) => Container(
padding: const EdgeInsets.all(5),
alignment: Alignment.center,
child: const Text('Items'),
),
)
],
),
),
Container(
color: Colors.blue,
child: TabBar(
tabs: _tabs
.map(
(String name) => Tab(
text: name,
),
)
.toList(),
),
)
],
),
),
),
];
},
body: TabBarView(
children: _tabs.map((String name) {
return name.split(' ')[1] != '3'
? SafeArea(
top: false,
bottom: false,
child: Builder(
builder: (BuildContext context) {
return CustomScrollView(
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverFixedExtentList(
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount:
name.split(' ')[1] != '2' ? 15 : 5,
),
),
),
],
);
},
),
)
: Container(
height: 50,
width: 50,
color: Colors.yellow,
);
}).toList(),
),
),
),
);
}
}
Upvotes: 5
Views: 2845
Reputation: 63549
TabBarView
requires finite height while wrapping with scrollable widget and on others cases all tabs become scrollable. Also trying with IndexedStack
provide the same behavior.
I am not using
TabBarView
.
I am loading widgets for tabs inside initState
and just passing inside body.
Run on dartPad.
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
final length = 8;
late final TabController controller;
final List<String> _tabs = <String>['Tab 1', 'Tab 2', 'Tab 3'];
List<Widget> tabViews = [];
@override
void initState() {
controller = TabController(
length: _tabs.length,
vsync: this,
)..addListener(() {
setState(() {});
});
tabViews = List.generate(
_tabs.length,
(index) => Column(
mainAxisSize: MainAxisSize.min,
children: [
...List.generate(
index * 3 + 2,
(itb) => Container(
alignment: Alignment.center,
height: 100,
width: double.infinity,
color: Color(Random().nextInt(0xffffffff)),
child: Text("Tab: $index item $itb"),
),
)
],
));
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Column(
children: [
...List.generate(
5,
(index) => Container(
height: 50,
color: Colors.deepPurple,
width: double.infinity,
padding: const EdgeInsets.all(10),
child: Text(" top item $index"),
),
),
],
),
Container(
color: Colors.primaries.first,
height: kToolbarHeight,
child: TabBar(
tabs: _tabs.map((e) => Text(e)).toList(),
controller: controller,
),
),
tabViews[controller.index],
],
),
),
);
}
}
Upvotes: 2