1

I have a simple app, with 3 bottom nav items.
However, when I click on one of the bottom nav items bellow, the entire page is moving, from right to left with iOS, from bottom with android.
I want my bottom navigation app to stay at the same place, as traditionnal Bottom Nav Bar

enter image description here

I know I can find working implementations of bottom navigation bar using a StatefulWidget, a int _selectedIndex = 0; and a static const List<Widget> _widgetOptions,

but my app is already big and using a different mecanism in 3 steps defines below : (that you find on my github repo) :

1. MaterialApp defines _onGenerateRoute :

main.dart

MaterialApp(
      title: 'Keep bottom navigation',
      themeMode: ThemeMode.dark,
      onGenerateRoute: _generateRoute,
    );

main.dart

  Route<dynamic> _generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case AppRoutes.computer:
        return MaterialPageRoute(builder: (context) => ComputerScreen());
      case AppRoutes.phone:
        return MaterialPageRoute(builder: (context) => PhoneScreen());
      case AppRoutes.person:
      default:
        return MaterialPageRoute(builder: (context) => PersonScreen());
    }
  }

route_config.dart

class AppRoutes {
  static const String computer = '/computer';
  static const String phone = '/phone';
  static const String person = '/person';
}

2. Every screens extends BaseScreen as a State :

screens/computer_screen.dart


class ComputerScreen extends StatefulWidget {
  @override
  _ComputerScreenState createState() => _ComputerScreenState();
}

class _ComputerScreenState extends BaseScreenState<ComputerScreen> {
  @override
  String get currentRoute => AppRoutes.computer;

  @override
  Widget buildScreen(BuildContext context) {
    return Center(
      child:
          Text("Computer screen", style: Theme.of(context).textTheme.headline3),
    );
  }
}

3. Base Sceen defines the basics of a screen :

base_screen.dart

import 'package:flutter/material.dart';
import 'package:keep_bottomnav/bottom_nav_bar.dart';

abstract class BaseScreenState<T extends StatefulWidget> extends State<T> {
  String get currentRoute;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: this.buildAppBar(context),
      body: this.buildScreen(context),
      bottomNavigationBar: this.buildBottomNavigationBar(context),
      floatingActionButton: this.buildFloatingActionButton(context),
    );
  }

  Widget buildScreen(BuildContext context);

  PreferredSizeWidget buildAppBar(BuildContext context) {
    return null;
  }

  Widget buildBottomNavigationBar(BuildContext context) {
    return BottomNavBar(
      selectedRoute: currentRoute,
      onSelectRoute: _onItemTapped,
    );
  }

  Widget buildFloatingActionButton(BuildContext context) {
    return null;
  }

  bool get centerFloatingActionButton => false;

  void _onItemTapped(String route) {
    Navigator.pushNamedAndRemoveUntil(context, route, (route) => false);
  }
}

4. UI of BottomNavBar

import 'package:flutter/material.dart';
import 'package:keep_bottomnav/bottom_nav_bar_item.dart';
import 'package:keep_bottomnav/route_config.dart';

class BottomNavBar extends StatelessWidget {
  final String selectedRoute;
  final ValueChanged<String> onSelectRoute;

  BottomNavBar({this.selectedRoute, this.onSelectRoute});

  @override
  Widget build(BuildContext context) {
    double screenWidth = MediaQuery.of(context).size.width;
    double itemWidth = (screenWidth / 3);
    return Material(
      color: Colors.white,
      child: SafeArea(
        child: Container(
          padding: const EdgeInsets.only(bottom: 10),
          color: Colors.white,
          height: 65,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              _buildNavigationItem(
                AppRoutes.computer,
                icon: Icon(Icons.computer),
                title: "Computer 1",
                itemWidth: itemWidth,
              ),
              _buildNavigationItem(
                AppRoutes.phone,
                icon: Icon(Icons.phone),
                title: "Phone 2",
                itemWidth: itemWidth,
              ),
              _buildNavigationItem(
                AppRoutes.person,
                icon: Icon(Icons.person),
                title: "Person 3",
                itemWidth: itemWidth,
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildNavigationItem(String route,
      {Widget icon, String title, itemWidth}) {
    return BottomNavBarItem(
      isSelected: selectedRoute == route,
      icon: icon,
      title: title,
      minWidth: itemWidth,
      onTap: () {
        if (onSelectRoute != null) {
          onSelectRoute(route);
        }
      },
    );
  }
}
import 'package:flutter/material.dart';

class BottomNavBarItem extends StatelessWidget {
  final bool isSelected;
  final VoidCallback onTap;
  final Widget icon;
  final String title;
  final double minWidth;

  BottomNavBarItem(
      {Key key,
      @required this.icon,
      @required this.title,
      this.isSelected = false,
      this.onTap,
      this.minWidth = 50})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: this.onTap,
      child: ConstrainedBox(
        constraints: BoxConstraints(minWidth: 0),
        child: Container(
          decoration: BoxDecoration(
              border: Border(
                  top: BorderSide(
                      color: isSelected ? Colors.yellow : Colors.transparent,
                      width: 3.0))),
          padding: const EdgeInsets.only(top: 10, left: 10, right: 10),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              icon,
              Text(
                title.toUpperCase(),
                style: TextStyle(color: Colors.white, fontSize: 9.0),
              )
            ],
          ),
        ),
      ),
    );
  }
}

Thank you very much for your help

Using Hero on BottomNavItems was buggy

Dimitri Leurs
  • 590
  • 5
  • 19

1 Answers1

1

The animation is coming from MaterialPageRoute. When navigating to and from a Widget, you need to supply a PageRoute. The PageRoute is the one responsible for telling the framework how to transition from one screen to another. By using MaterialPageRoute, you are using its predefined values where it adds the animation you see above. To remove that animation, you can create your own PageRoute with no animation:

Route _createRoute() {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => MyPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return child;
    },
  );
}

Use _createRoute() in your Navigator instead of MaterialPageRoute:

Navigator.of(context).push(_createRoute());

How the Problem was fixed :

  Route<dynamic> _generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case AppRoutes.computer:
        return _createRoute(ComputerScreen());
      case AppRoutes.phone:
        return _createRoute(PhoneScreen());
      case AppRoutes.person:
      default:
        return _createRoute(PersonScreen());
    }
  }
}

Route _createRoute(Widget screen) {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => screen,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return child;
    },
  );
}
CoderUni
  • 5,474
  • 7
  • 26
  • 58