How to add sliding transition animation to IndexedStack? - flutter

I'm working with IndexedStack and I would like to add a sliding transition animation when the page is changed with the Bottom Navigation Bar (NOT fade animation).
This is an abstract of my code:
class _LoggedHandleState extends State<LoggedHandle> {
int _selectedPage = 1;
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title"),
),
bottomNavigationBar: BottomNavigationBar(
unselectedItemColor: Colors.white60,
backgroundColor: Colors.red,
selectedItemColor: Colors.white,
currentIndex: _selectedPage,
onTap: (int index) {
setState(() {
_selectedPage = index;
});
},
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Hello',
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
label: 'Account',
),
]),
body: IndexedStack(
index: _selectedPage,
children: [HelloView(), HomeView(), UserView()],
),
);
}
}
PS: I need to use IndexedStack in order to mantain the state, so I can't use PageBuilder

At the end of the day, IndexedStack is just a Stack of elements that will show the current tab on top. To achieve what you want I'd suggest to do something similar, yet different, like this:
class LoggedHandle extends StatefulWidget {
final _pages = <Widget>[HelloView(), HomeView(), UserView()];
#override
State<StatefulWidget> createState() => _LoggedHandleState();
}
class _LoggedHandleState extends State<LoggedHandle> {
var _selectedPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('title'),
),
bottomNavigationBar: BottomNavigationBar(
unselectedItemColor: Colors.white60,
backgroundColor: Colors.red,
selectedItemColor: Colors.white,
currentIndex: _selectedPage,
onTap: (i) {
setState(() {
_selectedPage = i;
});
},
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Hello',
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
label: 'Account',
),
],
),
body: widget._pages[_selectedPage],
);
}
}
Then, for the animation, there's a Widget for that (AnimatedSwitcher). By exploiting that, along with an AnimatedWidget of your choice (or a custom one), you'll be good to go.
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 650),
transitionBuilder: (child, animation) =>
ScaleTransition(scale: animation, child: child),
child: widget._pages[_selectedPage],
),

Related

How do I set the bottom navigation bar for a Flutter app page by page?

I would like to set whether to show the bottom navigation bar in a Flutter app page/screen/route by route. For example, when the user navigates through their files and clicks on folders, I want to show the bottom navigation bar. But when the user clicks on a "Create" button, I would not like the bottom navigation for the rest of this flow, and the user navigates backward through the back button in the app bar.
If this is not possible without a very customized, hacky, inflexible, complication solution, I would also be fine with choosing at the beginning of the flow whether to remove the bottom navigation bar.
I have tried the "normal" method, in which the bottom navigation bar only stays for the buttons that are in the bar itself, and it does not persist for anything else (using an IndexedStack and a _currentIndex variable). I have also tried the opposite, in which the bottom navigation shows for everything, which is also not desirable.
Here's what I have currently:
MyApp top-level widget:
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flytrap Audio',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const CurrentPage(),
);
}
CurrentPageState (stateful) Widget
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: screens,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
showUnselectedLabels: false,
currentIndex: _currentIndex,
onTap: (index) {
// debugPrint("index: $index");
setState(() => _currentIndex = index);
// _navigatorKey.currentState!
// .pushNamedAndRemoveUntil(screens[index], (route) => false);
// debugPrint("Navigated to ${screens[index]}");
},
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.folder),
label: 'Files',
),
BottomNavigationBarItem(
icon: Icon(Icons.live_tv),
label: 'Live Broadcasts',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
),
);
}
And each page, like FolderPage is a Scaffold
I think you can check my code, I do the little same as you said.But I use the PageView instead:
final _listPage = [
const Page1(),
const Page2(),
const Page3(),
const Page4()
];
final _controller = PageController();
int _indexSelected = 0;
void _onSelected(int index) {
setState(() {
_indexSelected = index;
});
}
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _controller,
onPageChanged: _onSelected,
children: _listPage,
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: ImageIcon(
AssetImage('assets/images/bottom_tab/bw_u.png'),
size: 25,
),
label: 'Item 0'),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_bag), label: 'Item 1'),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle_outlined), label: 'Item 2'),
BottomNavigationBarItem(icon: Icon(Icons.widgets), label: 'Item 3')
],
currentIndex: _indexSelected,
onTap: (index) => _controller.animateToPage(index,
duration: const Duration(milliseconds: 200), curve: Curves.easeIn),
selectedItemColor: const Color.fromARGB(255, 38, 112, 205),
unselectedItemColor: Colors.grey,
type: BottomNavigationBarType.fixed,
),
);
}

