Los Widgets imprescindibles de Flutter para tus aplicaciones

jun. 15, 2024

Este artículo forma parte de una serie:

Flutter es un framework de desarrollo de aplicaciones móviles creado por Google que ha ganado popularidad por su capacidad para construir aplicaciones de alta calidad con una única base de código, ya hemos hablado de ello anteriormente en profundidad. También hemos visto que uno de los pilares fundamentales de Flutter es su enfoque en los Widgets. En Flutter, todo es un widget. Desde el más simple texto hasta las complejas interfaces de usuario, todo se construye utilizando Widgets. Esto simplifica enormemente el desarrollo y proporciona flexibilidad para los desarrolladores. Si recién empiezas con Flutter te recomiendo leer los capítulos anteriores de la serie antes de continuar.

Un widget en Flutter es un componente de la interfaz de usuario que describe una parte de la misma. Forman la base de una aplicación y pueden ser de diferentes tipos, como botones, textos, imágenes, listas, entre otros. Éstos no sólo definen la estructura y apariencia de la interfaz, sino también cómo se comporta y reacciona a la interacción del usuario.

Ya hemos visto que por defecto, Flutter, diferencia dos tipos de widgets: StatelessWidget y StatefulWidget. Ahora vamos a profundizar en algunos de los widgets más comunes y útiles que se utilizan en el desarrollo de aplicaciones Flutter para dar forma y estructura a nuestra interfaz de usuario.

Además de los StatelessWidget y StatefulWidget, Flutter proporciona una amplia gama de Widgets incorporados que cubren casi todas las necesidades del desarrollo de aplicaciones para móviles, se pueden clasificar en varias categorías.

Tipos de widgets

Widgets de disposición

Los widgets de disposición son responsables de organizar y posicionar los widgets en la pantalla. Algunos de los widgets de disposición junto a sus parámetros más comunes son:

  • Row: Organiza sus hijos en una fila horizontal.

    • children: Lista de widgets que se mostrarán en la fila.
    • mainAxisAlignment: Alineación principal de los hijos.
    • crossAxisAlignment: Alineación cruzada de los hijos.
    • mainAxisSize: Define si el eje principal ocupa el tamaño mínimo o máximo.
  • Column: Organiza sus hijos en una columna vertical.

    • children: Lista de widgets que se mostrarán en la columna.
    • mainAxisAlignment: Alineación principal de los hijos.
    • crossAxisAlignment: Alineación cruzada de los hijos.
    • mainAxisSize: Define si el eje principal ocupa el tamaño mínimo o máximo.
  • Container: Permite personalizar la apariencia de sus hijos, como el color de fondo, el margen, el relleno, etc.

    • alignment: Alineación del contenido dentro del contenedor.
    • padding: Relleno interno del contenedor.
    • margin: Margen externo del contenedor.
    • decoration: Decoración del contenedor, como bordes y sombras.
    • width: Ancho del contenedor.
    • height: Alto del contenedor.
  • Padding: Añade relleno a sus hijos.

    • padding: Define el relleno alrededor del hijo.
    • child: Widget hijo que recibe el relleno.
  • Center: Centra a sus hijos en la pantalla.

    • child: Widget hijo que se centrará.
  • Expanded: Expande a su hijo para ocupar todo el espacio disponible.

    • child: Widget hijo que se expandirá.
    • flex: Factor de flexión que determina la cantidad de espacio adicional asignado.
  • SizedBox: Contenedor que impone un tamaño fijo a sus hijos.

    • width: Ancho del SizedBox.
    • height: Alto del SizedBox.
    • child: Widget hijo que recibe el tamaño fijo.
  • ListView: Muestra una lista de elementos desplazables.

    • children: Lista de widgets que se mostrarán en la lista.
    • padding: Relleno alrededor de los elementos de la lista.
  • GridView: Presenta una cuadrícula de elementos.

    • children: Lista de widgets que se mostrarán en la cuadrícula.
    • padding: Relleno alrededor de los elementos de la cuadrícula.
  • Stack: Apila a sus hijos uno encima del otro.

    • children: Lista de widgets que se apilarán.
    • alignment: Alineación de los hijos dentro del stack.
    • fit: Cómo ajustar el tamaño de los hijos dentro del stack.
  • Align: Alinea a sus hijos dentro de sí mismo.

    • alignment: Alineación del hijo dentro del widget.
    • child: Widget hijo que se alineará.
    • widthFactor: Factor de ajuste del ancho.
    • heightFactor: Factor de ajuste del alto.

Widgets de visualización

