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,
);
}
}
CodePudding user response:
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,