Flutter BottomNavigationBar with AnimatedContainer - what is causing a RenderFlex overflow error? - flutter

My Flutter app uses a BottomNavigationBar wrapped in an AnimatedContainer. When the animation takes place (activated by scrolling the list) a RenderFlex overflow error occurs. I can't work out what is causing this to happen.
I've stripped down the project to bare bones code in the hope that someone could try it out and identify the issue.
The main class:
class TestMain extends StatefulWidget {
const TestMain({Key? key}) : super(key: key);
#override
State<TestMain> createState() => _TestMain();
}
class BottomNavBarItemData {
String label;Icon icon;Widget screen;
BottomNavBarItemData({required this.label,required this.icon,required this.screen});
}
late ScrollController mainScrollController;
class _TestMain extends State<TestMain> {
int _selectedIndex = 0;
bool _isVisible = true;
#override
void initState() {
_isVisible = true;
mainScrollController = ScrollController();
mainScrollController.addListener(() {
if (mainScrollController.position.userScrollDirection == ScrollDirection.reverse) {
setState(() {
_isVisible = false;
});
}
if (mainScrollController.position.userScrollDirection == ScrollDirection.forward) {
setState(() {
_isVisible = true;
});
}
});
super.initState();
}
final List<BottomNavBarItemData> screens = [
BottomNavBarItemData(
icon: const Icon(Icons.home, size: 25.0, color: Colors.red),
label: 'Page1',
screen: const Screen1(),
),
BottomNavBarItemData(
icon: const Icon(Icons.home, size: 25.0, color: Colors.red),
label: 'Page2',
screen: const Screen2(),
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
extendBody: true,
body: SafeArea(
child: IndexedStack(
index: _selectedIndex,
children: [
...screens.map((e) => e.screen).toList(),
],
),
),
bottomNavigationBar: AnimatedContainer(
duration: const Duration(milliseconds: 400),
height: _isVisible ? 70 : 0.0,
child: SizedBox(
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.orange,
currentIndex: _selectedIndex,
selectedIconTheme: IconThemeData(color: Colors.white),
selectedItemColor: Colors.white,
selectedFontSize: 14,
unselectedFontSize: 14,
unselectedIconTheme: const IconThemeData(
color: Colors.lightBlueAccent,
),
unselectedItemColor: Colors.lightBlueAccent,
onTap: _onItemTapped,
items: screens.map((e) => BottomNavigationBarItem(
label: e.label,
icon: e.icon,
),
).toList(),
),
),
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
}
And the two screens called by the main class:
class Screen1 extends StatefulWidget {
const Screen1({Key? key}) : super(key: key);
#override
State<Screen1> createState() => _Screen1();
}
class _Screen1 extends State<Screen1> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: SingleChildScrollView(
controller: mainScrollController,
physics: const AlwaysScrollableScrollPhysics(),
child: Column(children: [
Container(height: 150, color: Colors.blue),
Container(height: 150, color: Colors.white),
Container(height: 150, color: Colors.blue),
Container(height: 150, color: Colors.white),
Container(height: 150, color: Colors.blue),
Container(height: 150, color: Colors.white),
],),
),
);
}
}
class Screen2 extends StatefulWidget {
const Screen2({Key? key}) : super(key: key);
#override
State<Screen2> createState() => _Screen2();
}
class _Screen2 extends State<Screen2> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
);
}
}