Los widgets de visualización son responsables de mostrar información en la pantalla. Algunos de los widgets de los más comunes son:

  • Text: Despliega texto en la pantalla. Se debe introducir el texto a mostrar como primer parámetro.

    • style: Estilo del texto.
    • textAlign: Alineación del texto.
    • maxLines: Número máximo de líneas.
  • Image: Renderiza una imagen en la pantalla.

    • image: La imagen a mostrar.
    • width: Ancho de la imagen.
    • height: Alto de la imagen.
    • fit: Cómo ajustar la imagen dentro de su contenedor.
    • alignment: Alineación de la imagen.
    • color: Color para teñir la imagen.
  • Icon: Muestra un icono en la pantalla.

    • icon: El icono a mostrar.
    • size: Tamaño del icono.
    • color: Color del icono.
    • semanticLabel: Etiqueta semántica para accesibilidad.
  • Card: Presenta una tarjeta con una sombra y esquinas normalmente redondeadas.

    • child: Widget hijo dentro de la tarjeta.
    • color: Color de fondo de la tarjeta.
    • elevation: Elevación de la tarjeta.
    • margin: Margen externo de la tarjeta.
    • shape: Forma de la tarjeta.
  • Chip: Exhibe una etiqueta con un icono y un texto.

    • label: El texto de la etiqueta.
    • avatar: Un widget, típicamente una imagen o un icono, que aparece antes del texto.
    • deleteIcon: Un icono que aparece al final del chip.
    • onDeleted: Callback (función de devolución de llamada) que se ejecuta cuando se elimina el chip.
    • backgroundColor: Color de fondo del chip.
  • Divider: Inserta una línea divisoria entre elementos.

    • color: Color de la línea.
    • height: Altura del espacio ocupado por el divisor.
    • thickness: Grosor de la línea.
    • indent: Espacio antes de la línea.
    • endIndent: Espacio después de la línea.
  • Tooltip: Proporciona un mensaje emergente informativo.

    • message: El texto del tooltip.
    • child: El widget al que se asocia el tooltip.
    • preferBelow: Si el tooltip debe mostrarse debajo del widget.
  • SnackBar: Ofrece un mensaje emergente temporal.

    • content: Contenido principal del SnackBar.
    • action: Una acción que el usuario puede realizar.
  • CircularProgressIndicator: Indica el progreso con un círculo.

    • value: El progreso actual (0.0 a 1.0).
    • backgroundColor: Color de fondo del círculo.
    • valueColor: Color del progreso.
    • strokeWidth: Grosor de la línea del círculo.
  • LinearProgressIndicator: Representa el progreso de forma lineal.

    • value: El progreso actual (0.0 a 1.0).
    • backgroundColor: Color de fondo de la barra.
    • valueColor: Color del progreso.
    • minHeight: Altura mínima de la barra.
  • AppBar: Proporciona una barra de aplicaciones en la parte superior de la pantalla.

    • title: El título principal del AppBar.
    • actions: Lista de widgets que se muestran como acciones en el AppBar.
    • leading: Widget que se muestra al principio del AppBar.
    • backgroundColor: Color de fondo del AppBar.
    • elevation: Elevación del AppBar.
  • Drawer: Despliega un panel deslizable desde el borde de la pantalla.

    • child: Widget hijo dentro del Drawer.
    • elevation: Elevación del Drawer.
    • semanticLabel: Etiqueta semántica para accesibilidad.
    • backgroundColor: Color de fondo del Drawer.
  • Dialog: Muestra una caja de diálogo que aparece encima del contenido principal.

    • child: Widget hijo dentro del diálogo.
    • backgroundColor: Color de fondo del diálogo.
    • shape: Forma del diálogo.
  • AlertDialog: Muestra una caja de diálogo encima del contenido principal con acciones personalizadas. Todos los AlertDialog son Dialog pero no todos los Dialog son AlertDialog.

    • title: Título del diálogo.
    • content: Contenido principal del diálogo.
    • actions: Lista de widgets que representan las acciones del diálogo.
    • backgroundColor: Color de fondo del diálogo.
    • shape: Forma del diálogo.

Widgets de acción

Los widgets de acción son responsables de manejar la interacción del usuario. Algunos de los widgets de botón más comunes son:

  • ElevatedButton: Presenta un botón elevado con un fondo de color.

    • onPressed: Callback que se ejecuta cuando el botón es presionado.
    • child: Contenido del botón.
    • style: Estilo del botón.
  • TextButton: Ofrece un botón de texto sin fondo.

    • onPressed: Callback que se ejecuta cuando el botón es presionado.
    • child: Contenido del botón.
    • style: Estilo del botón.
  • IconButton: Despliega un botón con un icono.

    • icon: El icono a mostrar.
    • onPressed: Callback que se ejecuta cuando el botón es presionado.
    • tooltip: Mensaje emergente informativo.
    • color: Color del icono.
    • iconSize: Tamaño del icono.
  • FloatingActionButton: Muestra un botón flotante circular.

    • onPressed: Callback que se ejecuta cuando el botón es presionado.
    • child: Contenido del botón.
    • backgroundColor: Color de fondo del botón.
  • PopupMenuButton: Despliega un menú emergente.

    • itemBuilder: Función que crea los elementos del menú.
    • onSelected: Callback que se ejecuta cuando se selecciona un elemento.
    • icon: Icono del botón.
    • initialValue: Valor inicial seleccionado.
  • DropdownButton: Presenta un menú desplegable.

    • items: Lista de elementos del menú.
    • onChanged: Callback que se ejecuta cuando se selecciona un elemento.
    • value: Valor seleccionado actualmente.
    • hint: Widget que se muestra cuando no hay ningún valor seleccionado.
  • OutlinedButton: Ofrece un botón con un borde pero sin fondo.

    • onPressed: Callback que se ejecuta cuando el botón es presionado.
    • child: Contenido del botón.
    • style: Estilo del botón.

