Flutter basa la creación de interfaces en el uso de un componente base que ellos han bautizados cómo Widget. Una vista está compuesta por un árbol de widgets concatenados unos con otros en un árbol de widgets construidos jerárquicamente.
Estos widgets son independientes unos de otros y podemos componer las vistas utilizando cualquiera de estos widgets que ya existen predefinidos en la biblioteca del SDK de Flutter y colocándolos en la parte de la vista que lo necesitemos.
Por supuesto los widgets son customizables, teniendo cada uno de ellos muchas propiedades que son fácilmente configurables en color, tamaño, aspecto, ciertas funcionalidades o animaciones.
Uno de estos widgets existentes es la BottomNavigationBar que nos sirve para construir un menú de navegación en la parte inferior de la pantalla del terminal donde se ejecute la aplicación.

Esta barra de navegación nos permite ir a las diferentes opciones que tenga nuestra aplicación y es uno de los componentes más utilizados tanto en Android como en IOS para realizar esta navegación dentro de la aplicación.
Añadiendo y configurando un BottomNavigationBar a nuestra aplicación Flutter
Partimos de un proyecto básico y con los valores por defecto creados con el asistente de Android Studio. En mi ejemplo he extraído la parte del código donde se ha de representar a la barra de navegación a una clase que he llamado HomeController para separar y abstraer algo más el código.
También he extraído a un archivo llamado Styles, la parte configuración de un estilo de textos que he usado para darle algo de estilo al texto que se muestra en cada pantalla.
Mi árbol de archivos queda así:

En el fichero main únicamente queda el código de inicio de arranque de la aplicación y donde se instancia la clase padre de la aplicación y donde el widget padre de toda ella es un MaterialApp.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Ejemplo de BottomNavigationBar',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeController(title: 'Compilación Movil'),
);
}
}
Con este código lo que tenemos es un widget, MaterialApp, como padre de nuestra aplicación y que es el widget a partir del cual «cuelgan» el resto de vistas de toda la aplicación. Hemos estado un título y un theme a la aplicación. En la propiedad home
hemos instanciado la clase que va a contener nuestra barra de navegación y que será nuestro HomeController.
class HomeController extends StatefulWidget {
HomeController({Key key, this.title}) : super(key: key);
final String title;
@override
_HomeControllerState createState() => _HomeControllerState();
}
class _HomeControllerState extends State<HomeController> {
int _selectedIndex = 0;
static const List<Widget> _widgetOptions = <Widget>[
Text('Tab 0: Home', style: Styles.optionStyle),
Text('Tab 1: Business', style: Styles.optionStyle),
Text('Tab 2: School', style: Styles.optionStyle),
];
static const List<BottomNavigationBarItem> _navigationItems = <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: _navigationItems,
currentIndex: _selectedIndex,
selectedItemColor: Colors.red[800],
onTap: _onItemTapped,
),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Hacemos la barra de navegación utilizando un StatefulWidget
porque cuando pulsamos sobre cualquiera de las pestañas de la barra de navegación hay que volver a repintar toda la vista ya que el contenido de la misma cambia. No va a ser una pantalla estática.
static const List<Widget> _widgetOptions = <Widget>[
Text('Tab 0: Home', style: Styles.optionStyle),
Text('Tab 1: Business', style: Styles.optionStyle),
Text('Tab 2: School', style: Styles.optionStyle),
];
El primer array de widgets son el contenido de las diferentes pantallas que se van dibujar cuando seleccionemos cada una de las tabs de la barra de navegación.
En este caso solo vamos a pintar un texto, pero en condiciones normales aquí irían las jerarquías de vistas que dibujarían, lo más o menos complejas, que hiciéramos nuestras vistas, listas, scroll, gráficos, imágenes, etc…
static const List<BottomNavigationBarItem> _navigationItems = <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
];
El segundo array de widgets que encontramos corresponde a las diferentes opciones de la barra de navegación. Aquí estamos construyendo objetos del tipo BottomNavigationBarItem a los cuales les pasamos como únicos parámetros un icono y un texto como título. Además de estas propiedades también admite un color como background o un widget para setearlo como icono activo cuando es pulsado esa Tab.
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
El método _onItemTapped
es la función que se va a ejecutar cada vez que pulsemos sobre una pestaña de la barra de navegación. Dentro de él lo que hacemos es recibir el índice correspondiente con la pestaña pulsada y setearla a la variable global a la clase que tenemos creada.
Esto lo hacemos dentro de la lambda setState() de Flutter que le dice a la aplicación que debe de repintar todos los widgets que tengan estado dentro de la jerarquía de vistas.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: _navigationItems,
currentIndex: _selectedIndex,
selectedItemColor: Colors.red[800],
onTap: _onItemTapped,
),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
Por último tenemos el método build
que tenemos que sobreesribir en todo widget y que es el que el sistema llama para pintar lo que exista dentro de este método. Vemos que retorna un objeto tipo Widget, y este Widget es el que contendrá dentro toda la jerarquía de vistas a dibujar.
Dentro de este método tenemos un Scaffold como widget padre de toda esta vista. Este Widget admite una serie de parámetros entre los cuales están: una appBar
, un body
y por último uno de nombre bottomNavigationBar
. Como su nombre indica, aquí es donde debemos de instancia un objeto de este tipo y configurar los parámetros que requiere.
Los parámetros de la propiedad para la barra de navegación son:
items
: Aquí seteamos nuestro array de BottomNavigationItems que hemos creado antes y que son las tabs que va a tener nuestra barra de navegación.
currentIndex
: En esta propiedad debemos indicar el índice del item que está activo en cada momento. En nuestro caso tenemos una propiedad que cambia de valor cada vez que pulsamos en una Tab y que toma el valor del método _onItemTapped
.
onTap
: Aquí debemos de añadir una función que recibirá el índice del ítem que se ha pulsado. Con este índice que nos devuelve el sistema es con el que cambiamos el valor de nuestra variable _selectedIndex
en cada momento.
selectedItemColor
: Esta propiedad recibe un Color con el que estera el icono y el texto de la Tab que está pulsada en cada momento.
Por último señalar que en la propiedad body
del Scaffold es donde debemos de ir cambiando la vista a mostrar según la tab de la barra de navegación pulsada. Para hacer esto correctamente, nos ayudamos de la variable global que tiene como valor el número del índice de la tab pulsado, y con este valor seleccionamos dentro del array de vistas _widgetsOptions
la que corresponde con esa posición.

Todo el código del ejemplo está disponible en nuestra cuenta de Github.
Desarrollador Android/IOS desde los orígenes del universo. También he probado Flutter y en general me gusta todo lo que huela a plataformas móviles. Intento escribir sobre problemas que me encuentro en el día a día y que espero ayuden a otr@s.