Home > Software engineering >  Flutter BottomNavigationBar with AnimatedContainer - what is causing a RenderFlex overflow error?
Flutter BottomNavigationBar with AnimatedContainer - what is causing a RenderFlex overflow error?

Time:07-20

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,
  • Related