Widgets de entrada

Los widgets de entrada son responsables de recoger información del usuario. Algunos de los widgets de entrada más comunes son:

  • TextField: Permite al usuario introducir texto.

    • controller: Controlador del campo de texto.
    • decoration: Decoración del campo de texto.
    • keyboardType: Tipo de teclado a mostrar.
    • obscureText: Si el texto debe ocultarse (por ejemplo, para contraseñas).
    • onChanged: Callback que se ejecuta cuando el texto cambia.
    • validator: Función de validación del campo.
  • Slider: Muestra un control deslizante.

    • value: Valor actual del control deslizante.
    • onChanged: Callback que se ejecuta cuando el valor cambia.
    • min: Valor mínimo.
    • max: Valor máximo.
    • divisions: Número de divisiones.
  • Checkbox: Presenta una casilla de verificación.

    • value: Si la casilla está marcada.
    • onChanged: Callback que se ejecuta cuando el valor cambia.
    • activeColor: Color de la casilla cuando está marcada.
    • checkColor: Color de la marca.
    • tristate: Si la casilla admite un tercer estado (nulo).
  • Switch: Muestra un interruptor de encendido y apagado.

    • value: Si el interruptor está encendido.
    • onChanged: Callback que se ejecuta cuando el valor cambia.
    • activeColor: Color del interruptor cuando está encendido.
    • activeTrackColor: Color de la pista cuando está encendida.
    • inactiveThumbColor: Color del interruptor cuando está apagado.
  • Radio: Proporciona un botón de opción.

    • value: Valor del botón de opción.
    • groupValue: Valor del grupo de botones de opción.
    • onChanged: Callback que se ejecuta cuando el valor cambia.
    • activeColor: Color del botón de opción cuando está seleccionado.
    • toggleable: Si el botón de opción es alternable.
  • Form: Agrupa varios campos de entrada y proporciona validación.

    • child: Widget hijo dentro del formulario.
    • key: Clave del formulario.
    • autovalidateMode: Modo de autovalidación del formulario.
    • onChanged: Callback que se ejecuta cuando cualquier campo del formulario cambia.
    • onWillPop: Callback que se ejecuta cuando se intenta cerrar el formulario.

Gracias a todos estos Widgets y a la flexibilidad que proporciona Flutter, podemos construir interfaces de usuario complejas y atractivas con facilidad para aplicaciones móviles con una apariencia y funcionalidad excepcionales. Hay muchísimos más y aun encima pueden extenderse con widgets personalizados propios o ya creados por la comunidad. Sólo utilizando algunos de los aquí listados puedes hacer algo parecido a lo que se muestra en la captura siguiente, si quieres conocer su código fuente continúa leyendo.

Ejemplo práctico

Para nuestro ejemplo vamos a combinar algunos de estos widgets y así construir una interfaz de usuario llamativa y funcional. En él veremos varios de los diferentes tipos de widgets que hemos mencionado hasta ahora, como puede ser el Drawer:


    Drawer(
        child: SizedBox(
          height: double.infinity,
          width: 200,
          child: ListView(
            padding: EdgeInsets.zero,
            children: <Widget>[
              DrawerHeader(
                decoration: BoxDecoration(
                  color: Theme.of(context).primaryColor,
                ),
                child: Text(
                  'Drawer Header',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 24,
                  ),
                ),
              ),
              ListTile(
                leading: Icon(Icons.home),
                title: Text('Home'),
                onTap: () {
                  Navigator.pop(context);
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('Home tapped'),
                    ),
                  );
                },
              ),
              ListTile(
                leading: Icon(Icons.settings),
                title: Text('Settings'),
                onTap: () {
                  Navigator.pop(context);
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('Settings tapped'),
                    ),
                  );
                },
              ),
            ],
          ),
        )
      )
    

