You can import `import 'dart:math';` Just copy past my code and enjoy
class PullUpTab extends StatefulWidget {
@override
_PullUpTabState createState() => _PullUpTabState();
}
class _PullUpTabState extends State<PullUpTab> {
double get randHeight => Random().nextInt(100).toDouble();
List<Widget>? _randomChildren;
// Children with random heights - You can build your widgets of unknown heights here
// I'm just passing the context in case if any widgets built here needs access to context based data like Theme or MediaQuery
List<Widget> _randomHeightWidgets(BuildContext context) {
_randomChildren ??= List.generate(3, (index) {
final height = randHeight.clamp(
50.0,
MediaQuery.of(context).size.width, // simply using MediaQuery to demonstrate usage of context
);
return Container(
color: Colors.primaries[index],
height: height,
child: Text('Random Height Child ${index + 1}'),
);
});
return _randomChildren!;
}
@override
Widget build(BuildContext context) {
return Scaffold(
// Persistent AppBar that never scrolls
appBar: AppBar(
title: Text('AppBar'),
elevation: 0.0,
),
body: DefaultTabController(
length: 2,
child: NestedScrollView(
// allows you to build a list of elements that would be scrolled away till the body reached the top
headerSliverBuilder: (context, _) {
return [
SliverList(
delegate: SliverChildListDelegate(
_randomHeightWidgets(context),
),
),
];
},
// You tab view goes here
body: Column(
children: <Widget>[
TabBar(
tabs: [
Tab(text: 'A'),
Tab(text: 'B'),
],
),
Expanded(
child: TabBarView(
children: [
GridView.count(
padding: EdgeInsets.zero,
crossAxisCount: 3,
children: Colors.primaries.map((color) {
return Container(color: color, height: 150.0);
}).toList(),
),
ListView(
padding: EdgeInsets.zero,
children: Colors.primaries.map((color) {
return Container(color: color, height: 150.0);
}).toList(),
)
],
),
),
],
),
),
),
);
}
}
Don't use `TabBarView`, use `IndexedStack` with `Visibility` instead.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
TabController tabController;
int selectedIndex = 0;
@override
void initState() {
super.initState();
tabController = TabController(
initialIndex: selectedIndex,
length: 2,
vsync: this,
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: ListView(
children: [
Container(
height: 128,
color: Colors.blue,
),
Container(
height: 256,
color: Colors.green,
),
TabBar(
tabs: <Tab>[
Tab(text: 'Tab Left'),
Tab(text: 'Tab Right'),
],
controller: tabController,
onTap: (int index) {
setState(() {
selectedIndex = index;
tabController.animateTo(index);
});
},
),
Divider(height: 0),
IndexedStack(
children: <Widget>[
Visibility(
child: Container(
height: 200,
color: Colors.yellow,
child: Center(
child: Text('Content left'),
),
),
maintainState: true,
visible: selectedIndex == 0,
),
Visibility(
child: Container(
height: 1000,
color: Colors.red,
child: Center(
child: Text('Content right'),
),
),
maintainState: true,
visible: selectedIndex == 1,
),
],
index: selectedIndex,
),
],
),
),
);
}
}
`IndexedStack` will show a single child from a list of children based on `index` while `Visibility` will maintain to show or hide view. When the view is hiding, so there is no excess white space to show (stack height is equal to the maximum height of its children).
Here is the dartpad https://dartpad.dev/535f06aa01257b049c7f2f9c719c9881.