how to access PageViewController outside of widget

TabScreen is my main widget in my app that includes a BottomNavigationBar and a PageView to transition between pages.
PageController allows me to change views and everything is fine here, but there is one thing missing. How can I change my PageView controller outside of TabScreen? I'd like to put a button to access my main PageView controller from within my pages.
class _TabsScreenState extends State<TabsScreen> {
bool _isloading = true;
int? _selectedIndex;
List<Widget>? _pages = [
HomeScreen(),
CurrenPlanDetail(),
Container(),
ProfileScreen(),
SettingScreens(),
];
PageController? _pageController;
#override
void initState() {
_selectedIndex = 0;
_pageController = PageController(initialPage: _selectedIndex!);
}
}
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _pageController,
children: _pages!,
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: Visibility(
visible: !_isloading,
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedItemColor: kNewPurple,
//unselectedItemColor: Colors.grey,
currentIndex: _selectedIndex!,
onTap: (value) {
setState(() {
_selectedIndex = value;
_pageController!.jumpToPage(_selectedIndex!);
});
},
backgroundColor: Colors.grey[300],
items: [
BottomNavigationBarItem(
icon: Icon(
Ionicons.home_outline,
size: 15.sp,
),
label: 'Home'),
BottomNavigationBarItem(
icon: Icon(
Ionicons.reader_outline,
size: 15.sp,
),
label: 'Plan'),
BottomNavigationBarItem(
icon: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: CircleBorder(),
fixedSize: Size(50, 50),
),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return FloatingButton();
});
},
child: Icon(Ionicons.add)),
label: ''),
BottomNavigationBarItem(
icon: Icon(
Ionicons.happy_outline,
size: 15.sp,
),
label: 'Profile'),
BottomNavigationBarItem(
icon: Icon(
Ionicons.settings_outline,
size: 15.sp,
),
label: 'Setting'),
],
),
),
);
}
}
for example, somewhere in my app, I'd like to put a button and pass a function like this :
_pageController!.jumpToPage(1);
Define PageController as static and
TabScreen.pageController.jumpToPage(1);

separate the code of bottom navigation bar from the scaffold

I just need to separate the code of bottom navigation bar from the scaffold. But the issue is when i click another tab to switch to another page, there is a 7 seconds delay.
The code of scaffold Is:
Scaffold(
extendBody: true,
body: IndexedStack(
index: HomeScreen.pageIndex,
children: [
HomePage(isMember: widget.isMember),
(widget.isMember)
? const MyOpportunityPage()
: const AddOpportunity(),
ProfilePage(isMember: widget.isMember),
],
),
bottomNavigationBar: BottomNavBar(
isMember: widget.isMember,
),
);
The Bottom Navigation Bar code is:
class BottomNavBar extends StatelessWidget {
BottomNavBar({
Key? key,
required this.isMember,
}) : super(key: key);
bool isMember;
#override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (context, StateSetter setState) {
return BottomNavigationBar(
currentIndex: HomeScreen.pageIndex,
onTap: (int value) => setState(() {
HomeScreen.pageIndex = value;
}),
type: BottomNavigationBarType.fixed,
elevation: 2,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'home'.tr,
),
BottomNavigationBarItem(
icon: Icon(Icons.flag_sharp),
label:isMember
? 'my_opportunities'.tr
: "add_opportunity".tr,
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'profile'.tr,
),
],
);
}
);
}
}
I'm not sure why you are having a 7-second delay, especially when you did not specify one, maybe you should first close your program and probably uninstall it and reboot your IDE and try again, and if that is still the case, you can refactor your code like this;
Scaffold(
body: IndexedStack(
index: currentIndex,
children: [
Center(child: MyHomeWidget()),
Center(child: AnotherWidget()),
Center(child: SomeOtherWidget()),
],
),
bottomNavigationBar: BottomNavBar(
isMember: isMember,
currentI: currentIndex,
onPress: (int value) => setState(() {
currentIndex = value;
}),
),
),
class BottomNavBar extends StatelessWidget {
const BottomNavBar({
Key? key,
required this.isMember,
required this.onPress,
this.currentI = 0,
}) : super(key: key);
final bool isMember;
final Function(int) onPress;
final int currentI;
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: currentI,
onTap: onPress,
type: BottomNavigationBarType.fixed,
elevation: 2,
items: [
const BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'home',
),
BottomNavigationBarItem(
icon: const Icon(Icons.flag_sharp),
label: isMember ? 'my_opportunities' : "add_opportunity",
),
const BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'profile',
),
],
);
}
}
Hopefully there would be a difference. Just implement your own index and any other vital part of the code. I tested this on dartpad and it worked fine, here is a link

