I'm trying to get a different floatting button in a TabBar in flutter. But I will try a lot of option, but I don't know how.
Sorry, I add more details:
I want to do a app with a TabBar, like this flutter example.
If you see this is a tabBarDemo application, I can change between tabs,
but I don't know how to change the floating button between tabs. Thanks
Like this gif: https://i.stack.imgur.com/bxtN4.gif
class TabBarDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: Text('Tabs Demo'),
),
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
floatingActionButton: FloatingActionButton.extended
(onPressed: null,
icon: Icon(Icons.add, color: Colors.white,),
label: new Text('FLOATING TO CHANGE'),
),
floatingActionButtonLocation:FloatingActionButtonLocation.centerFloat,
),
),
);
}
}
A Minimal Example of what you want:
class TabsDemo extends StatefulWidget {
#override
_TabsDemoState createState() => _TabsDemoState();
}
class _TabsDemoState extends State<TabsDemo>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this, initialIndex: 0);
_tabController.addListener(_handleTabIndex);
}
#override
void dispose() {
_tabController.removeListener(_handleTabIndex);
_tabController.dispose();
super.dispose();
}
void _handleTabIndex() {
setState(() {});
}
#override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: Scaffold(
appBar: AppBar(
title: Text('Demo'),
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(
text: "Tab1",
),
Tab(
text: "Tab2",
),
],
),
), // floatingActionButton: _buildFloatingActionButton(context),
body: TabBarView(controller: _tabController, children: [
Center(
child: Container(
child: Text('Tab 1'),
),
),
Center(
child: Container(
child: Text('Tab 2'),
),
),
]),
floatingActionButton: _bottomButtons(),
),
);
}
Widget _bottomButtons() {
return _tabController.index == 0
? FloatingActionButton(
shape: StadiumBorder(),
onPressed: null,
backgroundColor: Colors.redAccent,
child: Icon(
Icons.message,
size: 20.0,
))
: FloatingActionButton(
shape: StadiumBorder(),
onPressed: null,
backgroundColor: Colors.redAccent,
child: Icon(
Icons.edit,
size: 20.0,
),
);
}
}
you can achieve this by TabController
Declaration: TabController _tabController;
Initialization: in initState()
_tabController = TabController(length: 2, vsync: this, initialIndex: 0);
_tabController.addListener(_handleTabChange);
and just pass setState((){}) in method _handleTabChange to reflect ontime like
_handleTabChange(){
setState((){});
}
Now Bind or Inject in both of widget TabBar and TabBarView in their controller property.
TabBarView(
controller: _tabController,
children: [
Widget(),
Widget()
],
),
TabBar(
controller: _tabController,
tabs:[
Tab(...),
Tab(...),
]
)
Now place your different FAB button to different Tabs by according to _tabController index
floatingActionButton: _tabController.index == 0
? FloatingActionButton(
backgroundColor: Colors.blue,
onPressed: () {},
)
: FloatingActionButton(
backgroundColor: Colors.red,
onPressed: () {},
),
Keep coding ;)
Check this
import 'package:flutter/material.dart';
class Lista extends StatefulWidget {
#override
_ListaState createState() => _ListaState();
}
class _ListaState extends State<Lista> {
int indexTab=0;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
initialIndex: 0,
child: Scaffold(
appBar: AppBar (
title: Text("Test"),
bottom: TabBar(
onTap: (index){
setState(() {
indexTab = index;
});
},
tabs: <Widget>[
Tab(icon: Icon(Icons.calendar_today)),
Tab(icon: Icon(Icons.whatshot)),
],
),
),
floatingActionButton: indexTab==0? FloatingActionButton (
onPressed: () {},
child: Icon(Icons.add),
):FloatingActionButton (
onPressed: () {},
child: Text('test'),
),
body: TabBarView(
children: <Widget>[
Text('1'),
Text('2'),
],
)
),
);
}
}
I found that the accepted answer was not providing a good enough solution for me. The problem is that animation feels laggy and untimely.
The main point of change is listening to Animation of TabController instead of TabController state.
There is my approach to create a more or less reusable solution:
class MultipleHidableFabs extends StatefulWidget {
#override
State<MultipleHidableFabs> createState() => _MultipleHidableFabsState();
}
class _MultipleHidableFabsState extends State<MultipleHidableFabs>
with SingleTickerProviderStateMixin {
// Index of initially opened tab
static const initialIndex = 0;
// Number of tabs
static const tabsCount = 3;
// List with current scales for each tab's fab
// Initialize with 1.0 for initial opened tab, 0.0 for others
final tabScales =
List.generate(tabsCount, (index) => index == initialIndex ? 1.0 : 0.0);
late TabController tabController;
#override
void initState() {
super.initState();
tabController = TabController(
length: tabsCount,
initialIndex: initialIndex,
vsync: this,
);
// Adding listener to animation gives us opportunity to track changes more
// frequently compared to listener of TabController itself
tabController.animation!.addListener(() {
setState(() {
// Current animation value. It ranges from 0 to (tabsCount - 1)
final animationValue = tabController.animation!.value;
// Simple rounding gives us understanding of what tab is showing
final currentTabIndex = animationValue.round();
// currentOffset equals 0 when tabs are not swiped
// currentOffset ranges from -0.5 to 0.5
final currentOffset = currentTabIndex - animationValue;
for (int i = 0; i < tabsCount; i++) {
if (i == currentTabIndex) {
// For current tab bringing currentOffset to range from 0.0 to 1.0
tabScales[i] = (0.5 - currentOffset.abs()) / 0.5;
} else {
// For other tabs setting scale to 0.0
tabScales[i] = 0.0;
}
}
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: tabController,
tabs: [
Tab(icon: Icon(Icons.one_k)),
Tab(icon: Icon(Icons.two_k)),
Tab(icon: Icon(Icons.three_k)),
],
),
),
body: SafeArea(
child: TabBarView(
controller: tabController,
children: [Icon(Icons.one_k), Icon(Icons.two_k), Icon(Icons.three_k)],
),
),
floatingActionButton: createScaledFab(),
);
}
Widget? createScaledFab() {
// Searching for index of a tab with not 0.0 scale
final indexOfCurrentFab = tabScales.indexWhere((fabScale) => fabScale != 0);
// If there are no fabs with non-zero opacity return nothing
if (indexOfCurrentFab == -1) {
return null;
}
// Creating fab for current index
final fab = createFab(indexOfCurrentFab);
// If no fab created return nothing
if (fab == null) {
return null;
}
final currentFabScale = tabScales[indexOfCurrentFab];
// Scale created fab with
// You can use different Widgets to create different effects of switching
// fabs. E.g. you can use Opacity widget or Transform.translate to create
// custom animation effects
return Transform.scale(scale: currentFabScale, child: fab);
}
// Create fab for provided index
// You can skip creating fab for any indexes you want
Widget? createFab(final int index) {
if (index == 0) {
return FloatingActionButton(
onPressed: () => print("On first fab clicked"),
child: Icon(Icons.one_k),
);
}
// Not created fab for 1 index deliberately
if (index == 2) {
return FloatingActionButton(
onPressed: () => print("On third fab clicked"),
child: Icon(Icons.three_k),
);
}
}
}
Advantages of this approach:
Synchronized animation between swiping and showing fabs
Tapping on tabs also animates in a right manner
Ability to easily skip creating fabs for selected indexes
See an example in action:
Based on this answer from Ilia Kurtov, here's a reusable component for tab-dependent FABs.
Implementation
import 'package:flutter/material.dart';
typedef FabBuilder = Widget? Function(int tabIndex);
typedef TransformBuilder = Widget Function(
BuildContext context, Widget child, double t);
The basic idea is to transform the animation from the tab controller to an index and distance (TabFocus class) using a custom subclass of Animatable.
/// Represent a tab index with a distance metric.
class TabFocus {
/// Distance to the tab
///
/// from 0.0 (on tab) to 1.0 (half way to next or previous tab)
final double distance;
/// Index of the tab that closest to the current `t`.
final int index;
const TabFocus._({required this.distance, required this.index});
/// Get the tab focus at a tab position
factory TabFocus.at(double t) {
final index = t.round();
final t0 = index.toDouble();
final distance = (t - t0).abs() * 2;
return TabFocus._(distance: distance, index: index);
}
}
/// Subclass of [Animatable] that transforms a `double t` tab position into a [TabFocus].
class TabFocusAnimatable extends Animatable<TabFocus> {
#override
TabFocus transform(double t) => TabFocus.at(t);
const TabFocusAnimatable();
}
When we create our widget, we turn this Animatable<TabFocus> into an Animation<TabFocus> by attaching it to the TabController.animation
/// A tab-dependent FAB based on <https://stackoverflow.com/a/71123870/4087068>
class TabbedFab extends StatefulWidget {
TabbedFab(
{Key? key,
required TabController tabController,
FabBuilder? builder,
Animatable<TabFocus> focusAnimatable = const TabFocusAnimatable(),
TransformBuilder? transformBuilder})
: this._(
key: key,
builder: builder,
tabController: tabController,
fabAnimation: focusAnimatable.animate(tabController.animation!));
const TabbedFab._(
{Key? key,
required this.tabController,
required this.fabAnimation,
this.transform = _defaultTransform,
this.builder})
: super(key: key);
final TransformBuilder transform;
final Animation<TabFocus> fabAnimation;
final TabController tabController;
final FabBuilder? builder;
#override
State<TabbedFab> createState() => _TabbedFabState();
}
We also define a default transformation, that just scales a widget (the FAB) based on a t from 0.0 to 1.0.
/// By default, scale the current floating action button, so that it is full
/// size when the tab is selected
Widget _defaultTransform(BuildContext context, Widget child, double t) {
return Transform.scale(scale: t, child: child);
}
In the widget state class, we listen to the fabAnimation and call setState only when the index changes.
class _TabbedFabState extends State<TabbedFab> {
int currentTab = 0;
_onTabAnimation() {
final animationIndex = widget.fabAnimation.value.index;
if (animationIndex != currentTab) {
setState(() {
currentTab = animationIndex;
});
}
}
#override
void dispose() {
widget.fabAnimation.removeListener(_onTabAnimation);
super.dispose();
}
#override
void initState() {
currentTab = widget.tabController.index;
widget.fabAnimation.addListener(_onTabAnimation);
super.initState();
}
/* build method, see below */
}
Finally, we use an AnimatedBuilder with our transform to scale the widget while the animation is running.
#override
Widget build(BuildContext context) {
// Creating fab for current index
final fab = widget.builder?.call(currentTab);
// If no fab created return nothing
if (fab == null) {
return const SizedBox.shrink();
}
return AnimatedBuilder(
animation: widget.fabAnimation,
builder: (context, child) {
// fall back to 0.0 if the animation rolled over, but we're still calling the old builder
final t = (currentTab == widget.fabAnimation.value.index)
? 1.0 - widget.fabAnimation.value.distance
: 0.0;
return widget.transform(context, child!, t);
},
child: fab,
);
}
Usage
To use it, add it to your Scaffold like so:
Widget? _createFab(index) {
return (index == 0)
? FloatingActionButton(
onPressed: () => print("Click!"),
child: const Icon(Icons.add),
)
: null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabController,
tabs: const <Tab>[
Tab(text: "Tab 1"),
Tab(text: "Tab 2")
],
)),
body: TabBarView(
controller: _tabController,
children: const <Widget>[
Center(child: Text("Tab 1")),
Center(child: Text("Tab 2")),
]),
floatingActionButton:
TabbedFab(tabController: _tabController, builder: _createFab));
}
you can use this code :
floatingActionButton: new Container(
height: 140.0,
child: new Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomRight,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Container(
height: 60.0,
child: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
),
new Container(
height: 20.0,
), // a space
Container(
height: 60.0,
child: new FloatingActionButton(
onPressed: _decremenrCounter,
backgroundColor: Colors.red,
tooltip: 'Increment',
child: new Icon(Icons.remove),
),
),
],
),
)
],
),
)
screenshot :
here is all the code if you want it : main.dart
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void _decremenrCounter() {
setState(() {
_counter--;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new Container(
height: 140.0,
child: new Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomRight,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Container(
height: 60.0,
child: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
),
new Container(
height: 20.0,
), // a space
Container(
height: 60.0,
child: new FloatingActionButton(
onPressed: _decremenrCounter,
backgroundColor: Colors.red,
tooltip: 'Increment',
child: new Icon(Icons.remove),
),
),
],
),
)
],
),
) // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Related
I have got a DefaultTabController with a couple of tabs that the user can swipe or press an ElevatedButton to proceed to the next slide, my issue is that I don't know how to change the button's label when the user reaches the last tab using swipes.
Using a stateful widget I managed to change the label when the user presses the button but it doesn't work if the user swipes. Is it possible to change the button when the user reaches the last tab?
class SlidesWidget extends StatelessWidget {
static List<Slide> slides = [
const Slide(
text: 'Welcome to ..'),
const Slide(
text: 'Ready to discover your city?')
];
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: slides.length,
child: Builder( // Builder here, otherwise `DefaultTabController.of(context)` returns null.
builder: (BuildContext context) => Padding(
padding: const EdgeInsets.all(8.0),
child: SafeArea(
child: Column(
children: [
const TabPageSelector(
selectedColor: Colors.white,
),
Expanded(
flex: 100,
child: TabBarView(
children: slides,
),
),
Padding(
padding: const EdgeInsets.all(18.0),
child: ElevatedButton(
onPressed: () {
final TabController controller =
DefaultTabController.of(context)!;
if (!controller.indexIsChanging &&
controller.index < slides.length - 1) {
// Go to next slide if exists
controller.index++;
}
},
child: Text('Next'), // <== on last slide should change label and do other things
),
)
],
),
),
),
),
);
}
}
I will recommend using StatefulWidget, also you can use inline StatefulBuilder to update the UI. And using TabController is handy instead of calling it multiple times, and there is risk of getting null for DefaultTabController.
class SlidesWidget extends StatefulWidget {
SlidesWidget({Key? key}) : super(key: key);
#override
State<SlidesWidget> createState() => _SlidesWidgetState();
}
class _SlidesWidgetState extends State<SlidesWidget>
with SingleTickerProviderStateMixin {
late TabController controller;
List<Slide> slides = [
const Slide(text: 'Welcome to ..'),
const Slide(text: 'Ready to discover your city?')
];
#override
void initState() {
super.initState();
controller = TabController(length: slides.length, vsync: this)
..addListener(() {
setState(() {});
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: SafeArea(
child: Column(
children: [
TabPageSelector(
controller: controller,
selectedColor: Colors.white,
),
Expanded(
flex: 100,
child: TabBarView(
controller: controller,
children: slides,
),
),
Padding(
padding: const EdgeInsets.all(18.0),
child: ElevatedButton(
onPressed: () {
if (!controller.indexIsChanging &&
controller.index < slides.length - 1) {
// Go to next slide if exists
controller.index++;
}
},
child: Text(controller.index == 1 ? 'start' : "Next"), //
),
)
],
),
),
));
}
}
More about TabController and I think you will also like IndexedStack for this case.
I use a default Tabbar. I have two tab .When I change tabview by clicking, onTab method call finely. But when I change tabview by swiping or scrolling, how I can call onTab method?. How I can listen my onTab changing value when I change my tabview by swiping or scrolling? I need change tabIndex value in controller when I change tabView by swiping or scroling.
UI Part here
#override
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: 0,
length: 2,
child: Scaffold(
appBar: AppBar(
backgroundColor: AllColors.deepPurple,
leading: InkWell(
onTap: () => Get.back(),
child: Icon(
Icons.arrow_back,
color: AllColors.whiteColor,
),
),
elevation: 0.0,
title: Text(
"Categories",
style: AllStyles.titleTextStyle,
),
actions: [
InkWell(
child: Padding(
padding: const EdgeInsets.only(right: 12.0),
child: Icon(Icons.add),
),
onTap: () {
},
)
],
bottom: TabBar(
controller: categoriesController.tabController,
onTap: (value) {
categoriesController.changeTabValue(value);
print("Value " + value.toString());
},
isScrollable: false,
indicatorColor: AllColors.whiteColor,
indicatorSize: TabBarIndicatorSize.label,
tabs: [Tab(text: "Income"), Tab(text: "Expense")],
),
),
body: TabBarView(
children: [
IncoomeTabCategories(),
ExpenseTabCategories()
],
),
),
);
}
Controller part here:
class CategoriesController extends GetxController with GetSingleTickerProviderStateMixin {
TabController? tabController;
int tabIndex=0;
#override
void onInit() {
super.onInit();
tabController = TabController(length: 2, vsync: this,initialIndex: 0)
}
#override
void dispose() {
super.dispose();
tabController!.dispose();
}
void changeTabValue(int index){
tabIndex=index;
update();
}
}
I am trying to achieve an effect where there is expandable content on the top end of a sidebar, and other links on the bottom of the sidebar. When the content on the top expands to the point it needs to scroll, the bottom links should scroll in the same view.
Here is an example of what I am trying to do, except that it does not scroll. If I wrap a scrollable view around the column, that won't work with the spacer or expanded that is needed to keep the bottom links on bottom:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() {
return MyWidgetState();
}
}
class MyWidgetState extends State<MyWidget> {
List<int> items = [1];
#override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
items.add(items.last + 1);
});
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
setState(() {
if (items.length != 1) items.removeLast();
});
},
),
],
),
for (final item in items)
MyAnimatedWidget(
child: SizedBox(
height: 200,
child: Center(
child: Text('Top content item $item'),
),
),
),
Spacer(),
Container(
alignment: Alignment.center,
decoration: BoxDecoration(border: Border.all()),
height: 200,
child: Text('Bottom content'),
)
],
);
}
}
class MyAnimatedWidget extends StatefulWidget {
final Widget? child;
const MyAnimatedWidget({this.child, Key? key}) : super(key: key);
#override
State<MyAnimatedWidget> createState() {
return MyAnimatedWidgetState();
}
}
class MyAnimatedWidgetState extends State<MyAnimatedWidget>
with SingleTickerProviderStateMixin {
late AnimationController controller;
#override
initState() {
controller = AnimationController(
value: 0, duration: const Duration(seconds: 1), vsync: this);
controller.animateTo(1, curve: Curves.linear);
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
return SizedBox(height: 200 * controller.value, child: widget.child);
});
}
}
I have tried using a global key to get the size of the spacer and detect after rebuilds whether the spacer has been sized to 0, and if so, re-build the entire widget as a list view (without the spacer) instead of a column. You also need to listen in that case for if the size shrinks and it needs to become a column again, it seemed to make the performance noticeably worse, it was tricky to save the state when switching between column/listview, and it seemed not the best way to solve the problem.
Any ideas?
Try implementing this solution I've just created without the animation you have. Is a scrollable area at the top and a persistent footer.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
home: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("My AppBar"),
),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
// Your scrollable widgets here
Container(
height: 100,
color: Colors.green,
),
Container(
height: 100,
color: Colors.blue,
),
Container(
height: 100,
color: Colors.red,
),
],
),
),
),
Container(
child: Text(
'Your footer',
),
color: Colors.blueGrey,
height: 200,
width: double.infinity,
)
],
),
),
),
);
}
}
I am using a combination of "BottomNavigationBar" and "PageView" for navigation in my app. The user can either swipe to the next page or use the navigation bar.
On one of my pages, I would like to use a gesture detector that handles pan gestures, both vertically and horizontally.
I can't find a way to override the PageView's gesture detection with the nested GestureDetector. This means only vertical pan gestures are handled, as the horizontal ones are occupied by the PageView.
How can I disable / override the PageViews gesture detection for only that page or only the widget, without completely disabling the PageViews scroll physics?
I have created a simplified version of my App to isolate the issue, and attached a video of the problem below.
Any help would be greatly appreciated!
Here is the code inside my main.dart:
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: GestureIssueExample(),
);
}
}
class GestureIssueExample extends StatefulWidget {
GestureIssueExample({Key key}) : super(key: key);
#override
_GestureIssueExampleState createState() => _GestureIssueExampleState();
}
class _GestureIssueExampleState extends State<GestureIssueExample> {
int _navigationIndex;
double _xLocalValue;
double _yLocalValue;
PageController _pageController;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
bottomNavigationBar: _buildBottomNavigationBar(),
backgroundColor: Colors.white,
body: PageView(
controller: _pageController,
onPageChanged: _onNavigationPageChanged,
children: [
//Just a placeholder to represent a page to the left of the "swipe cards" widget
_buildSamplePage("Home"),
//Center child of 'PageView', contains a GestureDetector that handles Pan Gestures
//Thanks to the page view however, only vertical pan gestures are detected, while both horizontal and vertical gestures
//need to be handled...
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Local X: ${_xLocalValue.toString()}\nLocal Y: ${_yLocalValue.toString()}"),
GestureDetector(
onPanStart: (details) => setState(
() {
this._xLocalValue = details.localPosition.dx;
this._yLocalValue = details.localPosition.dy;
},
),
onPanUpdate: (details) => setState(
() {
this._xLocalValue = details.localPosition.dx;
this._yLocalValue = details.localPosition.dy;
},
),
child: Container(
width: MediaQuery.of(context).size.width * 0.9,
height: 100.0,
color: Colors.red,
alignment: Alignment.center,
child: Text("Slidable Surface",
style: TextStyle(color: Colors.white)),
),
),
],
),
),
//Just a placeholder to represent a page to the right of the "swipe cards" widget
_buildSamplePage("Settings"),
],
),
);
}
#override
void initState() {
super.initState();
this._navigationIndex = 0;
this._pageController = PageController(
initialPage: _navigationIndex,
);
}
Widget _buildSamplePage(String text) {
// This simply returns a container that fills the page,
// with a text widget in its center.
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.grey[900],
alignment: Alignment.center,
child: Text(
text,
style: TextStyle(
color: Colors.white, fontSize: 30.0, fontWeight: FontWeight.bold),
),
);
}
Widget _buildBottomNavigationBar() {
//Returns the bottom navigation bar for the scaffold
return BottomNavigationBar(
backgroundColor: Colors.grey[900],
selectedItemColor: Colors.redAccent,
unselectedItemColor: Colors.white,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home_outlined), label: "Home"),
BottomNavigationBarItem(
icon: Icon(Icons.check_box_outline_blank), label: "Cards"),
BottomNavigationBarItem(
icon: Icon(Icons.settings_outlined), label: "Settings"),
],
currentIndex: _navigationIndex,
onTap: _onNavigationPageChanged,
);
}
void _onNavigationPageChanged(int newIndex) {
//Set the new navigation index for the nav bar
setState(() => this._navigationIndex = newIndex);
//Animate to the selected page
_pageController.animateToPage(
newIndex,
curve: Curves.easeInOut,
duration: Duration(microseconds: 100),
);
}
}
Can you try something like this:
Add this line to your PageView:
PageView(
...
physics: _navigationIndex == 1 ? NeverScrollableScrollPhysics() : AlwaysScrollableScrollPhysics(),
...
)
Note: the number 1 is because the page with the GestureDetector is on index 1.
How to add custom animation to flutter tabbar and tabbar view i want to add custom navigation animation to the code that is attached below when i attach the tab bar controller i only get the option of animate to certain value i don't want that i want to change the animation completely e.g when i drag or click the tabbar it slides in from that direction what if i want fade in transition or any other animation..
class TabsPage extends StatefulWidget {
#override
_TabsPageState createState() => _TabsPageState();
}
class _TabsPageState extends State<TabsPage>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, length: 2);
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: Colors.white.withOpacity(0.9),
appBar: AppBar(
bottom: TabBar(
onTap: (index) {
setState(() {
_tabController.index = index;
});
},
controller: _tabController,
labelColor: Colors.black,
tabs: <Widget>[
Tab(
icon: Icon(Icons.person),
text: 'Hello',
),
Tab(
icon: Icon(Icons.person),
text: 'World',
),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
HomePage(),
Favorites(),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
_tabController.animateTo(1,
duration: Duration(seconds: 5), curve: Curves.easeIn);
},
),
),
);
}
}