Container gets overflow because the inner item space is greater to the animation size, like on 35 it will show the overflow. You can use different animation, but it will be little difference.
You can use SizeTransition for this case
class _TestMain extends State<TestMain> with SingleTickerProviderStateMixin {
int _selectedIndex = 0;
bool _isVisible = true;
late final AnimationController _controller = AnimationController(
duration: const Duration(milliseconds: 400),
vsync: this,
)..forward();
late final Animation<double> _animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
void initState() {
_isVisible = true;
mainScrollController = ScrollController();
mainScrollController.addListener(() {
if (mainScrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
_controller.reverse();
setState(() {
_isVisible = false;
});
}
if (mainScrollController.position.userScrollDirection ==
ScrollDirection.forward) {
_controller.forward();
setState(() {
_isVisible = true;
});
}
});
super.initState();
}
final List<BottomNavBarItemData> screens = [
BottomNavBarItemData(
icon: const Icon(Icons.home, size: 25.0, color: Colors.red),
label: 'Page1',
screen: const Screen1(),
),
BottomNavBarItemData(
icon: const Icon(Icons.home, size: 25.0, color: Colors.red),
label: 'Page2',
screen: const Screen2(),
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
extendBody: true,
body: SafeArea(
child: IndexedStack(
index: _selectedIndex,
children: [
...screens.map((e) => e.screen).toList(),
],
),
),
bottomNavigationBar: SizeTransition(
sizeFactor: _animation,
child: SizedBox(
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.orange,
currentIndex: _selectedIndex,
selectedIconTheme: IconThemeData(color: Colors.white),
selectedItemColor: Colors.white,
selectedFontSize: 14,
unselectedFontSize: 14,
unselectedIconTheme: const IconThemeData(
color: Colors.lightBlueAccent,
),
unselectedItemColor: Colors.lightBlueAccent,
onTap: _onItemTapped,
items: screens
.map(
(e) => BottomNavigationBarItem(
label: e.label,
icon: e.icon,
),
)
.toList(),
),
),
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
}
or
bottomNavigationBar: AnimatedScale(
duration: const Duration(milliseconds: 400),
scale: _isVisible ? 1 : 0.0,
alignment: Alignment.bottomCenter,

Related

Flutter use Provider in BottomNavigationBar

I want to get data from Provider based on argument and populate that data into each BottomNavigationBarItem
On this screen I get "meetingId" argument and use that on Provider to populate a model class with data.
class MeetingDetailScreen extends StatefulWidget {
final meetingId;
const MeetingDetailScreen({Key? key, this.meetingId}) : super(key: key);
static const routeName = '/meeting-detail';
#override
_MeetingDetailScreenState createState() => _MeetingDetailScreenState();
}
class _MeetingDetailScreenState extends State<MeetingDetailScreen> {
String meetingId = '';
var _isInit = true;
var _isLoading = false;
final List<Widget> _pages = [
DetailsScreen(),
DocumentsScreen(),
AgendaScreen(),
MinutesScreen(),
];
int _selectedPageIndex = 0;
void _selectPage(int index) {
setState(() {
_selectedPageIndex = index;
});
}
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
//This is provider
Provider.of<MeetingDetails>(context)
.fetchAndSetMeetingDetails(widget.meetingId)
.then((_) {
setState(() {
_isLoading = false;
});
});
}
_isInit = false;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 248, 1),
appBar: AppBar(
backgroundColor: Colors.white,
centerTitle: true,
iconTheme: const IconThemeData(color: Color.fromRGBO(150, 188, 51, 1)),
title: Image.asset(
"assets/images/appbar-logo.png",
fit: BoxFit.contain,
height: 50,
),
),
drawer: const MainDrawer(),
body: _pages[_selectedPageIndex],
//body: getPage(_selectedPageIndex),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.info_outline),
label: 'Details',
),
BottomNavigationBarItem(
icon: Icon(Icons.file_download),
label: 'Documents',
),
BottomNavigationBarItem(
icon: Icon(Icons.format_list_bulleted_outlined),
label: 'Agenda',
),
BottomNavigationBarItem(
icon: Icon(Icons.note_alt_outlined),
label: 'Minutes',
),
],
currentIndex: _selectedPageIndex,
selectedItemColor: Colors.amber[800],
onTap: _selectPage,
),
);
}
}
This is the BottomNavigationBarItem screen "DetailsScreen()"
class DetailsScreen extends StatelessWidget {
static const routeName = '/meeting-detail';
const DetailsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final meetingDetailsList = Provider.of<MeetingDetails>(context);
final meetingDetails = meetingDetailsList.items.toList()[0];
return Center(
child: Text(
meetingDetails.event_name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
);
}
}
Problem is Provider updates data from API and it takes a moment. when I try to use Provider data on BottomNavigationBar it throws an error because provider data is null when loading the screen. Is there any way to delay the BottomNavigationBarItem until Provider populates data?
Thank you
Problem is I wasn't using builder with ChangeNotifierProvider.
Inside widget I used following builder code and problem solved.
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(10.0),
itemCount: meeting.length,
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: meeting[i],
child:
buildDetailRow(context, 'Meeting Name', meeting[i].event_name),
),
),
],
);

