Reputation: 2456
I've boiled my code down to a small sample app, so considering the following code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
TabController _tabController;
ScrollController _scrollController;
ScrollController _sliverScrollController;
var tabs = ['Tab1', 'Tab2', 'Tab3'];
@override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: 3);
_scrollController = ScrollController();
_sliverScrollController = ScrollController();
}
@override
void dispose() {
// "Unmount" the controllers:
_tabController.dispose();
_scrollController.dispose();
_sliverScrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
body: NestedScrollView(
controller: _scrollController,
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return [
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
sliver: SliverAppBar(
backgroundColor: Colors.black,
pinned: true,
expandedHeight: 400,
elevation: 0,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Flex(
direction: Axis.vertical,
children: <Widget>[
Padding(
padding: EdgeInsets.only(
top: 75,
left: 10,
right: 10,
bottom: 5),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text('Testing',
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold)),
Padding(
padding:
EdgeInsets.only(bottom: 5)),
Text('More Text',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold))
],
))
],
)))),
SliverPersistentHeader(
delegate: _SliverAppBarDelegate(PreferredSize(
preferredSize:
Size(MediaQuery.of(context).size.width, 30),
child: TabBar(
labelColor: Colors.white,
tabs: tabs
.map((String name) => Container(
color: Colors.blue, child: Tab(text: name)))
.toList(),
))),
pinned: true)
];
},
body: TabBarView(
children: [_buildPanel1(), _buildPanel2(), _buildPanel3()],
),
),
));
}
_wrapTabWidget(tabWidget) {
return Builder(
builder: (BuildContext context) {
return CustomScrollView(
controller: _sliverScrollController,
slivers: <Widget>[
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.only(top: 10, left: 10, right: 10),
child: tabWidget))
],
);
},
);
}
_buildPanel1() {
return _wrapTabWidget(ListView(
shrinkWrap: true,
children: new List<Widget>.generate(
100,
(i) => ListTile(
leading: Icon(Icons.shopping_cart), title: Text('item $i')))));
}
_buildPanel2() {
return _wrapTabWidget(Text('Panel 2'));
}
_buildPanel3() {
return _wrapTabWidget(Text('Panel 3'));
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final PreferredSizeWidget _tabBar;
@override
double get minExtent => _tabBar.preferredSize.height;
@override
double get maxExtent => _tabBar.preferredSize.height;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
color: Colors.blue,
child: _tabBar,
);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
What I would really like to do is effectively apply the same "scroll controller" to the whole page and be able to scroll the list widget in tab1 and have that scroll the TabBar up and anchor it to the top and then continue to scroll within the ListView.
So, if I were to tap and hold here:
and then start to scroll, it would scroll the whole screen until the TabBar reaches the top and then pin the TabBar there and continue scrolling the ListView at the bottom.
I hope I'm explaining this well enough! :)
Upvotes: 0
Views: 455
Reputation: 628
Is this what you want?
class TestWidget extends StatefulWidget {
@override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget>
with SingleTickerProviderStateMixin {
bool isAbsorbing = false;
TabController _primaryTC;
final List<Tab> tabs = [
Tab(
text: "tab1",
),
Tab(
text: "tab2",
),
Tab(
text: "tab3",
)
];
@override
void initState() {
super.initState();
_primaryTC = TabController(length: tabs.length, vsync: this);
_primaryTC.index = tabs.length - 1;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return []..add(SliverAppBar(
flexibleSpace: FlexibleSpaceBar(
title: Text(""),
),
expandedHeight: 500,
pinned: true,
floating: true,
snap: false,
));
},
body: Container(
child: Column(
children: <Widget>[
TabBar(
tabs: tabs,
controller: _primaryTC,
),
Expanded(flex:1,child: TabBarView(
controller: _primaryTC,
children: tabs
.map((tab) => Container(
child: ListView.builder(
itemCount: 30,
itemBuilder: (context, index) {
return Container(height: 50,alignment: Alignment.center,child: Text("test"),);
}),
width: double.infinity,
height: double.infinity,
alignment: Alignment.center,
))
.toList()))
],
),
)),
),
);
}
}
Upvotes: 1