¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
  1. Libros
  2. Flutter
  3. Los widgets básicos
Extrait - Flutter Desarrolle sus aplicaciones móviles multiplataforma con Dart
Extractos del libro
Flutter Desarrolle sus aplicaciones móviles multiplataforma con Dart Volver a la página de compra del libro

Los widgets básicos

Introducción

Flutter se compone de widgets. Este es el corazón del framework cuyo lema es "In Flutter everything is a Widget". En español, se podría decir que con Flutter todo es un widget.

Por tanto, el widget es el elemento central del SDK. Existe para casi todo: desde elementos gráficos hasta componentes funcionales.

Hasta la fecha, en la página web que hace referencia a todos los widgets, https://flutter.dev/docs/reference/widgets, es posible contar 154.

El objetivo no es detallarlos todos. El capítulo se centrará en descubrir los principales widgets que se pueden utilizar durante un proyecto. Otros se tratarán más adelante, ya que tienen ciertas características especiales.

Primero, nos tomaremos el tiempo necesario para examinar las principales estructuras gráficas en las plataformas Android e iOS. También será una oportunidad para mencionar la detección de estos últimos, para obtener un resultado adaptado.

Luego se detallarán otros aspectos, como los diferentes estilos de botones o la inserción de elementos multimedia (imágenes, iconos, etc.). Este último punto también nos permitirá centrarnos en las dependencias y en su gestión. El descubrimiento continuará con los textos y terminará con los widgets utilizados para administrar el diseño de otros...

Material Component

Entre las agrupaciones de widgets, se encuentran todas aquellas que tienen que ver con al aspecto gráfico, basado en el estilo Material Design de Google. Este es el que se aplica a Android. Condiciona el aspecto y distribución general de la pantalla, así como a otros elementos, como botones, por ejemplo.

1. Scaffold

Scaffold es la estructura básica de las aplicaciones de Android. Se trata de un conjunto que puede reagrupar varios elementos, como una appBar, un body, un floatingActionButton o incluso una bottomNavigationBar.

Dado que se trata de un elemento principal, será el primero en ser devuelto en el widget de construcción gráfica, el build.