Flutter: Looking up a deactivated widget's ancestor is unsafe with BottomNavigationBar and SearchPage

The app has a BottomNavigationBar (always visible) and when I added the search tab this error apears (Search page was writed following a tutorial from youtube). I'm not sure about how can manage the state when I leave the search page or how to restart it.
I realice error apears when no search has been performed
Can someone help to solve this error?
Here is my code
home
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static final List<Widget> _widgetOptions = <Widget>[
const Text(
'Index 1: Home',
style: optionStyle,
),
const Text(
'Index 1: Events',
style: optionStyle,
),
SearchScreen(),
const Text(
'Index 3: Messages',
style: optionStyle,
),
ProfilePage(uid: FirebaseAuth.instance.currentUser!.uid)
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
backgroundColor: mobileBackgroundColor,
),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_today),
label: 'Events',
backgroundColor: mobileBackgroundColor,
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
backgroundColor: mobileBackgroundColor,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble_2),
label: 'Messages',
backgroundColor: mobileBackgroundColor,
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
backgroundColor: mobileBackgroundColor,
),
],
unselectedItemColor: Colors.black12,
selectedItemColor: Colors.deepOrangeAccent,
currentIndex: _selectedIndex,
onTap: _onItemTapped,
));
}
}
search
class Search extends StatefulWidget {
#override
_SearchState createState() => _SearchState();
}
class _SearchState extends State<Search> {
static const historyLenght = 5;
final List<String> _searchHistory = ['pink', 'blue'];
late List<String> filteredSearchHistory;
String selectedTerm = "Search";
List<String> filterSearchTerms({
#required String? filter,
}) {
if (filter != null && filter.isNotEmpty) {
return _searchHistory.reversed
.where((term) => term.startsWith(filter))
.toList();
} else {
return _searchHistory.reversed.toList();
}
}
void addSearchTerm(String term) {
if (_searchHistory.contains(term)) {
putSearchTermFirst(term);
return;
}
_searchHistory.add(term);
if (_searchHistory.length > historyLenght) {
_searchHistory.removeRange(0, _searchHistory.length - historyLenght);
}
filteredSearchHistory = filterSearchTerms(filter: null);
}
void deleteSearchTerm(String term) {
_searchHistory.removeWhere((element) => element == term);
filteredSearchHistory = filterSearchTerms(filter: null);
}
void putSearchTermFirst(String term) {
deleteSearchTerm(term);
addSearchTerm(term);
}
late FloatingSearchBarController controller;
#override
void initState() {
super.initState();
controller = FloatingSearchBarController();
filteredSearchHistory = filterSearchTerms(filter: null);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FloatingSearchBar(
controller: controller,
body: FloatingSearchBarScrollNotifier(
child: SearchResultListView(
searchTerm: selectedTerm,
),
),
transition: CircularFloatingSearchBarTransition(),
physics: const BouncingScrollPhysics(),
title: Text(selectedTerm),
hint: 'Search ... ',
actions: [
FloatingSearchBarAction.searchToClear(),
],
onQueryChanged: (query) {
setState(() {
filteredSearchHistory = filterSearchTerms(filter: query);
});
},
onSubmitted: (query) {
setState(() {
addSearchTerm(query);
selectedTerm = query;
});
controller.close();
},
builder: (context, transition) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.white,
elevation: 4,
child: Builder(builder: ((context) {
if (filteredSearchHistory.isEmpty &&
controller.query.isEmpty) {
return Container(
height: 56,
width: double.infinity,
alignment: Alignment.center,
child: const Text(
'Start searching',
maxLines: 1,
overflow: TextOverflow.ellipsis,
));
} else if (filteredSearchHistory.isEmpty) {
return ListTile(
title: Text(controller.query),
leading: const Icon(Icons.search),
onTap: () {
setState(() {
addSearchTerm(controller.query);
selectedTerm = controller.query;
});
controller.close();
},
);
} else {
return Column(
mainAxisSize: MainAxisSize.min,
children: filteredSearchHistory
.map(
(e) => ListTile(
title: Text(e,
maxLines: 1,
overflow: TextOverflow.ellipsis),
leading: const Icon(Icons.history),
trailing: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
deleteSearchTerm(e);
});
},
),
onTap: () {
setState(() {
putSearchTermFirst(e);
selectedTerm = e;
});
controller.close();
},
),
)
.toList());
}
}))),
);
},
),
);
}
}
class SearchResultListView extends StatelessWidget {
final String searchTerm;
const SearchResultListView({
Key? key,
required this.searchTerm,
}) : super(key: key);
#override
Widget build(BuildContext context) {
if (searchTerm == null) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [Icon(Icons.search), Text('data')],
),
);
}
final sBar = FloatingSearchBar.of(context);
return ListView(
padding: EdgeInsets.only(
top: sBar!.style.height + sBar.style.margins.vertical),
children: List.generate(
50,
(index) => ListTile(
title: Text('Hola $searchTerm'),
subtitle: Text(index.toString()),
)));
}
}