En este ejemplo, hemos utilizado el Widget Drawer para mostrar un panel deslizable con dos elementos: Home y Settings como muestra de un menú lateral típico en muchas aplicaciones móviles. Al seleccionar uno de los elementos, en nuestro caso, se muestra un SnackBar con un mensaje informativo pero podríamos navegar a otra pantalla o realizar cualquier otra acción.

Otro de los widgets destacables es el FloatingActionButton que se muestra en la esquina inferior derecha de la pantalla. Este botón flotante circular es muy común en las aplicaciones móviles y se utiliza para acciones destacadas o comunes, en nuestro caso simplemente mostramos un diálogo al pulsarlo:


    FloatingActionButton(
        onPressed: () {
          showDialog(
            context: context,
            builder: (context) {
              return AlertDialog(
                title: Text('Alert Dialog'),
                content: Text('This is an alert dialog where you can cancel or confirm.'),
                actions: [
                  TextButton(
                    onPressed: () {
                      Navigator.pop(context);
                    },
                    child: Text('Cancel'),
                  ),
                  TextButton(
                    onPressed: () {
                      Navigator.pop(context);
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          content: Text('You are now a new member of the club!'),
                        ),
                      );
                    },
                    child: Text('Confirm'),
                  ),
                ],
              );
            },
          );
        },
        child: Icon(Icons.info),
      )
      

En el ejemplo anterior vemos la combinación de dos Widgets entre sí para mostrar un diálogo con dos botones de acción tras presionar el botón flotante. Para nuestro ejemplo está todo en el mismo lugar pero quizá lo conveniente sería modularizar el código y separar los diferentes elementos en diferentes lugares para mantener una estructura limpia y ordenada.

El widget de navegación inferior BottomNavigationBar es otro de los widgets comunes en las aplicaciones móviles. En este caso hemos utilizado un BottomNavigationBar con tres elementos: Home, Search y Profile. Al seleccionar uno de los elementos, la aplicación se mueve a una ventana diferente, similar a la distribución por pestañas pero en la parte inferior de la pantalla:


    BottomNavigationBar(
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
      )
      

Los Widgets interactivos incluidos en la aplicación de ejemplo como el Switch, el Slider o el Checkbox son elementos bastante comunes también en aplicaciones móviles y se utilizan para recoger información del usuario. En nuestro código fuente cada uno de ellos está encapsulado en un Widget Card para darles un aspecto más atractivo y organizado, el de volumen además, muestra la información de una forma distinta respecto a los otros dos para hacer la experiencia de usuario más amigable. Mientras que el Switch y el Checkbox sólo tienen dos estados posibles (activado/desactivado), el Slider permite múltiples valores decimales entre 0 a 1, no tendría sentido mostrar un Snackbar cada vez que éste cambie:


    Card(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              Text('Enable Notifications', style: TextStyle(fontSize: 18)),
              Spacer(),
              Switch(
                value: _isNotificationEnabled,
                onChanged: (bool value) {
                  setState(() {
                    _isNotificationEnabled = value;
                  });
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('Notifications ${value ? 'enabled' : 'disabled'}'),
                    ),
                  );
                },
              ),
            ],
          ),
        ),
      ),
      Card(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              Text('Dark Mode', style: TextStyle(fontSize: 18)),
              Spacer(),
              Checkbox(
                value: _isDarkModeEnabled,
                onChanged: (bool? value) {
                  setState(() {
                    _isDarkModeEnabled = value ?? false;
                  });
                  ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('Dark Mode ${value ?? false ? 'enabled' : 'disabled'}'),
                      )
                  );
                },
              ),
            ],
          ),
        ),
      ),
      Card(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              Text('Volume', style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic)),
              SizedBox(width: 20),
              Text(_volumeValue.toStringAsFixed(2)),
              Spacer(),
              Slider(
                value: _volumeValue,
                onChanged: (double value) {
                  setState(() {
                    _volumeValue = value;
                  });
                },
              ),
            ],
          ),
        ),
      ),
    

Y eso ha sido todo para este capítulo de nuestra serie de Flutter, te invito a investigar por ti mismo cómo hemos hecho para combinar algunos de los widgets aquí mencionados y así forma a la interfaz de usuario de nuestra aplicación. El código fuente completo está disponible en nuestro repositorio de Github, allí podrás tanto verlo como descargarlo para probar por ti mismo y hacer las modificaciones que consideres, podrías empezar añadiendo aquellos Widgets de la lista que no hayan sido integrados en la aplicación y familizarizarte con aquellos que no conozcas. Si te ha gustado este artículo y quieres seguir aprendiendo sobre Flutter y otros temas de desarrollo de aplicaciones móviles, coméntanos qué te ha parecido y qué te gustaría ver en futuros artículos y sobre todo ¡Happy Coding!

Artículos relacionados

Quizá te puedan interesar

September 15, 2023

Introducción a Flutter

Flutter es un framework de desarrollo de aplicaciones móviles creado por Google, utiliza el motor de …

leer más