Flutter sliver app bar with tab bar and grid view - flutter

I want to use sliver app bar with tab bar and grid view.
I tried this code:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: Colors.white,
centerTitle: true,
pinned: true,
snap: false,
floating: true,
title: Image(
image: AssetImage('assets/images/appbar_logo.png'),
width: 152,
height: 42,
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(48),
child: TabBar(
isScrollable: true,
automaticIndicatorColorAdjustment: false,
indicatorSize: TabBarIndicatorSize.tab,
tabs: STR_TAB_TITLE_LIST.map((e) => Container(
padding: EdgeInsets.only(left: 4, right: 4),
child: Tab(text: e),
),
).toList(),
controller: _tabController,
),
),
),
];
},
body: TabBarView(
children: []..addAll(STR_TAB_TITLE_LIST.map((e) {
if (e == 'myWork') {
return MyWorkPage(e);
} else if (e == 'character') {
return CharactersPage(onCharacterPageItemSelected, e);
}
return TabPage(e);
})),
controller: _tabController,
),
),
),
);
}
And pages:
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topCenter,
child: GridView.builder(
shrinkWrap: true,
padding: EdgeInsets.all(4.0),
physics: BouncingScrollPhysics(),
itemCount: SAMPLE_CARD_LIST.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemBuilder: (context, index) {
return ItemCard(SAMPLE_CARD_LIST[index]);
},
),
);
}
The problems I faced are two
It does not save page's scroll position
like this: https://github.com/flutter/flutter/issues/40740
it cuts top of page when swipe to next tab
like this: Flutter TabBar and SliverAppBar that hides when you scroll down
I tried all suggestions above links but it did not worked
How can I fix this?