How to change the color of bottom navigation bar icon according to the user choice

I am new at flutter, and I am trying to change the color of a button when the button is active (been pressed), my code is not working as per expectation. Someone knows how to I fix that?
My code:
import 'package:flutter/material.dart';
class BottomNavBar extends StatefulWidget {
const BottomNavBar({Key? key}) : super(key: key);
#override
_BottomNavBarState createState() => _BottomNavBarState();
}
class _BottomNavBarState extends State<BottomNavBar> {
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: <Widget>[
iconButtonBar(context, Icons.home, 0, _selectedIndex),
iconButtonBar(context, Icons.favorite, 1, _selectedIndex),
iconButtonBar(context, Icons.person, 2, _selectedIndex),
iconButtonBar(context, Icons.search, 3, _selectedIndex),
],
),
);
}
Container iconButtonBar(
BuildContext context, IconData icon, int index, int _selectedIndex) {
return Container(
height: 60,
width: MediaQuery.of(context).size.width / 4,
color: index == _selectedIndex ? Colors.blue : Colors.white, // changing the color
child: IconButton(
icon: Icon(icon),
onPressed: () {
_selectedIndex = index;
},
));
}
}
really happy if you find the time to answer.
You should try to refer below code:
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
PageController _pageController = PageController();
List<Widget> _screen = [
Home(),
MyProfile(),
Conversations(),
SearchPage()
];
void _onPageChanged(int index) {
setState(() {
_selectedIndex = index;
});
}
void _onItemTapped(int selectedIndex) {
_pageController.jumpToPage(selectedIndex);
}
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _pageController,
children: _screen,
onPageChanged: _onPageChanged,
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: this._selectedIndex,
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.black45,
backgroundColor: Colors.black,
selectedLabelStyle: TextStyle(fontWeight: FontWeight.bold),
onTap: _onItemTapped,
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.home,
),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(
Icons.person,
),
label: 'Profile'),
BottomNavigationBarItem(
icon: Icon(
Icons.sms,
),
label: 'Messages'),
BottomNavigationBarItem(
icon: Icon(
Icons.search,
),
label: 'Search'),
],
),
);
}
}
You need to call setState(); so that the changes will be reflected to the UI. so your code looks
Container iconButtonBar(
BuildContext context, IconData icon, int index, int _selectedIndex) {
return Container(
height: 60,
width: MediaQuery.of(context).size.width / 4,
color: index == _selectedIndex ? Colors.blue : Colors.white, // changing the color
child: IconButton(
icon: Icon(icon),
onPressed: () {
setState((){
_selectedIndex = index;
});
},
));
}

Flutter - How to keep the page alive when changing it with PageView or BottomNavigationBar