@override 
Widget build(BuildContext context) {  
 return Scaffold( 
) ; //Scaffold 

a. AppBar

La appBar es un widget que se encuentra en la parte superior de la pantalla y en el que normalmente se coloca el título. Es parte de Scaffold.

Se trata de una completa herramienta, que también puede contener botones.

He aquí un ejemplo de código que sitúa una appBar dentro de un Scaffold e integra un botón «me gusta».

@override 
Widget build(BuildContext context) {  
 return Scaffold( 
   appBar: AppBar( 
     title: Text(widget.title), 
     actions: <Widget>[ 
       IconButton( 
           icon: _corazon, 
           onPressed: _likeThis 
       ), 
     ], 
     backgroundColor: Colors.red, 
   ), 
 ); 
} 

En el ejemplo, dentro de la estructura básica, Scaffold, se integra una appBar. Tiene el título de la página y el botón «Me gusta». Para variar un poco, la propiedad backgroundColor se cambia a fin de mostrar el color rojo. 

Para el botón, la elección es un widget IconButton. Está integrado en un parámetro denominado action, que espera una lista de widgets. El IconButton se verá con más detalle un poco después. La propiedad onPressed llama al método...

Cupertino (Style iOS)

Junto con el estilo de Google, Material Design, que se aplica a la plataforma Android, Flutter incluye widgets de iPhone que coinciden con el estilo que se encuentra en iOS. Se trata de los widgets de Cupertino (nombre dado en referencia a la ciudad donde se encuentra la sede de Apple). En esta parte, no se verán todos los widgets de Cupertino. Algunos elementos más específicos, como los botones, se tratarán un poco más adelante.

1. Scaffold básico

En iOS también existe un Scaffold. Se utiliza, al igual que el de Material Design, para estructurar la pantalla. Se trata de CupertinoPageScaffold.

Para usarlo, debe importar la librería de Cupertino porque, por defecto, solo está presente la de Material.

import 'package:flutter/cupertino.dart'; 

El resto del código se parece al Scaffold que hemos visto con anterioridad:

@override 
Widget build(BuildContext context) {  
 return CupertinoPageScaffold( 
     child: Center( 
       child: Column( 
         mainAxisAlignment: MainAxisAlignment.center, 
         children: <Widget>[ 
 
         ], 
       ), 
     ), 
   backgroundColor: Colors.red[100], 
 ); 
} 

El ejemplo retoma la presentación que se ve habitualmente, es decir, un Center seguido de un Column. También existe una propiedad backgroundColor para establecer el color de fondo. El otro widget que se puede incorporar es una navigationBar. Antes de ver este elemento en detalle, queda otro Scaffold por descubrir para quien ya tiene uno.

2. Scaffold con barra de navegación

Existe otro Widget correspondiente al Scaffold, pero que también maneja una barra de navegación esta vez: el CupertinoTabScaffold.

Tradicionalmente, en iOS existe una barra de navegación...

Detección de la plataforma

Si el objetivo de una aplicación es mezclarse con el estilo de su sistema operativo host, entonces debería tener ambos estilos: Material Design y Cupertino. Por lo tanto, es necesaria una herramienta que permita detectar la plataforma para saber qué estilo se debe cargar.

Para probar esto, en primer lugar debe crear dos widgets, uno para Android y otro para iOS. En este caso, se trata respectivamente de un Scaffold y de un CupertinoPageScaffold.

Dentro de la classe _MyHomePageState, el código del widget Material Design se corresponderá con esto:

Widget material() {  
 return Scaffold( 
   appBar: AppBar( 
     title: Text(widget.title), 
     backgroundColor: Colors.red, 
   ), 
   body: Center( 
     child: Column( 
       mainAxisAlignment: MainAxisAlignment.center, 
       children: <Widget>[ 
         Text(  
           'estilo Material Design', 
         ), 
       ], 
     ), 
   ), 
   bottomNavigationBar: BottomNavigationBar( 
     items: const <BottomNavigationBarItem>[ 
       BottomNavigationBarItem( 
           icon: Icon( 
             Icons.home, 
             color: Colors.white, 
           ), 
           title: Text(  
             'Inicio',  
             style: TextStyle(color: Colors.white), 
           )), 
       BottomNavigationBarItem( ...

Botones

En el desarrollo de una aplicación, los botones son fundamentales. Garantizan la posibilidad de actuación del usuario. Los botones pueden adoptar varias formas. Esta parte explicará los widgets de tipo botón que se usan comúnmente con Flutter.

1. FloatingActionButton

El botón de acción flotante, a veces denominado por su acrónimo FAB, se ha detallado anteriormente en las secciones «AppBar» y «FloatingActionButton». Este widget puede considerarse parte del Scaffold, a diferencia de otros botones que a su vez encajan en la propiedad del body del Scaffold, con mayor frecuencia dentro de otros widgets.

2. RaisedButton

El raiseButton es un botón de Material Design. Se trata de un botón que simula un relieve. Este efecto viene dado por una elevación. Este último aumenta más cuando se presiona. Su tamaño mínimo es 88,0 por 36,0.

En el lado de la acción, ofrece dos llamadas: onPressed y onLongPressed. Si son nulos, el botón está desactivado (disabled) por defecto.

El cambio de color del botón solo aparece cuando está activo. Por tanto, es necesario alimentar un valor no nulo para onPressed u onLongPressed.

Para ver la apariencia de un RaisedButton, se crea una aplicación solo con un floatingActionButton y un texto central, que detectará una pulsación sobre él:

class _MyHomePageState extends State<MyHomePage> {  
 String _opcionBtn = 'Ningún botón';  
  
 void _queBoton(String btn) { 
   setState(() {  
     switch (btn) {  
       case 'fab': 
         {  
           _opcionBtn = "FloatingActionButton"; 
         }  
         break; 
       } 
   }); 
 }  
  void _fabOnPressed() { 
   _queBoton('fab'); 
 } 
 
 
 @override 
 Widget build(BuildContext...

Elementos multimedia

En el desarrollo de aplicaciones, es indispensable hacer que la experiencia del usuario sea lo más exitosa posible. Por tanto, es fundamental saber integrar los recursos multimedia. En esta sección, vamos a tratar sobre los iconos y las imágenes.

1. Iconos

Los iconos son una de las herramientas más utilizadas en la actualidad. Le permiten obtener una comprensión rápida, solo a través de un objeto visual. Además, a fuerza de usarlos, se han establecido estándares y convenciones. Por ejemplo, todo el mundo asocia la lupa con una búsqueda o una rueda dentada con la configuración.

La inserción del icono se puede realizar a través del widget Icono. Este último le permite jugar con ciertas propiedades. En particular, es posible definir el tamaño del icono usando size. Su color también se puede cambiar a través de color.

Aparte de estos parámetros opcionales, la clase Icon necesariamente espera un objeto de tipo IconData. Este es el caso de los Icons y los CupertinoIcons

Flutter ofrece una amplia variedad de iconos. Fácil de encontrar, puesto que hay una gran cantidad ya prevista. Ofrece dos posibilidades básicas, ya sea trabajando con la clase Icons, que tiene una colección de estilo Material Design, o con la clase CupertinoIcons, que ofrece un estilo iOS.

a. Icons (Material Design)

Es posible encontrar el conjunto de iconos de esta clase en: https://material.io/resources/icons/?style=baseline

Esta es la implementación de una aplicación sencilla que tiene un icono de estilo Material Design (Icons):

import 'package:flutter/material.dart';  
  
void main() => runApp(MyApp());  
  
class MyApp extends StatelessWidget { 
 @override 
 Widget build(BuildContext context) {  
   return MaterialApp( 
     title: 'Flutter Demo', 
     theme: ThemeData  
       primarySwatch: Colors.blue, 
     ), 
     home: MyHomePage(title: 'Iconos Flutter'), 
   ); 
 } 
}  
  
class MyHomePage extends StatelessWidget { 
 MyHomePage({Key key, this.title}): super(key: key);  
  
 final String title; 
 
 
 @override 
 Widget build(BuildContext context) {  
   return Scaffold( 
     appBar: AppBar( 
       title: Text(title), 
     ), 
     body: Center( 
       child: Column( 
         mainAxisAlignment: MainAxisAlignment.center, 
         children: <Widget>[ 
           Row( 
             mainAxisAlignment: MainAxisAlignment.center, 
             children: <Widget>[ 
               Text(  
                 'Icono Material Design:', 
                 style: TextStyle( 
                     color: Colors.teal, 
                     fontSize: 25, 
                     fontWeight: FontWeight.bold 
                 ), 
               ), 
               Padding(padding: EdgeInsets.all(10)), 
               Icon( 
                 Icons.home,  
                 color: Colors.green, 
                 size: 35, 
               ), 
             ], 
           ), 
         ], 
       ), 
     ), 
   ); 
 } 
} 

En este código, se colocan un texto y un icono. Este último representa una casa. Estos dos elementos se insertan en un widget Row, que se utiliza para crear una fila. Además, para crear un espacio mínimo entre ellos, se utiliza otro widget, Padding. Estos temas se tratarán más adelante en este capítulo.

Por ahora, la representación visual es sencilla:

images/cap6_pag55.png

Figura 17 - Icono Material Design

b. CupertinoIcons (Style iOS)

En el ejemplo anterior, se crea una nueva línea (Row) con un texto y un icono, que representa de nuevo una casa, pero esta vez en estilo iOS. Por tanto, se añade este código:

Row( 
 mainAxisAlignment: MainAxisAlignment.center, 
 children: <Widget>[ 
   Text(  
     'Icono iOS:', 
     style: TextStyle( 
         color: Colors.blueGrey, 
         fontSize: 25, 
         fontWeight: FontWeight.bold 
     ), 
   ), 
   Padding(padding: EdgeInsets.all(10)), 
   Icon( 
     CupertinoIcons.home, 
     color: Colors.black, 
     size: 35, 
   ), 
 ], 
), 

Se mantienen las dimensiones, pero se modifican los colores para identificar mejor los diferentes elementos de la pantalla. Esto ahora se ve de la siguiente manera:

images/cap6_pag56.png

Figura 18 - CupertinoIcons

c. FontAwesome

FontAwesome es un sitio de Internet muy conocido por los desarrolladores web. Ofrece una colección muy importante de iconos de todo tipo. Para descubrirlo, se puede acceder a través de la URL: https://fontawesome.com

Hasta la fecha, tiene un catálogo de más de 7 700 iconos, de los cuales alrededor de 1 500 son gratuitos.

De forma predeterminada, no se puede acceder directamente a ellos en Flutter, pero hay una manera de conseguir dicho acceso.

El equipo de desarrollo de Flutter, junto con su comunidad, participan en la creación de paquetes. Están centralizados en el sitio https://pub.dev

En desarrollo, agregar paquetes o librerías a un proyecto se denomina gestión de dependencias. Flutter garantiza la organización dentro de un archivo específico: pubspec.yaml.

En cuanto a FontAwesome, un paquete proporciona el acceso a él. Se llama font_awesome_flutter. Hasta la fecha, el sitio pub.dev lo ofrece en su versión 8.5.0:

images/flutter-c6-i100.PNG

Figura 19 - Package font_awesome_flutter 8.5.0 en el sitio pub.dev

El sitio ofrece todas las explicaciones necesarias para el uso del paquete. Más allá de eso, también permite obtener una descripción, conocer a los autores de este último, así como la licencia de uso.

Para obtener este paquete, debe ir al archivo pubspec.yaml. Debajo de las dependencies, simplemente agregue la siguiente línea:

font_awesome_flutter: ^8.5.0 

En el proyecto iniciado anteriormente, las dependencias de pubspec.yaml deberían ser las siguientes:

dependencies:  
 flutter:  
   sdk: flutter 
 
 # The following adds the Cupertino Icons font to your application. 
 # Use with the CupertinoIcons class for iOS style icons.  
 cupertino_icons: ^0.1.2  
 font_awesome_flutter: ^8.5.0 

Tenga en cuenta que este código es válido en el momento de escribir este libro. Siempre se debe verificar que la versión de destino sea la misma y que no haya cambiado. Si este fuera el caso, sería suficiente cambiar el número de versión para que coincida con el del paquete que se encuentra en pub.dev.

Queda un paso crucial. Ahora que se ha declarado la dependencia, es necesario recuperar los datos. Para esto, deberá hacer clic en Packages get en la parte superior derecha de la pantalla en Android Studio. Tenga en cuenta que esta opción solo está disponible en el archivo pubspec.yaml.

images/flutter-c6-i105.PNG

Figura 20 - Recuperación de los paquetes gracias a Packages get

El procesamiento debería tener lugar en la consola. Si todo salió bien, el código de salida resultante es 0:

images/flutter-c6-i110.PNG

Figura 21 - Resultado de Packages get

A partir de ese momento, este paquete está disponible y se puede utilizar en el código del programa. En el ejemplo que hemos comenzado un poco antes, se inserta una última línea (Row):

Row( 
 mainAxisAlignment: MainAxisAlignment.center, 
 children: <Widget>[ 
   Text(  
     'Icono font_awesome:', 
     style: TextStyle( 
         color: Colors.deepPurple, 
         fontSize: 25, 
         fontWeight: FontWeight.bold 
     ), 
   ), 
   Padding(padding: EdgeInsets.all(10)), 
   Icon( 
     FontAwesomeIcons.home, 
     color: Colors.purple, 
     size: 35, 
   ), 
 ], 
), 

Ahora se puede acceder a una nueva clase. Se trata de FontAwesomeIcons. Esto se obtuvo gracias a la recuperación del paquete, realizada justo antes. Para mantener la coherencia, todavía se le asigna un icono en forma de casa. Se cambian los colores. El resto no se mueve.

Antes de examinar la representación visual, es interesante observar las importaciones: 

import 'package:flutter/cupertino.dart';  
import 'package:flutter/material.dart';  
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 

El uso de los CupertinoIcons y del paquete font_awesome_flutter es evidente aquí. De hecho, gracias a las importaciones es posible usar las clases CupertinoIcons y FontAwesomeIcons.

Ahora veamos a qué se parece nuestra pantalla después de agregar este último icono:

images/Manon1.png

Figura 22 - FontAwesomeIcons

2. Imágenes

Para hacer que una aplicación sea agradable en términos de experiencia de usuario, es habitual insertar imágenes. Con Flutter, también hay un widget dedicado: Image.

Para ello, se dispone de varios constructores con nombre. Se corresponden con los diferentes tipos de procedencia de una imagen: las incluidas en el proyecto o las procedentes directamente de la web.

Se tienen en cuenta muchos formatos de imagen: JPEG, PNG, GIF, WebP, Animate WebP, BMP y WBMP.

a. Imágenes del proyecto

Para insertar una imagen directamente en el proyecto, todo lo que tiene que hacer es crear una carpeta en su raíz. En el siguiente ejemplo, se crea una primera carpeta denominada Assets y luego una subcarpeta denominada Images. Para crear una carpeta, todo lo que tiene que hacer es un clic derecho en el nombre del proyecto, ir a New y luego a Directory

images/p217.png

Figura 23 - Creación de carpetas para las imágenes

Para este ejemplo, se utilizará el logotipo de Flutter. El archivo se llama flutter_logo_tm.png. Para insertarlo, basta con que copie/pegue el archivo directamente en la subcarpeta Images que acaba de agregar a la aplicación. Se debería abrir la siguiente ventana:

images/p218.png

Figura 24 - Inserción de un archivo

En esta ventana, es posible cambiar el nombre del archivo. Esta también es una oportunidad para verificar dónde se insertará. Para validar la inserción, simplemente haga clic en OK.

Ahora, para poder utilizar las imágenes, tendremos que volver al archivo pubspec.yaml. En el interior, en la sección «flutter:», tendrá que descomentar el ejemplo de inserción de assets:

# assets: 
#  - images/a_dot_burr.jpeg 
#  - images/a_dot_ham.jpeg 

Tenga en cuenta que se trata de un archivo yaml. Se trata, por lo tanto, de un archivo de configuración, donde la sangría es de crucial importancia. Se debe asegurar absolutamente de que se respete. Por ejemplo, assets se debe introducir con dos espacios a la izquierda.

Para declarar la imagen en los assets, deberá indicar su ruta. Esto es lo que se debe escribir:

flutter: 
 uses-material-design: true 
 
 assets: 
   - Assets/Images/flutter_logo_tm.png 

Una vez realizada esta declaración, es necesario volver a hacer clic en Packages get para que se tenga en cuenta.

images/flutter-c6-i105.PNG

Figura 24 - Packages get

La pregunta es la siguiente: ¿qué pasa con la declaración de una gran cantidad de imágenes?

Obviamente, sería tedioso tener que declarar una gran cantidad de imágenes de esta manera. Por esta razón, es posible declarar solo la carpeta que contiene todas las imágenes, de modo que todas se tendrán en cuenta en una sola línea. Para esto, todo lo que tiene que hacer es indicar la ruta a la carpeta y finalizar la declaración con «/». Para el ejemplo anterior, esto daría como resultado: 

flutter:  
 uses-material-design: true  
  
 assets: 
   - Assets/Images/ 

Ahora que la declaración es efectiva, solo queda añadir esta imagen en el proyecto. El widget Image tiene un constructor con nombre para los assets: Image.asset. Como mínimo, espera la ruta de la imagen.

Image.asset('Assets/Images/flutter_logo_tm.png'), 

La imagen renderizada es demasiado grande. Para ajustar su tamaño, es posible ajustar la propiedad width.

Image.asset(  
   'Assets/Images/flutter_logo_tm.png', 
   width: 150, 
), 

Gracias a esta operación, la imagen es más pequeña, de modo que el aspecto visual de la aplicación ha evolucionado así:

images/cap6_pag65.png

Figura 25 - Inserción de una imagen (assets)

b. Imágenes de la Web

Si bien insertar imágenes directamente en el proyecto es una buena forma de controlar sus recursos, también es posible recuperar una imagen de la web. Esta técnica obviamente es más simple y menos costosa en términos de almacenamiento. Pero presenta un riesgo importante: ver cómo desaparece el recurso y, por tanto, deja de ser accesible. Este es el caso especialmente cuando la aplicación ya no puede conectarse a la red.

Para incluir imágenes de la Web, el widget Image ofrece un constructor llamado Image.network, que espera al menos una URL.

Para seguir con el código ya presentado, se agrega esta línea:

Image.network('https://www.ediciones-eni.com/rsc/shared/images/
logo_ENI.svg'), 

El desafío es recuperar el logotipo de Ediciones ENI que se presenta en el sitio web, en la dirección: https://www.ediciones-eni.com/rsc/shared/images/logo_ENI.svg

En el emulador, la operación funciona correctamente. La representación visual es la siguiente:

images/cap6_pag66.png

Figura 26 - Inserción de una imagen que proviene de la Web

3. Texto

a. Text

En el transcurso de los diversos ejemplos estudiados, los textos aparecieron de manera regular sin que hayan explicados realmente. Se insertan a través del widget Text.

De forma predeterminada, se debe introducir al menos una cadena de caracteres. Se trata del único parámetro obligatorio para el constructor. Sin embargo, existen otros parámetros que le permiten trabajar sobre este texto. Es el caso de style, que ofrece la posibilidad de arreglar su apariencia. La alineación es modulable con la propiedad textAlign. En cuanto a textOverflow, le permite definir qué hacer si el texto es demasiado grande y ocupa más de una línea.

He aquí un ejemplo:

Text(  
 'Este es un texto cualquiera', 
 style: TextStyle( 
   color: Colors.brown, 
   fontWeight: FontWeight.bold, 
   fontSize: 50, 
   fontStyle: FontStyle.italic 
 ),  
 textAlign: TextAlign.left, 
), 

Para este widget, se registra la frase «Este es un texto cualquiera».

b. Formateo del texto

Para empeorar las cosas, la propiedad style recibe un widget TextStyle. Se trata de un widget que, como su nombre indica, le permite dar formato al texto. Es posible trabajar sobre el color (color), el peso de la fuente (fontWeight), su tamaño (fontSize) o su estilo (fontStyle).

La propiedad textAlign le permite definir cómo se colocará el texto. Para esto, todo lo que tiene que hacer es usar el widget TextAlign, que tiene diferentes valores, como center, left, right, etc.

El código anterior se agrega a la aplicación, justo después de los iconos y las imágenes. La pantalla ahora se ve así:

images/cap6_pag68.png

Figura 27 - Implementación de un texto

En la imagen anterior, el texto es relativamente grande y ocupa más de una línea. El uso de overflow permite rectificar esto y fuerza un comportamiento concreto. Aquí, el texto se coloca directamente en la página (en el widget Column para ser precisos) y no tiene restricciones de altura. Por tanto, los efectos de overflow son limitados. Se vuelve realmente útil cuando el texto encaja en un elemento que está restringido en altura y anchura.

Para mostrar las diferentes posibilidades, el código anterior se coloca en un widget Container (este widget se detallará un poco más adelante en este capítulo), limitado en anchura y altura. Este es el código modificado:

Container( 
 width: MediaQuery.of(context).size.width / 1.2, 
 height: 100, 
 color: Colors.black, 
 child: Text(  
   'Este es un texto cualquiera', 
   style: TextStyle( 
       color: Colors.brown, 
       fontWeight: FontWeight.bold, 
       fontSize: 50, 
       fontStyle: FontStyle.italic 
   ), 
   textAlign: TextAlign.left, 
   overflow: TextOverflow.clip 
 ), 
), 

El aspecto visual cambia y el texto, se presentan a partir de ahora de la siguiente manera:

images/cap6_pag68_b.png

Figura 28 - Texto ubicado en un elemento demasiado pequeño

El widget TextOverflow tiene diferentes valores, que se pueden usar en estos casos:

  • Clip: corta el exceso de texto (predeterminado). En el ejemplo, por lo tanto, no hay ningún cambio aparente.

images/cap6_pag70.png

Figura 29 - TextOverflow.clip

  • Ellipsis: la parte del texto que excede la línea se reemplaza por puntos suspensivos ‘…’.

images/cap6_pag70_b.png

Figura 30 - TextOverflow.ellipsis

  • Fade: el texto que excede la línea se desvanece.

images/cap6_pag70_c.png

Figura 31 - TextOverflow.fade

  • Visible: el texto se ve completamente y desborda el elemento padre.

images/cap6_pag71.png

Figura 32 - TextOverflow.visible

c. Elección del tipo de letra

Otro parámetro importante es el tipo de fuente elegido. Para diferenciarse, las aplicaciones a menudo necesitan impulsar la personalización tanto como sea posible. La elección de un tipo de fuente proporciona una identidad visual propia que es esencial para cualquier buena aplicación.

Dentro del widget TextStyle, el constructor acepta un parámetro llamado fontFamily, que se usa para indicar la voluntad de usar otro tipo de letra.

Pero, antes de eso, es necesario respetar algunos pasos preliminares.

Primero, necesita almacenar una fuente en el proyecto. Para continuar con el ejemplo anterior, se crea una segunda subcarpeta denominada Font en la carpeta Assets. La fuente Fruktur de Viktoria Grabowska se descarga de Google Fonts y se copia en la carpeta recién creada.

images/flutter-c6-i165.PNG

Figura 34 - Implementación de un tipo de letra

El siguiente paso consiste en alimentar el archivo pubspec.yaml para declarar este tipo de letra. Al igual que para las imágenes, se debe insertar una línea fonts: seguida de la declaración de su nombre (- family), que se puede utilizar posteriormente en la aplicación y las rutas de los archivos (- assets) asociados a ella.

Por lo tanto, el archivo pubspec.yaml del ejemplo se enriquece y se convierte en:

flutter:  
 uses-material-design: true  
  
 assets: 
   - Assets/Images/  
  
 fonts:  
   - family: Fruktur  
     fonts:  
       - asset: Assets/Fonts/Fruktur-Regular.ttf 

Para esta nueva fuente, se indica el nombre Fruktur:

- family: Fruktur 

Luego se indica la ruta de la fuente y apunta al archivo copiado justo antes en la subcarpeta Fonts.

- asset: Assets/Fonts/Fruktur-Regular.ttf 

Todo lo que tiene que hacer es clic en Packages get nuevamente para actualizar el proyecto.

Todo está en orden para poder trabajar con esta nueva fuente. Para comprobar que funciona correctamente, se insertan dos textos en la aplicación. El primero servirá como punto de referencia y de modelo. Al segundo se le aplicará la nueva fuente. Se agrega este código:

Text(  
 'Fuente por defecto', 
 style: TextStyle(fontSize: 20), 
), 
Text(  
 'Fuente Fruktur', 
 style: TextStyle( 
     fontSize: 20, 
     color: Colors.purple, 
     fontFamily: 'Fruktur' 
 ), 
), 

En el segundo texto, la fuente Fruktur se asigna como valor a fontFamily. La pantalla cambia y muestra:

images/cap6_pag72.png

Figura 35 - Texto con la fuente Fruktur

d. Google Fonts

En la sección anterior se ha descrito la inserción de una fuente distinta a la predeterminada. El procedimiento es bastante engorroso, pero tiene la ventaja de ser relativamente seguro, ya que el tipo de letra está integrado en la aplicación.

Sin embargo, desde hace poco es posible utilizar fuentes de Google Fonts directamente, sin tener que integrarlas en el proyecto. El problema sigue siendo la conexión a Internet. De hecho, si no hay conexión a la red, el tipo de letra no se cargará. Por otro lado, la aplicación es más liviana, ya que no tiene que alojar archivos para las diferentes fuentes utilizadas.

Esta funcionalidad se anunció durante la conferencia Flutter Interact el 11 de diciembre de 2019 en Londres. Hay un paquete disponible en el sitio pub.dev con el nombre google_fonts 1.1.0.

Para encontrar el paquete, simplemente siga este enlace:

https://pub.dev/packages/google_fonts

Para usarlo, debe comenzar por agregarlo a las dependencias del archivo. pubspec.yaml

Dependencies:  
 flutter:  
   sdk: flutter  
  
 cupertino_icons: ^0.1.2  
 font_awesome_flutter: ^8.5.0  
 google_fonts: ^1.1.0 

Una vez hecho esto, no olvide hacer clic en el enlace Packages get para que el paquete se descargue y se pueda usar más tarde.

En el archivo main.dart, ahora es necesario agregar la importación que permitirá la manipulación de este paquete:

import 'package:google_fonts/google_fonts.dart'; 

A partir de ahí, simplemente llame al widget GoogleFonts en un texto. Para ilustrar esto, añadiremos el texto «Fuente Pacifico» al ejemplo anterior. La propiedad style llama al widget GoogleFonts y luego especifica la fuente con la que hay que trabajar.

Es posible añadir una personalización adicional gracias a TextStyle para modificar el color, el tamaño, etc.

Text(  
   'Fuente Pacifico', 
   style: GoogleFonts.pacifico( 
     textStyle: TextStyle( 
       fontSize: 20, 
       color: Colors.blue 
     ), 
   ), 
), 

Esta es la representación visual final de la pantalla:

images/cap6_pag69.png

Figura 36 - Implementación de una Google Font

4. Layouts (diseño de página)

Para colocar los elementos correctamente, Flutter integra una gran variedad de widgets. Pueden tomar la forma de contenedores o pueden proporcionar instrucciones sobre la ubicación de los widgets hijos. Algunos de ellos ya se han visto en los diversos ejemplos presentados, pero sin que se hayan explicado realmente.

a. Center

Center es uno de los primeros widgets descubiertos. Es parte del proyecto de demostración y se utiliza con regularidad. Como sugiere su nombre, está destinado a centrar el widget que será su hijo, tanto en cuanto a la altura como a la anchura.

Es posible jugar con la dimensión usando widthFactor y heightFactor. Representan coeficientes. Esto se aplica al elemento hijo. Por ejemplo, si heightFactor es igual a 3, entonces la altura de Center será tres veces la altura del widget hijo que contiene.

He aquí un código de muestra para ilustrar este widget:

Center( 
child: null, 
heightFactor: 3); 

b. Column

El widget Column también se ha utilizado mucho desde el inicio. Está integrado en la aplicación básica con el contador de incrementos.

Su particularidad es que permite alinear verticalmente una lista de widgets hijos. Sin embargo, no permite el desplazamiento en este eje (ver capítulo Listas y mallas, los ListView). Los hijos se espacian en la columna según la propiedad MainAxisAlignment. Sus diferentes valores posibles son:

  • Start: los elementos se agrupan en la parte superior de la columna.

  • End: los elementos se agrupan en la parte inferior de la columna.

  • Center: los elementos se agrupan en la parte central de la columna.

  • SpaceEvenly: los elementos se reparten proporcionalmente en la columna con márgenes equivalentes entre ellos y con el margen.

  • SpaceAround: los elementos se reparten proporcionalmente en la columna con márgenes reducidos en los extremos.

  • SpaceBetween: los elementos se reparten proporcionalmente en la columna sin márgenes en los extremos.

Para visualizar mejor las diferencias entre estas posiciones y poner en práctica las columnas, este código de ejemplo se escribe en una nueva aplicación:

Column( 
 mainAxisAlignment: MainAxisAlignment.start, 
 children: <Widget>[ 
   Icon( 
     Icons.android, 
     color: Colors.amber, 
   ), 
   Icon( 
     Icons.home, 
     color: Colors.amber, 
   ), 
   Icon( 
     Icons.build, 
     color: Colors.amber, 
   ), 
   Icon( 
     Icons.phone, 
     color: Colors.amber, 
   ), 
   Icon( 
     Icons.group, 
     color: Colors.amber, 
   ) 
 ], 
), 
Column( 
 mainAxisAlignment: MainAxisAlignment.end, 
 children: <Widget>[ 
   Icon( 
     Icons.android, 
     color: Colors.deepOrange, 
   ), 
   Icon( 
     Icons.home, 
     color: Colors.deepOrange, 
   ), 
   Icon( 
     Icons.build, 
     color: Colors.deepOrange, 
   ), 
   Icon( 
     Icons.phone, 
     color: Colors.deepOrange, 
   ), 
   Icon( 
     Icons.group, 
     color: Colors.deepOrange, 
   ) 
 ], 
), 
Column( 
 mainAxisAlignment: MainAxisAlignment.center, 
 children: <Widget>[ 
   Icon( 
     Icons.android, 
     color: Colors.blue, 
   ), 
   Icon( 
     Icons.home, 
     color: Colors.blue, 
   ), 
   Icon( 
     Icons.build, 
     color: Colors.blue, 
   ), 
   Icon( 
     Icons.phone, 
     color: Colors.blue, 
   ), 
   Icon( 
     Icons.group, 
     color: Colors.blue, 
   ) 
 ], 
), 
Column( 
 mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
 children: <Widget>[ 
   Icon( 
     Icons.android, 
     color: Colors.red, 
   ), 
   Icon( 
     Icons.home, 
     color: Colors.red, 
   ), 
   Icon( 
     Icons.build, 
     color: Colors.red, 
   ), 
   Icon( 
     Icons.phone, 
     color: Colors.red, 
   ), 
   Icon( 
     Icons.group, 
     color: Colors.red, 
   ) 
 ], 
), 
Column( 
 mainAxisAlignment: MainAxisAlignment.spaceAround, 
 children: <Widget>[ 
   Icon( 
     Icons.android, 
     color: Colors.purple, 
   ), 
   Icon( 
     Icons.home, 
     color: Colors.purple, 
   ), 
   Icon( 
     Icons.build, 
     color: Colors.purple, 
   ), 
   Icon( 
     Icons.phone, 
     color: Colors.purple, 
   ), 
   Icon( 
     Icons.group, 
     color: Colors.purple, 
   ) 
 ], 
), 
Column( 
 mainAxisAlignment: MainAxisAlignment.spaceBetween, 
 children: <Widget>[ 
   Icon( 
     Icons.android, 
     color: Colors.green, 
   ), 
   Icon( 
     Icons.home, 
     color: Colors.green, 
   ), 
   Icon( 
     Icons.build, 
     color: Colors.green, 
   ), 
   Icon( 
     Icons.phone, 
     color: Colors.green, 
   ), 
   Icon( 
     Icons.group, 
     color: Colors.green, 
   ) 
 ], 
), 

En este código, hay seis columnas y cada una tiene un valor de MainAxisAlignment diferente. Para hacer visible esta diferencia, cada columna incorpora los mismos cinco iconos (el robot Android, una casa, una llave inglesa, un teléfono y un grupo de personas).

Para que este código esté completo, aún faltan algunas cosas y en particular las filas que serán necesarias. Este es precisamente el tema que se aborda en la siguiente sección. A la espera de descubrirlos, a continuación se muestra la representación visual de la aplicación, que permite visualizar realmente los efectos de las distintas alineaciones definidas.

images/cap6_pag80.png

Figura 37 - Row y Column con diferentes alineaciones

c. Row

El widget Row es muy similar a Column, excepto que el eje en el que se colocan los widgets hijos ya no es vertical, sino horizontal. Le permite integrar una lista de widgets hijos (children). No se pretende crear un desplazamiento horizontal, sino simplemente distribuir estos hijos en el espacio que se le asigna. Tiene una propiedad mainAxisAlignment, como Column, cuyos posibles valores son los mismos.

Para entender la implementación, a continuación se muestra otro extracto del código de la figura 37. Este se refiere a las columnas de iconos y muestra su integración dentro de la misma fila (Row):

Row( 
 mainAxisAlignment: MainAxisAlignment.spaceAround, 
 children: <Widget>[ 
   Column( 
     ... 
   ),  
   Column( 
     ... 
   ), 
   Column( 
     ... 
   ), 
   Column( 
     ... 
   ), 
   Column( 
     ... 
   ), 
   Column( 
     ... 
   ), 
 ], 
), 

d. Expanded

El widget Expanded es muy interesante, especialmente cuando se usa junto con las filas (Row) y las columnas (Column). Su función es ampliar, modificar la proporción que toma un hijo en una Column o una Row. Para ello, tiene una propiedad denominada flex que servirá como coeficiente.

En una columna con dos hijos, si ambos están agrupados por el mismo Expanded con un valor flex de 1 para el primero y 2 para el segundo, entonces el segundo hijo ocupará el doble de espacio en la columna que su hermano. 

En el ejemplo presentado en la figura 37, de arriba abajo, en la columna principal podemos distinguir al menos tres elementos importantes: el título (Alignements:), el texto de las diferentes alineaciones posibles (start, end, center, etc.) y las columnas de iconos. Visualmente, es obvio que la parte inferior, donde se ubican los iconos, es mucho más grande que las otras dos. Esto es lo que produce un Expanded. Este es el código correspondiente:

Column( 
 mainAxisAlignment: MainAxisAlignment.spaceAround, 
 children: <Widget>[ 
   Padding( 
     padding: EdgeInsets.only(top: 20), 
   ), 
   Text(  
     'Alineaciones:', 
     style: TextStyle( 
       fontSize: 30, 
       fontWeight: FontWeight.bold, 
       color: Colors.white, 
     ), 
   ), 
   Padding( 
     padding: EdgeInsets.only(top: 20), 
   ), 
   Expanded( 
       flex: 4, 
       child: Row( 
         ... 
       ) 
), 
   Divider( 
     color: Colors.white, 
   ), 
   Expanded( 
     flex: 20, 
     child: Row( 
        ... 
     ), 
   ) 
 ], 
), 

El área de los títulos toma un valor flex de 4, mientras que el de la línea de los iconos es 20. Sin estos valores, las líneas tendrían la misma proporción. Por tanto, Expand es una herramienta muy práctica para gestionar el diseño en una pantalla.

e. Container

El Container es simplemente un contenedor de tamaño definido, en el que se pueden colocar otros widgets. Por defecto, su dimensión es la de su hijo y no tiene color, es invisible.

Por otro lado, es posible darle un color gracias a la propiedad color. Entonces se hace visible. La ventaja del Container es que es posible darle un estilo específico usando una propiedad: decoration. Puede aceptar los widgets BoxDecoration o ShapeDecoration. Gracias a ellos, es posible definir bordes, por ejemplo.

El tamaño del Container es ajustable con varias propiedades. Es posible jugar con el padding, que se corresponde con el margen interno. De la misma forma, será necesario utilizar margin para gestionar el espacio del contenedor frente a los widgets que lo rodean.

Finalmente, tiene dos propiedades esenciales: width y height. Le permiten definir un tamaño para el ancho y el alto.

He aquí un primer ejemplo de contenedor:

@override 
Widget build(BuildContext context) {  
 return Scaffold( 
   appBar: AppBar( 
     title: Text(title), 
   ), 
   body: Center( 
     child: Column( 
       mainAxisAlignment: MainAxisAlignment.center, 
       children: <Widget>[ 
         Container( 
           child: Text('Contenedor 1'), 
           color: Colors.blue, 
           margin: EdgeInsets.only(bottom: 30), 
         ), 
       ], 
     ), 
   ), 
 ); 
} 

En este ejemplo, un Container que contiene un texto se colorea de azul. Se establece un margen inferior para poder implementar otros widgets sin que se peguen entre sí.

Como muestra la representación visual, el Container toma su contenido como su dimensión:

images/cap6_pag84.png

Figura 38 - Ejemplo de Container básico

Añadamos un segundo ejemplo. Esta vez, este contenedor está dimensionado con precisión en anchura y altura. Es interesante observar que su contenido, en este caso el texto, se ve obligado a adaptarse a esta nueva dimensión. Antes de detenernos en el aspecto visual, mostraremos el código utilizado:

Container( 
 child: Text('Contenedor 2'), 
 color: Colors.green, 
 height: 40, 
 width: 40, 
 margin: EdgeInsets.only(bottom: 30), 
), 

Y la representación visual es:

images/cap6_pag84_b.png

Figura 39 - Contenedor dimensionado

Un tercer ejemplo explora las posibilidades de la propiedad decoration, utilizando el widget BoxDecoration. La forma que se le da al Container ya no es rectangular, sino redonda. De forma predeterminada y si ningún otro elemento lo restringe, la dimensión del contenedor se establece en el valor más pequeño presente entre la altura y el ancho del widget hijo. En el caso del texto que es contenido aquí, el valor más pequeño es, obviamente, la altura.

Container( 
 child: Text('Contenedor 3'), 
 decoration: BoxDecoration( 
   shape: BoxShape.circle, 
   color: Colors.red 
 ), 
 margin: EdgeInsets.only(bottom: 30), 
), 

En la representación visual, el contenedor coloreado de rojo es un círculo cuyo diámetro se corresponde con la altura del texto:

images/cap6_pag85.png

Figura 40 - Contenedor con una decoration BoxDecoration

El siguiente ejemplo retoma el código anterior y lo evoluciona para que el círculo rojo abarque todo el widget Text. Para realizar esta operación, todo lo que tiene que hacer es jugar con el margen interno, el padding:

Container( 
 child: Text('Contenedor 4'), 
 decoration: BoxDecoration( 
     shape: BoxShape.circle, 
     color: Colors.red 
 ), 
 padding: EdgeInsets.all(30), 
 margin: EdgeInsets.only(bottom: 30), 
), 

El resultado es un poco más satisfactorio:

images/cap6_pag86.png

Figura 41 - Contenedor con una decoration BoxDecoration dimensionada

Finalmente, el último ejemplo explorará el widget ShapeDecoration para implementar un borde con varios colores.

Container( 
 child: Text('Contenedor 5'), 
 padding: EdgeInsets.all(15), 
 decoration: ShapeDecoration( 
     color: Colors.white, 
     shape: Border.all( 
       color: Colors.red, 
       width: 8, 
     ) + Border.all( 
       color: Colors.green, 
       width: 8.0, 
     )+ Border.all( 
       color: Colors.blue, 
       width: 8.0, 
     ), 
 ), 
), 

El resultado es el siguiente:

images/cap6_pag87.png

Figura 42 - Contenedor con los bordes de diferentes colores

Para terminar con los contenedores, debemos mencionar una última propiedad interesante: alignment. Permite ubicar con precisión el hijo en el Container. Es posible recurrir a la clase Alignment, que ofrece varios valores predeterminados:

  • topLeft (arriba a la izquierda)

  • topRight (arriba a la derecha)

  • topCenter (centrado arriba)

  • bottomLeft (abajo a la izquierda)

  • bottomCenter (centrado abajo)

  • bottomRight (abajo a la derecha)

  • center (centrado)

  • centerLeft (centrado a la izquierda)

  • centerRight (centrado a la derecha)

He aquí un ejemplo de su implementación:

Container( 
 height: 150, 
 width: 150, 
 color: Colors.blueGrey, 
 child: Icon( 
   Icons.directions_car, 
   color: Colors.white, 
   size: 40, 
 ), 
 alignment: Alignment.bottomRight, 
), 

En este código, un Container que forma un cuadrado gris con una longitud lateral de 150 píxeles incorpora un icono de un coche. De forma predeterminada, se encuentra en el centro de su widget padre.

Al dar valor al parámetro alignment con Alignment.bottomRight, ahora se coloca en la parte inferior derecha:

images/flutter-c6-i210.PNG

Figura 43 - Alineación de un hijo en un Container

Si las opciones propuestas por defecto no son adecuadas, Alignment, a través de su constructor, también acepta valores precisos para las abscisas (x) y las ordenadas (y). Van de -1 a 1 según este gráfico

images/flutter-c6-i215.PNG

Figura 44 - Valores de alignment

Retomando el código anterior, es posible hacer evolucionar la alineación de esta manera:

Container( 
 height: 150, 
 width: 150, 
 color: Colors.blueGrey, 
 child: Icon( 
   Icons.directions_car, 
   color: Colors.white, 
   size: 40, 
 ), 
 alignment: Alignment(-0.8,0.7), 
), 

Esta vez, la posición del coche es -0,8 en el eje de abscisas; por lo tanto, cerca del borde izquierdo, y 0,7 en el de las ordenadas, es decir, ubicado bastante abajo.

images/flutter-c6-i220.PNG

Figura 45 - Alineación específica de un hijo en un Container

f. Padding

El Padding es un widget que ya se ha encontrado en algunos ejemplos. Se utiliza para gestionar un espacio. Potencialmente, también puede acoger un hijo.

No hay necesariamente mucha diferencia entre este widget y un Container. Sin embargo, es bastante fácil de implementar.

En el constructor, solo hay dos parámetros. El primero, obligatorio, es el padding, que se corresponde con un objeto heredado de la clase EdgeInsetsGeometry. El segundo, opcional, es el child, es decir, un widget hijo.

EgdeInsetsGeometry es una clase abstracta y, por lo tanto, no permite la creación de una instancia. Por otro lado, cuenta con dos clases hijas que, por su parte, pueden ser EdgeInsets y EdgeInsetsDirectionnal.

La clase EdgeInsets tiene varios constructores con nombre, que vienen a satisfacer diferentes necesidades:

  • EdgeInsets.all(double valor): permite, con un solo valor, aplicar un espacio a todos los lados (superior, inferior, izquierdo y derecho).

Padding de 20 píxeles para cada lado:

Padding(padding: EdgeInsets.all(20)), 
  • EdgeInsets.fromLTRB(double izquierda, double arriba, double derecha, double abajo): permite indicar un espacio para cada lado.

Padding de 20 píxeles a la izquierda, 10 en la parte superior, 5 a la derecha y 30 en la parte inferior:

Padding(padding: EdgeInsets.fromLTRB(20, 10, 5, 30)), 
  • EdgeInsets.only({double izquierda: 0.0, double arriba: 0.0, double derecha: 0.0, double abajo: 0.0}): le permite aplicar una desviación a uno o más lados. Los lados sin valor tomarán el valor 0.0 por defecto.

Padding de 10 píxeles a la izquierda y 20 a la derecha (y, por lo tanto, 0 en la parte superior y abajo):

Padding(padding: EdgeInsets.only(left: 10, right: 20)), 
  • EdgeInsets.symmetric({double vertical: 0.0, double horizontal: 0.0}): se utiliza para especificar una desviación vertical (superior e inferior) u horizontal (izquierda y derecha). El parámetro sin valor tomará el valor 0.0 por defecto.

Padding vertical de 10 píxeles y 30 en horizontal:

Padding(padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30)), 

La clase EdgeInsetsDirectionnal se basa en los mismos principios. La diferencia notable reside en el hecho de que ya no hay ni izquierda ni derecha, sino un inicio y un final.

g. Divider

El Divider es un widget bastante simple. Su función es crear una línea divisoria horizontal. Se utiliza habitualmente en los Drawer o las listas (consulte el capítulo Listas y mallas).

Su constructor permite trabajar sobre diferentes propiedades, como el color de la línea, su grosor, margen vertical o sangría.

He aquí un ejemplo:

Divider( 
 color: Colors.blue, 
 indent: 40, 
 endIndent: 40, 
 thickness: 2, 
 height: 30, 
), 

En este código, agregamos un Divider azul (color). Su indentación desde el margen izquierdo (indent) es de 40 píxeles, al igual que la sangría a la derecha (endIntent). El grosor de la línea (thickness) se acentúa un poco (por defecto, su valor es 1). Finalmente, se aumenta el espacio total del Divider

Este Divider se ha agregado a la pantalla Container para crear una separación entre los dos últimos. A modo de comparación, por encima se inserta un segundo Divider, configurado de forma predeterminada (no se llama a ningún parámetro del constructor).

Esta es la representación visual:

images/cap6_pag89.png

Figura 46 - Divider

h. Stepper

El widget Stepper permite gestionar una secuencia paso a paso. El Stepper evoluciona a medida que pasa el tiempo.

Para descubrir esta gran herramienta, se ha iniciado un nuevo proyecto. La clase principal extiende StatefulWidget (tema mencionado en el capítulo Los estados) para ser dinámica:

import 'package:flutter/material.dart';  
  
void main() => runApp(MyApp());  
  
class MyApp extends StatelessWidget { ...