I got it working somehow, but the scroll feature is gone:
return Scaffold(
body: DefaultTabController(
length: 2,
child: ListView(
children: <Widget>[
Container(
height: 120,
child: Center(
child: Text('something on top'),
),
),
TabBar(
// controller: _tabController,
labelColor: Colors.redAccent,
isScrollable: true,
tabs: [
Tab(text: "Finished"), // TODO: translate
Tab(text: "In progress"), // TODO: translate
],
),
Center(
child: [
Text('second tab1232'),
Text('second tab111'),
Column(
children: List.generate(20, (index) => Text('line: $index'))
.toList(),
),
Text('third tab')
][0], // change this
),
Container(child: Text('another component')),
],
),
),
);
Note: check the [0] that I simplified.
Not sure if I can fix the scroll from this or if I need to take a totally different approach.
Example of content scroll working with the original way: https://flutter.dev/docs/cookbook/design/tabs
With this we don't have the swipe and its animation but it could be created by some other widgets. It works with a dynamic height anyway:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class OptionsScreen extends StatefulWidget {
const OptionsScreen({Key? key}) : super(key: key);
@override
OptionsScreenState createState() => OptionsScreenState();
}
class OptionsScreenState extends State<OptionsScreen> {
final bodyGlobalKey = GlobalKey();
late var streamFunction;
@override
void initState() {
streamFunction = delayFunction;
super.initState();
}
@override
void dispose() {
super.dispose();
}
Stream<int> delayFunction = (() async* {
for (int i = 1; i <= 3; i++) {
await Future<void>.delayed(const Duration(seconds: 1));
yield i;
}
})();
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: streamFunction,
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
if (!snapshot.hasData) return const Text('loading...');
if (snapshot.data == null) return Container();
// return tabs();
return const CustomTabs();
},
);
}
}
class CustomTabs extends StatefulWidget {
const CustomTabs({Key? key}) : super(key: key);
@override
State<CustomTabs> createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabs>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
_tabController = TabController(length: 3, vsync: this);
super.initState();
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TabBar(
isScrollable: true,
controller: _tabController,
// COMMENT FOR WAY B
onTap: (int index) {
setState(() {
_tabController.animateTo(index);
});
},
labelColor: Theme.of(context).primaryColor,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
),
),
tabs: [
Tab(text: '1'),
Tab(text: '2'),
Tab(text: '3'),
],
),
// WAY A
// THIS WAY HAS NO ANIMATION BUT IT HAS DYNAMIC HEIGHT
// SetState TRIGGERS STREAMBUILDER WITH TAB CHANGES
Column(
children: [
Visibility(
visible: _tabController.index == 0,
child: const Text('1111'),
),
Visibility(
visible: _tabController.index == 1,
child: Column(
children: const [
Text('2222'),
Text('2222'),
Text('2222'),
Text('2222'),
Text('2222'),
Text('2222'),
],
),
),
Visibility(
visible: _tabController.index == 2,
child: const Text('33333'),
),
const Text('This is a different widget. Tabs will push it down')
],
),
// THIS WAY HAS NO DYNAMIC HEIGHT BUT IT HAS ANIMATION
// STREAMBUILDER ONLY TRIGGERS FIRST TIME
// Container(
// color: Colors.red,
// height: 200,
// padding: const EdgeInsets.only(top: 8.0),
// child: TabBarView(
// controller: _tabController,
// children: [
// const Text('1111'),
// Column(
// children: const [
// Text('2222'),
// Text('2222'),
// Text('2222'),
// Text('2222'),
// ],
// ),
// const Text('3333'),
// ],
// ),
// ),
],
),
],
);
}
}