i'm making audio app with PageView and BottomNavigationBar, it should run the audio when isSelected
is true and it's working but when I change pages it stop working and isSelected become false again, how to prevent that from happening? i'm also using AudioPlayers pagckage.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int selectedIndex = 0;
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: PageView(
controller: pageController,
children: <Widget>[
TimerPage(),
TodoPage(),
CalenderPage(),
MusicPage(),
SettingsPage(),
],
onPageChanged: (pageIndex) {
setState(() {
selectedIndex = pageIndex;
});
},
),
),
bottomNavigationBar: SizedBox(
height: 70,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(25),
topLeft: Radius.circular(25),
),
child: BottomNavigationBar(
onTap: (selectedIndex) {
setState(() {
pageController
..animateToPage(selectedIndex,
duration: Duration(milliseconds: 500),
curve: Curves.ease);
});
},
backgroundColor: MyColors.lightgray,
selectedItemColor: MyColors.accentRed,
unselectedItemColor: MyColors.disabledGrey,
selectedFontSize: 15,
unselectedFontSize: 15,
type: BottomNavigationBarType.fixed,
currentIndex: selectedIndex,
showSelectedLabels: false,
showUnselectedLabels: false,
items: [
BottomNavigationBarItem(
icon: const Icon(FontAwesomeIcons.clock),
label: "",
),
BottomNavigationBarItem(
icon: const FaIcon(FontAwesomeIcons.check),
label: "",
),
BottomNavigationBarItem(
icon: const FaIcon(FontAwesomeIcons.calendarAlt),
label: "",
),
BottomNavigationBarItem(
icon: const FaIcon(FontAwesomeIcons.music),
label: "",
),
BottomNavigationBarItem(
icon: const FaIcon(FontAwesomeIcons.ellipsisH)
label: "",
),
],
),
),
),
);
}
}
the play button:
class SoundChip extends StatefulWidget {
final String title;
final String image;
final String audioName;
final VoidCallback onPress;
SoundChip({Key key, this.title, this.image, this.onPress, this.audioName})
: super(key: key);
#override
_SoundChipState createState() => _SoundChipState();
}
class _SoundChipState extends State<SoundChip> {
bool isSelected = false;
AudioPlayer audioPlayer = AudioPlayer();
PlayerState audioPlayerState = PlayerState.PAUSED;
AudioCache audioCache;
play() async {
await audioCache.loop(widget.audioName,
stayAwake: true, mode: PlayerMode.LOW_LATENCY);
}
pause() async {
await audioPlayer.pause();
}
#override
void initState() {
super.initState();
audioCache = AudioCache(fixedPlayer: audioPlayer);
audioPlayer.onPlayerStateChanged.listen((PlayerState state) {
setState(() {
audioPlayerState = state;
});
});
}
#override
void dispose() {
super.dispose();
audioPlayer.release();
audioPlayer.dispose();
audioCache.clearAll();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (audioPlayerState == PlayerState.PLAYING) {
pause();
isSelected = false;
} else {
play();
isSelected = true;
}
widget.onPress();
},
child: AnimatedOpacity(
opacity: isSelected ? 1 : 0.5,
duration: Duration(milliseconds: 100),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: AnimatedContainer(
duration: Duration(seconds: 1),
width: 160,
height: 100,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(widget.image),
fit: BoxFit.cover,
),
),
child: Center(
child: Text(
widget.title,
style: TextStyle(
fontSize: 30,
shadows: [
Shadow(
color: Colors.black,
blurRadius: 20,
),
],
),
)),
),
),
),
);
}
}
Add AutomaticKeepAliveClientMixin to your page that you want to keep alive even if it is not in focus in the PageView.
How to add AutomaticKeepAliveClientMixin?
Add with AutomaticKeepAliveClientMixin to your widget's state class.
class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
...
}
Add wantKeepAlive getter to your widget's state.
class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
bool get wantKeepAlive => true;
...
}
Add super.build(context) to the build method of your widget's state.
class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return ...
}
}

Changing the TabBar shape in flutter