Use of code:
You can make your custom appbar. I think this code will help you.
appBar: CustomTab(
onDone: (tabNo) {
},
),)
Where we have used:
import 'package:filepath/md2indicator.dart';
class CustomTab extends StatefulWidget implements PreferredSizeWidget {
final void Function(int) onDone;
CustomTab({
Key? key,
required this.onDone,
}) : super(key: key);
#override
State<CustomTab> createState() => _CustomTabState();
#override
// TODO: implement preferredSize
final Size preferredSize = const Size.fromHeight(kToolbarHeight);
}
class _CustomTabState extends State<CustomTab>
with SingleTickerProviderStateMixin {
late TabController tabcontroller;
// final GlobalKey<ScaffoldState> _key = GlobalKey<ScaffoldState>();
#override
void initState() {
// TODO: implement initState
super.initState();
tabcontroller = TabController(length: 9, vsync: this);
tabcontroller.addListener(() {
setState(() {
widget.onDone(tabcontroller.index);
tabcontroller.animateTo(tabcontroller.index);
});
});
}
#override
void dispose() {
tabcontroller.dispose();
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: TabBar(
controller: tabcontroller,
labelStyle: TextStyle(
fontWeight: FontWeight.w700,
),
indicatorSize: TabBarIndicatorSize.label,
labelColor: Colors.green,
unselectedLabelColor: Colors.black,
isScrollable: true,
indicator: MD2Indicator(
indicatorHeight: 3,
indicatorColor: Colors.green.shade700,
indicatorSize: MD2IndicatorSize.full,
),
tabs: [
Tab(
text: 'Trending',
),
Tab(
text: 'Sports',
),
Tab(
text: 'Economy',
),
Tab(
text: 'Fashion',
),
Tab(
text: 'Entertainment',
),
Tab(
text: 'Technology',
),
Tab(
text: 'POLITICS',
),
Tab(
text: 'Viral',
),
Tab(
text: 'Videos',
)
],
),
);
}
}
And custom tab bar md2_tab_indicator style can be designed as:
import 'package:flutter/widgets.dart';
enum MD2IndicatorSize {
tiny,
normal,
full,
}
class MD2Indicator extends Decoration {
final double indicatorHeight;
final Color indicatorColor;
final MD2IndicatorSize indicatorSize;
const MD2Indicator({
required this.indicatorHeight,
required this.indicatorColor,
required this.indicatorSize,
});
#override
_MD2Painter createBoxPainter([VoidCallback? onChanged]) {
return _MD2Painter(this, onChanged);
}
}
class _MD2Painter extends BoxPainter {
final MD2Indicator decoration;
_MD2Painter(this.decoration, VoidCallback? onChanged) : super(onChanged);
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
assert(configuration.size != null);
Rect rect;
if (decoration.indicatorSize == MD2IndicatorSize.full) {
rect = Offset(
offset.dx,
configuration.size!.height - decoration.indicatorHeight,
) &
Size(configuration.size!.width, decoration.indicatorHeight);
} else if (decoration.indicatorSize == MD2IndicatorSize.tiny) {
rect = Offset(
offset.dx + configuration.size!.width / 2 - 8,
configuration.size!.height - decoration.indicatorHeight,
) &
Size(16, decoration.indicatorHeight);
} else {
rect = Offset(
offset.dx + 6,
configuration.size!.height - decoration.indicatorHeight,
) &
Size(configuration.size!.width - 12, decoration.indicatorHeight);
}
final Paint paint = Paint()
..color = decoration.indicatorColor
..style = PaintingStyle.fill;
canvas.drawRRect(
RRect.fromRectAndCorners(
rect,
topRight: Radius.circular(8),
topLeft: Radius.circular(8),
),
paint,
);
}
}```

Related

Rebuild screen in flutter TabBar

I use TabBar and its screens List of StatelessWidget
I want when go to one of those screens rebuild it again ( re call api ) because its data depend on lots of actions on the other screens , for now when I open it for the first time the api call and the screen is built but if I go back to it it shows the last data ... note that i use getx
example of one screen controller
class UnderReviewPostsController extends GetxController
with StateMixin<List<UnderReviewPost>> {
final DashboardApiProvider dashboardApiProvider;
UnderReviewPostsController({required this.dashboardApiProvider});
#override
void onInit() {
_getUnderReviewPosts();
super.onInit();
}
void _getUnderReviewPosts() async {
await dashboardApiProvider
.getUnderReviewPosts()
.then((value) {
change(value, status: RxStatus.success());
}, onError: (err) {
change(null, status: RxStatus.error(err.toString()));
});
}
}
TabBar Ui :
class UserPosts extends StatelessWidget {
UserPosts({super.key});
final GlobalKey<ScaffoldState> _key = GlobalKey();
final TabControllers _tabx = Get.find();
static final List<StatelessWidget> _views = [
ActivePostsItems(),
RenewablePostsItems(),
const UnderReviewPostsItems(),
const DisapprovedPostsItems(),
const MarketerPostsItems(),
];
#override
Widget build(BuildContext context) {
User user = Get.arguments;
return DefaultTabController(
length: 5,
child: Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
key: _key,
drawer: NavBar(
user: user,
),
appBar: AppBar(
leading: IconButton(
onPressed: () {
_key.currentState?.openDrawer();
},
icon: const Icon(
Icons.menu,
color: kBlackColor,
),
),
automaticallyImplyLeading: false,
bottom: TabBar(
controller: _tabx.tabController,
unselectedLabelColor: Colors.grey,
indicatorWeight: 10,
indicatorSize: TabBarIndicatorSize.tab,
indicatorPadding: const EdgeInsets.all(5),
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: kPrimaryColor,
),
isScrollable: true,
physics: const BouncingScrollPhysics(),
enableFeedback: true,
tabs: _tabx.tabs,
),
backgroundColor: kLightGrayColor,
),
body: TabBarView(
physics: const BouncingScrollPhysics(),
controller: _tabx.tabController,
children: _views,
),
),
),
);
}
}
thanks

Animate tab in Tab Bar when scroll/slide tabs

I'm using flutter_animator 3.2.0 to implement animation when tapped on Tab Bar. Everything is doing fine but when scroll/slide to next Tab. The animation wouldn't work and I've tried with tab controller listener as well as listening to tabcontroller animation, but it wouldn't trigger the immediate playing of animation like when the TabBar is selected.
Edit:
https://imgur.com/a/qNNEQ2g as shown in here, when I tap on the Tab Bar, it animates nicely, while when I scroll/slide to another Tab, it doesn't work properly & instead it animates also the previous Index's tab. I would like to achieve the same way the animation plays (on tap) when scroll/slide to another Tab.
import 'dart:ui' as ui;
import 'package:flutter_animator/flutter_animator.dart';
class Playground extends StatefulWidget {
#override
_PlaygroundState createState() => _PlaygroundState();
}
class _PlaygroundState extends State<Playground> with TickerProviderStateMixin {
late TabController _tabController;
late final List<GlobalKey<AnimatorWidgetState>> _animKeys;
#override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this)
..animation!.addListener(_animationStatus);
_animKeys = List.generate(
4,
(index) => GlobalKey<AnimatorWidgetState>(),
);
}
#override
void dispose() {
_animKeys.forEach((key) {
if (key.currentState != null) {
key.currentState!.dispose();
}
});
super.dispose();
}
void _animationStatus() {
if (_tabController.animation!.status == AnimationStatus.forward) {
print('Going forward');
_animKeys[_tabController.index].currentState!.forward();
}
}
#override
Widget build(BuildContext context) {
List<Widget> _tabs = List.generate(4, (index) {
return ClipRRect(
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 100,
maxHeight: 55,
),
child: Tada(
preferences: AnimationPreferences(
duration: Duration(milliseconds: 1000),
autoPlay: AnimationPlayStates.None,
),
key: _animKeys[index],
child: CustomPaint(
painter: TabPainter(
animation: _tabController.animation!,
index: index,
),
child: Tab(
child: Text(
'Tab $index',
style: TextStyle(color: Colors.black),
),
),
),
),
),
);
});
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
elevation: 0,
title: Text(
'Tab Bar',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400,
),
),
centerTitle: true,
bottom: TabBar(
onTap: (index) => _animKeys[index].currentState!.forward(),
controller: _tabController,
labelPadding: EdgeInsets.only(top: 5.0, bottom: 2.0),
indicatorColor: Colors.transparent,
tabs: _tabs,
),
),
body: TabBarView(
controller: _tabController,
children: List.generate(
4,
(index) => FittedBox(
child: Text('Tab $index'),
)),
),
);
}
}
class TabPainter extends CustomPainter {
final Animation<double> animation;
final int index;
final tabPaint = Paint();
TabPainter({
required this.animation,
required this.index,
});
#override
void paint(ui.Canvas canvas, ui.Size size) {
if ((animation.value - index).abs() < 1) {
final rect = Offset.zero & size;
canvas.clipRect(rect);
canvas.translate(size.width * (animation.value - index), 0);
final tabRect =
Alignment.bottomCenter.inscribe(Size(size.width, 3), rect);
canvas.drawRect(tabRect, tabPaint..color = Colors.black);
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

Flutter Listview swipe to change TabBar index

I have implemented TabBar without TabBarView. I am using a single ListView as body since the layout after selecting a tab is same for all tabs.
What I want to achieve is, change the tab while swiping left / right in the listview. How can I do this?
TabBar
TabBar(
indicatorWeight: 3,
indicatorSize: TabBarIndicatorSize.label,
onTap: (index) {
categoryId = newsProvider.categories[index].id;
page = 1;
fetchPosts(newsProvider);
},
isScrollable: true,
tabs: [
for (Category category in newsProvider.categories)
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: Text(
category.name,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
),
],
),
body
ListView.builder(
padding: EdgeInsets.only(bottom: 60),
physics: BouncingScrollPhysics(),
controller: _scrollController,
itemCount: newsProvider.posts.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
HabaruDetails(newsProvider.posts[index]),
));
},
child: Container(
height: 200,
margin: EdgeInsets.only(left: 10, right: 10, top: 10),
child: ClipRRect(
borderRadius: BorderRadius.circular(7),
child: Stack(
children: [
Positioned.fill(
child: Hero(
tag: newsProvider.posts[index].id,
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
fit: BoxFit.cover,
image: newsProvider
.posts[index].betterFeaturedImage.mediumLarge),
),
),
Container(
height: 200,
decoration: BoxDecoration(
color: Colors.white,
gradient: LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
Colors.black.withOpacity(0.0),
Colors.black.withOpacity(0.95),
],
stops: [
0.0,
1.0
])),
),
Positioned.fill(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 30),
child: Text(
newsProvider.posts[index].title.rendered,
textAlign: TextAlign.center,
textDirection: TextDirection.rtl,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
height: 1.7,
),
),
),
),
),
],
),
),
),
);
},
)
I think you still must use a TabBarView, but you can generate its children dynamically based on the categories list like below.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [...],
),
),
body: TabBarView(
children: newsProvider.categories.map(
(e) => ListView.builder(...).toList(),
),
),
),
),
);
}
From what I can tell, I think you're trying to create a tab bar that moves when the user scrolls down on the listview? If so, I have created an example of a class that uses a selection of tabs you can implement as a TabBar for your title in your AppBar whose controller is set to the TabBarController I have created above the build method. The listview is then set to the controller of the scrollcontroller which listens to the state of the tabbarcontroller.
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,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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 TickerProviderStateMixin {
TabController _tabController;
ScrollController _scrollController;
void _scrollListener() {
var index = (_scrollController.offset / 70).round();
if(index >= choices.length){
index = choices.lastIndexOf(choices.last);
}
if(index <= 0){
index = 0;
}
if (index == choices.length)
index = choices.length;
if(mounted){
setState(() {
_tabController.animateTo(index, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
});
}
}
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: choices.length);
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
_scrollController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.black45,
labelStyle: TextStyle(fontWeight: FontWeight.w700, fontFamily: 'Valera'),
controller: _tabController,
isScrollable: true,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: Colors.transparent),
onTap: (int index){
setState(() {
_scrollController.animateTo(index*80.0, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
});
},
tabs: choices.map((Choice choice) {
return Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Tab(
text: choice.title,
),
);
}).toList(),
),
),
body: Container(
child: ListView.builder(
controller: _scrollController,
itemBuilder: (context, index){
return ListTile(
);
}),
),
);
}
}
class Choice {
const Choice({this.title});
final String title;
}
const List<Choice> choices = const <Choice>[
const Choice(title: 'First Tab'),
const Choice(title: 'Second Tab'),
const Choice(title: 'Third Tab'),
const Choice(title: 'Fourth Tab'),
const Choice(title: 'Fifth Tab'),
const Choice(title: 'Sixth Tab'),
];
class ChoiceCard extends StatelessWidget {
const ChoiceCard({Key key, this.choice}) : super(key: key);
final Choice choice;
#override
Widget build(BuildContext context) {
return Text(choice.title, style: TextStyle(
color: Colors.black,
),
);
}
}
You can do this by TabController and Listener
First you can define a TabController. It need to be in StatefulWidget.
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: tabLenght, vsync: this);
}
TabBar(
...
controller: _tabController,
...
)
Then you can add Listener class to the ListView. Save the clicked position and swiping distance in onPointerDown,onPointerMove,onPointerUp and
onPointerCancel (same as onPointerUp).
Change the tab by control the offset and index. Notice offset is in range (-1.0,1.0). You should animate by yourself if it reach the next index.
double startX;
double sensitivity = 0.01;
...
child: Listener(
onPointerDown: (event) {
startX = event.position.dx;
},
onPointerMove: (event) {
double newX = event.position.dx;
double offset = ((startX - newX) * sensitivity).clamp(-1.0, 1.0);
if (!_tabController.indexIsChanging)
_tabController.offset = offset;
if (offset == 1.0 &&
_tabController.index < _tabController.length - 1) {
_tabController.animateTo(_tabController.index + 1);
startX = newX;
}
if (offset == -1.0 && _tabController.index > 0) {
_tabController.animateTo(_tabController.index - 1);
startX = newX;
}
},
onPointerUp: (_) {
if (_tabController.offset > 0.5 &&
_tabController.index < _tabController.length - 1)
_tabController.animateTo(_tabController.index + 1);
else if (_tabController.offset < -0.5 && _tabController.index > 0)
_tabController.animateTo(_tabController.index - 1);
else {
if (!_tabController.indexIsChanging)
_tabController.offset = 0;
}
},
onPointerCancel: (_) {
if (_tabController.offset > 0.5 &&
_tabController.index < _tabController.length - 1)
_tabController.animateTo(_tabController.index + 1);
else if (_tabController.offset < -0.5 && _tabController.index > 0)
_tabController.animateTo(_tabController.index - 1);
else {
if (!_tabController.indexIsChanging)
_tabController.offset = 0;
}
},
child: ListView.builder(
...
I think you want the drag effect like TabBarView? You can also check the source code inside TabBarView.

Flutter - How to make a row to stay at the top of screen when scrolled

Iam trying to implement a appbar like this
When scrolling down I need to hide the search bar alone and pin the row and the tabs on the device top. Which is like
And when we scroll down the all the three rows needs to be displayed.
Using SliverAppBar with bottom property tabs are placed and pinned when scrolling, but a row above it should be pinned at the top above the tabbar. Im not able to add a column with the row and tabbar because of preferedSizeWidget in bottom property. Flexible space bar also hides with the appbar so I cannot use it. Does anyone know how to make this layout in flutter.
Please try this.
body: Container(
child: Column(
children: <Widget>[
Container(
// Here will be your AppBar/Any Widget.
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
// All your scroll views
Container(),
Container(),
],
),
),
),
],
),
),
You could create your own SliverAppBar or you can divide them in 2 items, a SliverAppBar and a SliverPersistentHeader
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home>
with SingleTickerProviderStateMixin {
TabController controller;
TextEditingController textController = TextEditingController();
#override
void initState() {
super.initState();
controller = TabController(
length: 3,
vsync: this,
);
}
#override
void dispose(){
super.dispose();
controller.dispose();
textController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
leading: const Icon(Icons.menu),
title: TextField(
controller: textController,
textInputAction: TextInputAction.search,
decoration: InputDecoration(
isDense: true,
hintText: 'Search Bar',
hintStyle: TextStyle(color: Colors.black.withOpacity(.5), fontSize: 16),
border: InputBorder.none
)
),
snap: true,
floating: true,
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () => print('searching for: ${textController.text}'),
)
]
),
//This is Where you create the row and your tabBar
SliverPersistentHeader(
delegate: MyHeader(
top: Row(
children: [
for(int i = 0; i < 4; i++)
Expanded(
child: OutlineButton(
child: Text('button $i'),
onPressed: () => print('button $i pressed'),
)
)
]
),
bottom: TabBar(
indicatorColor: Colors.white,
tabs: [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
controller: controller,
),
),
pinned: true,
),
SliverFillRemaining(
child: TabBarView(
controller: controller,
children: <Widget>[
Center(child: Text("Tab one")),
Center(child: Text("Tab two")),
Center(child: Text("Tab three")),
],
),
),
],
),
);
}
}
//Your class should extend SliverPersistentHeaderDelegate to use
class MyHeader extends SliverPersistentHeaderDelegate {
final TabBar bottom;
final Widget top;
MyHeader({this.bottom, this.top});
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Theme.of(context).accentColor,
height: math.max(minExtent, maxExtent - shrinkOffset),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if(top != null)
SizedBox(
height: kToolbarHeight,
child: top
),
if(bottom != null)
bottom
]
)
);
}
/*
kToolbarHeight = 56.0, you override the max and min extent with the height of a
normal toolBar plus the height of the tabBar.preferredSize
so you can fit your row and your tabBar, you give them the same value so it
shouldn't shrink when scrolling
*/
#override
double get maxExtent => kToolbarHeight + bottom.preferredSize.height;
#override
double get minExtent => kToolbarHeight + bottom.preferredSize.height;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => false;
}
UPDATE
A NestedScollView let you have 2 ScrollViews so you can control the inner scroll with the outer (just like you want with a TabBar)
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
TextEditingController textController = TextEditingController();
List<String> _tabs = ['Tab 1', 'Tab 2', 'Tab 3'];
// Your tabs, or you can ignore this and build your list
// on TabBar and the TabView like my previous example.
// I don't create a TabController now because I wrap the whole widget with a DefaultTabController
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
textController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
elevation: 0.0,
leading: const Icon(Icons.menu),
title: TextField(
controller: textController,
textInputAction: TextInputAction.search,
decoration: InputDecoration(
isDense: true,
hintText: 'Search Bar',
hintStyle: TextStyle(
color: Colors.black.withOpacity(.5),
fontSize: 16),
border: InputBorder.none)
),
snap: true,
floating: true,
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () => print('searching for: ${textController.text}'),
)
]
),
),
SliverPersistentHeader(
delegate: MyHeader(
top: Row(children: [
for (int i = 0; i < 4; i++)
Expanded(
child: OutlineButton(
child: Text('button $i'),
onPressed: () => print('button $i pressed'),
))
]),
bottom: TabBar(
indicatorColor: Colors.white,
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
pinned: true,
),
];
},
body: TabBarView(
children: _tabs.map((String name) {
return SafeArea(
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
// You can ignore it if you're going to build your
// widgets in another Stateless/Stateful class.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber
// above.
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'),
onTap: () => print('$name at index $index'),
);
},
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
));
}
}
import 'dart:io';
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(
primaryColor: Colors.white,
),
home: NewsScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class NewsScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _NewsScreenState();
}
class _NewsScreenState extends State<NewsScreen> {
final List<String> _tabs = <String>[
"Featured",
"Popular",
"Latest",
];
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: DefaultTabController(
length: _tabs.length,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverSafeArea(
top: false,
bottom: Platform.isIOS ? false : true,
sliver: SliverAppBar(
title: Text('Tab Demo'),
elevation: 0.0,
floating: true,
pinned: true,
snap: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
),
];
},
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
),
);
}
}

Scroll in TabBarView without affecting other tabs

I was trying to scroll in one tab without affecting the other tabs scroll position. But I don't how to implement it. Also in this code, I added a SliverAppBar to scroll up when you tap the App bar.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with TickerProviderStateMixin {
TabController _tabController;
ScrollController _scrollViewController;
double scrollPosition;
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: 2, initialIndex: 0);
_scrollViewController = ScrollController();
}
#override
void dispose() {
_tabController.dispose();
_scrollViewController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: DefaultTabController(
length: 2,
initialIndex: 0,
child: Scaffold(
body: NestedScrollView(
controller: _scrollViewController,
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: InkWell(
onTap: () {
_scrollViewController.animateTo(
0.0,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 300),
);
},
child: const Text(kCandice, style: kLogoText)),
centerTitle: true,
elevation: 0,
backgroundColor: Colors.white,
pinned: true,
floating: true,
snap: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: <Tab>[
Tab(
text:
AppLocalizations.of(context).translate('following'),
),
Tab(
text:
AppLocalizations.of(context).translate('trending'),
)
],
unselectedLabelColor: Colors.black54,
labelColor: kPink,
unselectedLabelStyle: kMediumBoldText,
labelStyle: kMediumBoldText,
indicatorSize: TabBarIndicatorSize.tab,
indicatorColor: Colors.transparent,
controller: _tabController,
),
),
];
},
body: TabBarView(
children: [
PostSection(),
PostSection(),
],
),
),
),
),
);
}
}
I don't if it's the correct way, but one option is:
#override
void initState() {
_scrollViewController = ScrollController();
_tabController = TabController(vsync: this, length: 2, initialIndex: 0)
..addListener(() => _scrollViewController.animateTo(
0.0,
curve: Curves.easeIn,
duration: const Duration(milliseconds: 300),
));
super.initState();
}