Flutter, how to change current tab bar view or page or index from child

I have a BottomNavigationBar
import 'screen_one.dart' as screen_one;
import 'screen_two.dart' as screen_two;
List<Widget> _widgetOptions = <Widget>[
screen_one.ScreenOne(),
screen_two.ScreenTwo(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
Scaffold(
bottomNavigationBar:
BottomNavigationBar(
backgroundColor: Colors.green,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
backgroundColor: Colors.green,
icon: Icon(Icons.person),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text(''),
),
],
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
body: _widgetOptions.elementAt(_selectedIndex),
),
I want to change current tab index from children class (ScreenOne, ScreenTwo).
when a function occurs in ScreenOne, it needs to move on to ScreenTwo.
how could I acheive this?
Using callbacks,
Pass _onItemTapped as parameter to ScreenOne() : ScreenOne(_onItemTapped)
Inside ScreenOne : final Function(int) onItemTapped;
cCall onItemTapped(your_index_here)

Flutter: selected Icon on BottomNavigationBar doesn't change when tap

I work on flutter and I make an app with 5 tabs, using a BottomNavigationBar, who change the currently displayed content.
When I tap on a tab, the content updates to the new content, but the tabs icon doesn't change.
I have tried to change the BottomNavigationBarType but that changes nothing...
here is base page widget:
class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
#override
_Home createState() => _Home();
}
class _Home extends State<Home> {
int _selectedIndex = 0;
static List<Widget> _widgetOptions = <Widget>[
HomePage(),
CreateTrain(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
print(_selectedIndex);
return Scaffold(
body: _widgetOptions.elementAt(_selectedIndex),
bottomNavigationBar: BottomNavigationBar(
currentIndex: 0, // this will be set when a new tab is tapped
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text('Home'),
),
BottomNavigationBarItem(
icon: new Icon(Icons.assignment),
title: new Text('Training'),
),
BottomNavigationBarItem(
icon: new Icon(Icons.play_arrow),
title: new Text('start'),
),
BottomNavigationBarItem(
icon: new Icon(Icons.insert_chart),
title: new Text('Stats'),
),
BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('Profile'))
],
selectedFontSize: 12,
unselectedFontSize: 12,
selectedItemColor: Colors.amber[800],
unselectedItemColor: Colors.grey[500],
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
onTap: _onItemTapped,
),
);
}
}
Here is HomePage widget:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: const Text(
'Home',
style: TextStyle(
color: Colors.black,
fontSize: 30,
),
),
backgroundColor: _bgColor,
),
body: Text('data'),
);
}
}
Thanks for your help.
In your code
currentIndex: 0, // this will be set when a new tab is tapped
This should be
currentIndex: _selectedIndex, // this will be set when a new tab is tapped