I made a bottom app bar with the shape property set to "CircularNotchedRectangle" and it worked like a charm! The problem is that I'm looking for a "swipe to change page" functionality as TabBar provides but I don't see any way I can change its shape to the CircularNotchedRectangle. Can I change its shape? Or should I try and make my own "swipe to change page" functionality?
Thanks!
My current BottomNavigationBar:
BottomAppBar(
shape: CircularNotchedRectangle(),
notchMargin: 2.0,
child: Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(Icons.search),
iconSize: 35,
color: widget.currentTab == 0 ? Colors.purple[500] : Colors.black,
onPressed: (){
setState(() {
widget.currentTab = 0;
});
}
),
IconButton(
icon: Icon(Icons.account_circle),
iconSize: 35,
color: widget.currentTab == 1 ? Colors.purple[500] : Colors.black,
onPressed: (){
setState(() {
widget.currentTab = 1;
});
}
),
SizedBox(width: 40),
IconButton(
icon: Icon(Icons.group),
iconSize: 35,
color: widget.currentTab == 2 ? Colors.purple[500] : Colors.black,
onPressed: (){
setState(() {
widget.currentTab = 2;
});
}
),
IconButton(
icon: Icon(Icons.chat_bubble),
iconSize: 35,
color: widget.currentTab == 3 ? Colors.purple[500] : Colors.black,
onPressed: (){
setState(() {
widget.currentTab = 3;
});
}
),
]
)
]
)
);
This is the shape I'm trying to get with the TabBar
You can copy paste run full code below
To implement swipe to change page functionality with your current code
You can directly use PageView
code snippet
PageController pageController = PageController(
initialPage: 0,
keepPage: true,
);
Widget buildPageView() {
return PageView(
controller: pageController,
onPageChanged: (index) {
pageChanged(index);
},
children: <Widget>[
Red(),
Blue(),
Yellow(),
Green(),
],
);
}
...
void bottomTapped(int index) {
setState(() {
currentTab = index;
pageController.animateToPage(index,
duration: Duration(milliseconds: 500), curve: Curves.ease);
});
}
...
IconButton(
icon: Icon(Icons.search),
iconSize: 35,
color: currentTab == 0 ? Colors.purple[500] : Colors.black,
onPressed: () {
bottomTapped(0);
}),
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double width;
Color primaryColor = Colors.blue;
int currentTab = 0;
PageController pageController = PageController(
initialPage: 0,
keepPage: true,
);
Widget buildPageView() {
return PageView(
controller: pageController,
onPageChanged: (index) {
pageChanged(index);
},
children: <Widget>[
Red(),
Blue(),
Yellow(),
Green(),
],
);
}
#override
void initState() {
super.initState();
}
void pageChanged(int index) {
setState(() {
currentTab = index;
});
}
void bottomTapped(int index) {
setState(() {
currentTab = index;
pageController.animateToPage(index,
duration: Duration(milliseconds: 500), curve: Curves.ease);
});
}
#override
Widget build(BuildContext context) {
width = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: buildPageView(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.red,
child: const Icon(
Icons.add,
),
onPressed: () {},
),
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
notchMargin: 2.0,
child: Stack(children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
IconButton(
icon: Icon(Icons.search),
iconSize: 35,
color: currentTab == 0 ? Colors.purple[500] : Colors.black,
onPressed: () {
bottomTapped(0);
}),
IconButton(
icon: Icon(Icons.account_circle),
iconSize: 35,
color: currentTab == 1 ? Colors.purple[500] : Colors.black,
onPressed: () {
bottomTapped(1);
}),
SizedBox(width: 40),
IconButton(
icon: Icon(Icons.group),
iconSize: 35,
color: currentTab == 2 ? Colors.purple[500] : Colors.black,
onPressed: () {
bottomTapped(2);
}),
IconButton(
icon: Icon(Icons.chat_bubble),
iconSize: 35,
color: currentTab == 3 ? Colors.purple[500] : Colors.black,
onPressed: () {
bottomTapped(3);
}),
])
])),
);
}
}
class Red extends StatefulWidget {
#override
_RedState createState() => _RedState();
}
class _RedState extends State<Red> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.purple,
);
}
}
class Blue extends StatefulWidget {
#override
_BlueState createState() => _BlueState();
}
class _BlueState extends State<Blue> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blueAccent,
);
}
}
class Yellow extends StatefulWidget {
#override
_YellowState createState() => _YellowState();
}
class _YellowState extends State<Yellow> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellowAccent,
);
}
}
class Green extends StatefulWidget {
#override
_GreenState createState() => _GreenState();
}
class _GreenState extends State<Green> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.greenAccent,
);
}
}
All you need to do is to define the Fab location inside Scaffold like this :
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,