Scroll in TabBarView without affecting other tabs - flutter

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();
}

Related

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 sliver app bar with tab bar and grid view

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,
);
}
}```

Can I use flutter's TabBar for filtering a list of cards?

I have created a TabBar inside my AppBar with the code below. I'm curious if I can use this TabBar as a filtering mechanism like the image below. I feel like there might already be a widget available for this, but I can't find any evidence of that. If I do have to use the TabBar, how would I go about toggling each option and filtering based on this list of toggles.
Current code:
import 'package:flutter/material.dart';
class ExploreDetail extends StatefulWidget {
static const routeName = 'explore_detail';
#override
_ExploreDetailState createState() => _ExploreDetailState();
}
class _ExploreDetailState extends State<ExploreDetail>
with SingleTickerProviderStateMixin {
TabController _tabController;
List<Widget> tabs = [
Tab(
text: 'All',
),
Tab(
text: 'Experience Consulting',
),
Tab(
text: 'Front Office Transformation',
),
];
#override
void initState() {
// TODO: implement initState
super.initState();
// Create TabController for getting the index of current tab
_tabController = TabController(
length: tabs.length,
initialIndex: 0,
vsync: this,
);
}
#override
Widget build(BuildContext context) {
final Map category = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kTextTabBarHeight + kToolbarHeight),
child: AppBar(
title: Text(
category['title'],
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.white,
iconTheme: IconThemeData(
color: Colors.black,
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(kTextTabBarHeight),
child: Align(
alignment: Alignment.centerLeft,
child: TabBar(
tabs: tabs,
controller: _tabController,
indicatorColor: Colors.transparent,
labelColor: Colors.blue,
isScrollable: true,
unselectedLabelColor: Colors.grey,
),
),
),
),
),
body: Center(
child: Text('list of cards will go here'),
),
);
}
}
You can handle the tab selections by adding an addListener method to your tabController in your initState(). And then you can filter your data by the selected tab option.
It will look like:
#override
void initState() {
// TODO: implement initState
super.initState();
// Create TabController for getting the index of current tab
_tabController = TabController(
length: tabs.length,
initialIndex: 0,
vsync: this,
);
// Here is the addListener!
_tabController.addListener(_handleTabSelection);
}
And then:
void _handleTabSelection() {
if (_tabController.indexIsChanging) {
switch (_tabController.index) {
case 0:
filterData('all');
break;
case 1:
filterData('experienceConsulting');
break;
case 2:
filterData('frontOfficeTransformation');
break;
}
}
}

How to use TabController

I just learned flutter, I was confused how to use the TabController, I had followed what was described on the official website, but an error appeared, and I don't know how to fix it.
I just want to change the title and leading from the appbar when changing tabs.
final List<ChangeTitleAndLeading> _data = [
new ChangeTitleAndLeading(title: "Home", leading: Icon(Icons.home)),
new ChangeTitleAndLeading(title: "Profile", leading: Icon(Icons.person)),
new ChangeTitleAndLeading(title: "Friends", leading: Icon(Icons.people))
];
ChangeTitleAndLeading _handler;
TabController _controller;
#override
void initState() {
super.initState();
_checkEmailVerification();
_controller = TabController(vsync: this, length: 3);
_handler = _data[0];
_controller.addListener(_handleSelected);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleSelected() {
setState(() {
_handler = _data[_controller.index];
});
}
return MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.teal,
),
home: new Scaffold(
appBar: new AppBar(
leading: Icon(Icons.home),
title: new Text("Home"),
bottom: new TabBar(
controller: _controller,
tabs: _tabs,
),
),
body: TabBarView(
controller: _controller,
children: _pages,
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('Current Index: ${_handler.title}');
}
),
class ChangeTitleAndLeading {
final String title;
final Widget leading;
ChangeTitleAndLeading({
#required this.title,
#required this.leading
}) :
assert(title != null),
assert(leading != null);
}
Error log:
Error Log:
I/flutter (19638): No TabController for TabBarView.
I/flutter (19638): When creating a TabBarView, you must either provide an explicit TabController using the "controller"
I/flutter (19638): property or you must ensure that there is a DefaultTabController above the TabBarView.
I/flutter (19638): In this case, there was neither an explicit controller nor a default controller.
════════════════════════════════════════════════════════════════════════════════════════════════════
I/flutter (19638): Another exception was thrown: No TabController for TabBar.
And when i change this:
leading: Icon(Icons.home), to leading: _handler.leading,
and this:
title: new Text("Home"), to title: new Text(_handler.title),
always return error _handler.leading or _handler.title was null
Image
Try this solution :-
don't forget to inhert
TickerProviderStateMixin
class HomePage extends StatefulWidget {
const HomePage();
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
late TabController tabController;
#override
void initState() {
super.initState();
tabController = TabController(
initialIndex: 0,
length: 2,
vsync: this,
);
}
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Row(
children: [
Image.asset(
'assets/images/png/logo.png',
height: 60,
width: 60,
),
Spacer(),
Container(
width: 400,
child: TabBar(
labelColor: Color.fromRGBO(4, 2, 46, 1),
labelStyle: theme.textTheme.headline1,
indicatorColor: Color.fromRGBO(4, 2, 46, 1),
unselectedLabelColor: Colors.grey,
controller: tabController,
tabs: [
Text('الفاتورة'),
Text('دليفري'),
],
),
),
],
),
),
body: Container(
child: TabBarView(
controller: tabController,
children: [
Container(
color: Colors.red,
),
Container(
color: Colors.orange,
),
],
),
),
);
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
}
The issue is that you are missing a tabbarcontroller
Your code should be:
return MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.teal,
),
home: DefaultTabController(
length: 3,
child: new Scaffold(
appBar: new AppBar(
leading: Icon(Icons.home),
title: new Text("Home"),
bottom: new TabBar(
controller: _controller,
tabs: _tabs,
),
),
body: TabBarView(
controller: _controller,
children: _pages,
)...

Flutter - Change the animation of TabBarView

I implemented a basic TabBar and TabBarView with a DefaultTabController, see code below.
class MyApp2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: BOTTOM_TABS,
child: Scaffold(
appBar: AppBar(title: const Text('Bottom App Bar')),
body: _tabBarView(),
bottomNavigationBar: _bottomTabBar(),
),
);
}
_tabBarView() {
return TabBarView(
physics: NeverScrollableScrollPhysics(),
children: [
Container(
color: Colors.blue,
),
Container(
color: Colors.orange,
),
Container(
color: Colors.lightGreen,
),
Container(
color: Colors.red,
),
],
);
}
_bottomTabBar() {
return TabBar(
tabs: [
Tab(
icon: new Icon(Icons.home),
),
Tab(
icon: new Icon(Icons.public),
),
Tab(
icon: new Icon(Icons.group),
),
Tab(
icon: new Icon(Icons.person),
)
],
);
}
}
Works great! Now what I want to do is change the animation between the two tabs from the default animation. But I can't find an easy way to do that.
After a bit of research it seems like I need to use a custom TabController and somehow use its animateTo method. To me that seems like a pretty big change just to change the animation. What I wonder is if that is the correct way or if I am missing some easier way to just change the default animation between the tabviews?
If someone could give me some good resources to point me in the right direction I'd greatly appreciate it.
This is not hard, just use TabController (to do so you need to use SingleTickerProviderStateMixin ) and AnimatedBuilder.
class MyApp2 extends StatefulWidget {
#override
_MyApp2State createState() => _MyApp2State();
}
class _MyApp2State extends State<MyApp2> with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = TabController(length: 4, vsync: this);
super.initState();
}
_tabBarView() {
return AnimatedBuilder(
animation: _tabController.animation,
builder: (BuildContext context, snapshot) {
return Transform.rotate(
angle: _tabController.animation.value * pi,
child: [
Container(
color: Colors.blue,
),
Container(
color: Colors.orange,
),
Container(
color: Colors.lightGreen,
),
Container(
color: Colors.red,
),
][_tabController.animation.value.round()],
);
},
);
}
_bottomTabBar() {
return TabBar(
controller: _tabController,
labelColor: Colors.black,
tabs: [
Tab(
icon: new Icon(Icons.home),
),
Tab(
icon: new Icon(Icons.public),
),
Tab(
icon: new Icon(Icons.group),
),
Tab(
icon: new Icon(Icons.person),
)
],
);
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(title: const Text('Bottom App Bar')),
body: _tabBarView(),
bottomNavigationBar: _bottomTabBar(),
),
);
}
}
Screenshot (Null safe):
Code:
If you want fine-grained control, you can make use of the AnimationController.
class _MyPageState extends State<MyPage> with TickerProviderStateMixin {
late final TabController _tabController;
late final AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
value: 1,
);
_tabController = TabController(
length: 3,
vsync: this,
)..addListener(() {
if (_tabController.indexIsChanging) {
setState(() => _controller.forward(from: 0.5));
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ScaleTransition(
scale: _controller,
child: [
Container(color: Colors.red),
Container(color: Colors.green),
Container(color: Colors.blue),
][_tabController.index],
),
bottomNavigationBar: TabBar(
controller: _tabController,
tabs: [
Tab(child: Text('Red')),
Tab(child: Text('Green')),
Tab(child: Text('Blue')),
],
),
);
}
}
I don't know if you want to completely change the animation.
But if you just need some customization, did you try to use a TabController instead of a DefaultTabController ?
You just need to pass the tabController as an arg to the TabBar & TabBarView.
To customize the animation with the tabController, you should specify an Animation for the tabController and also specify the curve and duration with the animateTo function of the tabController.
https://api.flutter.dev/flutter/material/TabController/animateTo.html
https://api.flutter.dev/flutter/material/TabController-class.html
Disable animation between flutter tabs by setting animation duration to zero like this
tabController = TabController(
animationDuration: Duration.zero,
length: 4, vsync: this, initialIndex: 0);
Thank me later.