Flutter Widget Diseño de pantallas
Ir a la navegación
Ir a la búsqueda
Imagen obtenida de este sito web
En color rojo tenemos el padding y en color verde tenemos el margin
Sumario
Introducción
- En las secciones anteriores vimos algunos de los widgets que podemos emplear para visualizar contenidos en una pantalla.
- En esta sección vamos a ver widgets que nos permiten colocar a otros widgets en ciertas posiciones en base a las propiedades que disponga el Widget.
- Vienen a ser el equivalente a divs en HTML.
- Asociado a este punto vamos a ver como identificar y solucionar los problemas que podamos tener de posicionamiento cuando los widgets no quepan en el espacio asignado por su widget contenedor.
Column / Row
- Más información en:
- Estos widgets permiten 'colocar' en forma de fila o columna el cojunto de Widget que se encuentran dentro.
- El widget Column coloca los widgets uno debajo del otro (en una línea vertical)
- El widgert Row coloca los widgets uno al lado del otro (en una línea horizontal).
- No poseen Scroll y por lo tanto los Widgets que lo conforman no deberían ocupar más espacio que el espacio del contenedor donde se encuentren.
- En caso de necesitar un scroll tendríamos que hacer uso de un Widget ListView como ya vimos en la sección anterior o de un SingleChildScrollView.
- Creamos una nueva página en la carpeta pages de nombre columnrow_page.dart
- En todas las páginas haremos lo mismo:
- Importamos la librería de 'material' (recordar que existe un snippet que lo hace automático).
- Creamos una clase (ColumnRowPage) que derive de StatelessWidget (al hacerlo será necesario implementar el método build, ya os da un aviso el Android Studio).
- Los parámetros más usados son:
- children[]: Conjunto de Widget que van a aparecer visualmente.
- MainAxisAlignment: Indicamos como van a posicionarse los children con respecto al eje principal.
- En el caso de Column, el eje principal es el vertical y en el caso de Row, es el eje horizontal.
- CrossAxisAlignment: Indicamos como van a posicionarse los children con respecto al eje perpendicular al principal.
- En el caso de Column, el eje Cross es el horizontal y en el caso de Row, es el eje vertical.
- Veamos una imagen en la que va a quedar más claro a que se refiere cada eje en cada caso:
- Veamos un ejemplo sencillo en el que emplearemos un Column para colocar texto visualmente en forma de filas (recordar que Column coloca a los widget en una línea vertical).
- Dejamos los valores por defecto para cada eje y veremos que los elementos empiezan a colocarse en la parte superior (MainAxisAlignment.start) y horizontalmente están centrados con respecto al espacio que ocupan (CrossAxisAlignment.center), cogiendo como referencia la fila que ocupa más ancho)
import 'package:flutter/material.dart'; class ColumnRowPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Ejemplo de uso de Row y Colum'), ), body: Column( children: [ Text('Texto de la primera fila',style: TextStyle(backgroundColor: Colors.yellow),), Text('Texto de la segunda fila. Este texto es más largo.',style: TextStyle(backgroundColor: Colors.red),), Text('Texto de la tercera fila',style: TextStyle(backgroundColor: Colors.brown),), ], ) ); } }
- Ahora veamos un ejemplo modificando alguno de los parámetros de los ejes:
import 'package:flutter/material.dart'; class ColumnRowPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Ejemplo de uso de Row y Colum'), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('Texto de la primera fila',style: TextStyle(backgroundColor: Colors.yellow),), Text('Texto de la segunda fila. Este texto es más largo.',style: TextStyle(backgroundColor: Colors.red),), Text('Texto de la tercera fila',style: TextStyle(backgroundColor: Colors.brown),), ], ) ); } }
- En este ejemplo hacemos que el contenido se centre en su eje principal (verticalmente). Funciona debido a que el Column se expande en su eje main.
- Por otra parte, en su eje cross (horizontalmente) al usar textos, estos hacen que el espacio que reserva el row se adapte al tamaño de los mismos (no los expande) y por eso al situarlos al final horizontalmente, lo hacen con respecto al texto más largo. Si rodeamos el Column con un Container y le damos un ancho, la alineación la harían con respecto al tamaño en ancho de dicho container.
- Como en Html, estos widgets son 'contenedores' y podemos tener como parte de un children otro contenedor, por lo tanto podemos tener dentro de Row y Column, otros Row y Column de forma anidada...
- Veamos un ejemplo.
- Fijarse que por defecto, tanto Row como Column toman como 'referecia' el widget que ocupa más espacio.
- En el siguiente ejemplo, el Widget Row está alineado en el CrossAxis al final (CrossAxisAlignment.end) y como uno de los iconos es más alto, el resto se sitúa en la parte baja del mismo.
import 'package:flutter/material.dart'; class ColumnRowPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Ejemplo de uso de Row y Colum'), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text('Texto de la primera fila',style: TextStyle(backgroundColor: Colors.yellow),), Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ Icon(Icons.favorite, size: 50,), Icon(Icons.audiotrack, size: 100,), Icon(Icons.cast, size:50,), ], ), Text('Texto de la tercera fila',style: TextStyle(backgroundColor: Colors.brown),), ], ) ); } }
Rows y Columns con widget que se expanden
- Row no tiene límite en su eje main axis (horizontal) y por eso si ponemos muchos elementos podemos quedarnos sin espacio.
- Verticalmente intenta darle el espacio que necesita y si el children se expande (como un Container) le dará todo el espacio vertical hasta su contenedor padre.
- Column no tiene límite en su eje main axis (verticalmente) y por eso si ponemos muchos elementos podemos quedarnos sin espacio.
- Horizontalmente intenta darle el espacio que necesita y si el children se expande (como un Container) le dará todo el espacio horizontal hasta su contenedor padre.
- Imaginemos que tenemos un row con un listview en su interior con la dirección de scroll horizontal (os dará un error):
return Scaffold( appBar: AppBar( title: const Text('ROWS_COLS'), ), body: Row( children: [ ListView( scrollDirection: Axis.horizontal, children: [ Text('1111111'), Text('2222222'), Text('3333333'), ], ), );
- ¿ Por qué es necesario un width en la lista ?
- Porque en el eje main axis del Row no se establece límites y por tanto la lista se intenta expandar horizontalmente para poder añadir sus elementos y al no encontrar límites nos dará un error. Por lo tanto, siempre deberemos poner un límite en el eje de scroll de una lista cuando se encuentre dentro de un row/column
- Verticalmente (que es el Cross Axis del Row) no tenemos problema ya que la lista se va a expandir hasta lo máximo que permite su padre que es el scaffold (en este ejemplo)
return Scaffold( appBar: AppBar( title: const Text('ROWS_COLS'), ), body: Row( children: [ Container( width: 100, color: Colors.blue, child: ListView( scrollDirection: Axis.horizontal, children: [ Text('1111111'), Text('2222222'), Text('3333333'), ], ), ], ), );
- Cambiemos ahora la orientación del scroll de la lista a vertical (dará un error):
return Scaffold( appBar: AppBar( title: const Text('ROWS_COLS'), ), body: Row( children: [ ListView( scrollDirection: Axis.vertical, children: [ Text('1111111'), Text('2222222'), Text('3333333'), ], ), );
- En este caso necesitamos establecer limitaciones en el ancho y en el alto. ¿ Por qué ?
- En el alto ya que la lista se va a expandir para dar espacio a los elementos que va a añadir (verticalmente) y no tiene un límite de espacio establecido.
- En el ancho ya que la lista también se expande horizontalmente, pero a diferencia del caso anterior, al estar dentro de un row no tiene límite horizontal al ser el main axis del row.
return Scaffold( appBar: AppBar( title: const Text('ROWS_COLS'), ), body: Row( children: [ Container( width: 100, height: 200, color: Colors.blue, child: ListView( scrollDirection: Axis.horizontal, children: [ Text('1111111'), Text('2222222'), Text('3333333'), ], ), ], ), );
Expanded
- Asociado al widget Row y Column se suele utilizar el widget Expanded, que permite 'expandir' el tamaño del widget hasta el tamaño de su contenedor en su MainAxis.
- Recordar que en Android Studio y en otros ide´s podéis 'envolver' un Widget con otro Widget, situándonos sobre él y pulsando las teclas ALT+Enter aparece la opción Wrap...
- Entre los parámetros a enviar tenemos:
- child: Es el widget que queremos que se expanda. Obligatorio.
- flex: Este parámetro es equivalente a emplear un LinearLayout en Android Studio. Cuando tenemos varios widget´s que se expanden, permite darle un peso a cada uno de ellos para hacer que uno ocupe más que el otro.
- Veamos un ejemplo:
- En este caso, estamos expandiendo en el widget Column, el widget Row para que ocupe todo el espacio entre los dos textos.
- Por otro lado, dentro del Row, estamos expandiendo dos iconos y hacemos que el grande ocupe 3/6 partes del espacio de la fila y el icono de la derecha las 2/6 partes.
- Para poder visualizar el espacio que están ocupando, envolví el Widget Icon con un Container, que explicaré en las siguientes secciones y que nos permite establecer un color de fondo.
import 'package:flutter/material.dart'; class ColumnRowPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Ejemplo de uso de Row y Colum'), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text('Texto de la primera fila',style: TextStyle(backgroundColor: Colors.yellow),), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded(child: Container(child: Icon(Icons.favorite, size: 50,),color: Colors.yellow,),flex: 1,), Expanded(child: Container(child: Icon(Icons.audiotrack, size: 100,),color: Colors.blue),flex:3), Expanded(child: Container(child: Icon(Icons.cast, size:50,),color: Colors.red,),flex: 2,), ], ), ), Text('Texto de la tercera fila',style: TextStyle(backgroundColor: Colors.brown),), ], ) ); } }
- Nota: Como podéis comprobar Expanded sólo afecta el eje principal. Si quisiérais que un icono ocupe todo el alto disponible, podéis modificar la propiedad height del Container y darle el valor double.infinity.
Spacer
- Más información en https://api.flutter.dev/flutter/widgets/Spacer-class.html
- Asociado a los Widget Row y Column, podemos hacer uso de este widget para establecer una separación entre los widgets que conforman el Row/Column.
- Parámetros a emplear => Parámetro flex:
- Si no empleamos el parámetro flex, cogerá el espacio en blanco total que encuentre en el MaixAxis.
- Si explemamos el parámetro flex, dividirá el espacio vacío total en el MainAxis por la suma de todos los Spacer que encuentre con sus respectivos flex (si no se establece tendrá el valor de 1) y asignará a cada Spacer el número flex indicado.
- Veamos un ejemplo:
import 'package:flutter/material.dart'; class ColumnRowPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Ejemplo de uso de Row y Colum'), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( children: [ Icon(Icons.favorite, size: 30,), Spacer(flex: 3,), Icon(Icons.audiotrack, size: 30,), Spacer(flex: 2,), Icon(Icons.cast, size:30,), Spacer(flex: 2,), Icon(Icons.access_alarm, size:30,), ], ), ], ) ); } }
Container
- Más información en https://api.flutter.dev/flutter/widgets/Container-class.html
- Ejercicios de diseño empleando Container y sus propiedades: https://medium.com/flutter-espa%C3%B1a/desaf%C3%ADos-con-flutter-container-38842d23e2b7
- Creamos una nueva página en la carpeta pages de nombre container_page.dart
- En todas las páginas haremos lo mismo:
- Importamos la librería de 'material' (recordar que existe un snippet que lo hace automático).
- Creamos una clase (ContainerPage) que derive de StatelessWidget (al hacerlo será necesario implementar el método build, ya os da un aviso el Android Studio).
- Contenedor que permite 'envolver' cualquier widget y con el cual podemos establecer un tamaño, padding, margin, color de fondo y borde entre otras propiedades.
- La diferencia entre margin y padding es la misma que en HTML con sus div´s.
- Todos los contenidos conforman un contenedor de forma rectangular que lo rodea. El marign es el espacio que hay entre dos contenedores y el padding es el espacio que hay entre el contenido y su contenedor. Más información en este enlace.
- Al establecer un margin y un padding podemos indicar en qué dirección queremos que se aplica esa modificación.
- Para facilitarnos el establecer estos valores haremos uso de la clase EdgeInsets que permite emplear diferentes métodos:
- all: establece un margin-padding en todas las direcciones.
- symmetric: establece un margin-padding simétrico, es decir, se envían dos valores, uno de ellos establece el margin-padding inferior y superior y otro establece el margin-padding derecho e izquierdo.
- only: indicamos sobre que lados vamos a establecer el margin-padding.
- Nota importante:
- Si tenemos un contenedor dentro de otro contenedor, el contenedor interior se expandirá hasta ocupar todo el espacio del contenedor padre. Por lo tanto si intentamos alinear el contenido no funcionará. Debemos establecer un tamaño más pequeño en el contenedor hijo y desde el contenedor padre, donde debe situarse el contenedor hijo con la propiedad alignment.
- Se un contenedor tiene la propiedad alignmet, el child del mismo se situará en lo indicado por alignment .
Container( margin: EdgeInsets.all(10.0), // Establece un margin de 10 en todas las direcciones alignment: Alignment.center, // Expande y centra color: Colors.yellow, child: Text('CONTENEDOR 3'), )
- Al poner alignment, debe de posicionar el contenedor con respecto a su contenedor padre por lo que debe determinar que tamaño tiene. En ese caso se expandirá hasta encontrar un límite.
- Veamos un ejemplo.
- En este caso voy a hacer uso de la clase MediaQuery para obtener información del ancho de la pantalla y así poder establecer un ancho de uno de los contenedores en base a un porcentaje de dicho ancho.
import 'package:flutter/material.dart'; class ContainerPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Ejemplo de contenedores'), ), body: _conjuntoContenedores(context) // Le pasamos el context para establecer un ancho en porcentaje de la pantalla, lo necesita la clase MediaQuery ); } Widget _conjuntoContenedores(BuildContext context){ return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( width: double.infinity, // De ancho todo el espacio disponible height: 50.0, color: Colors.red, margin: EdgeInsets.only(bottom: 20.0), // Establece un margin sólo en la parte inferior padding: EdgeInsets.all(10.0), // Establece un padding en las cuatro direcciones child: Text('CONTENEDOR 1',textAlign: TextAlign.center,), ), Expanded( flex: 2, child: Container( padding: EdgeInsets.only(top:20.0,left: 20.0), margin: EdgeInsets.only(top: 30.0), width: MediaQuery.of(context).size.width*0.5, color: Colors.blue, child: Text('CONTENEDOR 2'), ), ), Expanded( flex: 2, child: Container( margin: EdgeInsets.all(10.0), // Establece un margin de 10 en todas las direcciones alignment: Alignment.center, // Expande horizontalmente y centra color: Colors.yellow, child: Text('CONTENEDOR 3'), ), ), ], ); } }
- El widget Container también tiene entre sus propiedades un constraint de tipo BoxContraints, con lo que podemos establecer un ancho y alto al igual que hacemos con width - height. La diferencia es que de esta forma el tamaño del contenedor puede variar entre un max-min.
Container( constraints: BoxConstraints( maxHeight: 100, maxWidth: 100, minHeight: 50, minWidth: 50, ), color: Colors.red, ),
Align / Center
- Más información en:
- Creamos una nueva página en la carpeta pages de nombre aligncenter_page.dart
- En todas las páginas haremos lo mismo:
- Importamos la librería de 'material' (recordar que existe un snippet que lo hace automático).
- Creamos una clase (AlignCenterPage) que derive de StatelessWidget (al hacerlo será necesario implementar el método build, ya os da un aviso el Android Studio).
- Normalmente son usados cuando queramos centrar o alinear un único widget. La diferencia entre Align y Center es que con Align podemos establecer donde situar al Widget dentro de su contenedor (arriba-abajo, izquierda-derecha) mientras que con Center siempre lo va a centrar.
import 'package:flutter/material.dart'; class AlignCenterPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Ejemplo de Align y Center'), ), body: _conjuntoContenedores() ); } Widget _conjuntoContenedores(){ return Column( children: [ Center( child: Container( height: 150.0, width: 150.0, color: Colors.yellow, child: Center(child: Icon(Icons.access_alarm,size: 40.0,)), ), ), Align( alignment: Alignment.centerRight, // Es igual que sea top o botton. Sólo hace uso del right al usar Column child: Container( height: 150.0, width: 150.0, color: Colors.red, child: Align( alignment: Alignment.bottomLeft, // Sitúa al icono abajo a la izquierda con respecto a su contenedor child: Icon(Icons.ac_unit,size: 40.0,), ), ), ), Expanded( child: Container( // Creo un container que ocupe todo el espacio restante color: Colors.brown, child: Align( alignment: Alignment.bottomRight, // Mueve el container verde abajo a la derecha child: Container( height: 150.0, width: 150.0, color: Colors.green, child: Align( alignment: Alignment.bottomLeft, // Mueve el icono dentro del contenedor verde, abajo a la izquierda child: Icon(Icons.ac_unit,size: 40.0,), ), ), ), ), ), ], ); } }
SizedBox
- Más información en https://api.flutter.dev/flutter/widgets/SizedBox-class.html
- Creamos una nueva página en la carpeta pages de nombre sizedbox_page.dart
- En todas las páginas haremos lo mismo:
- Importamos la librería de 'material' (recordar que existe un snippet que lo hace automático).
- Creamos una clase (SizedBoxPage) que derive de StatelessWidget (al hacerlo será necesario implementar el método build, ya os da un aviso el Android Studio).
- Este Widget permite crear un contenedor vacío con un tamaño determinado.
- Puede ser utilizado para 'separar' widgets unos de otros.
- Ya vimos en el caso de las Listas que podíamos definir un Widget para separar los elementos de la misma. Normalmente poníamos una línea divisoria, pero ahora con este Widget podemos separarlos con espacios en blanco.
- Podéis consultar los diferentes constructores de este Widget: https://api.flutter.dev/flutter/widgets/SizedBox-class.html#constructors
- La diferencia entre SizedBox y SizedBox.expand es que el segundo establece un ancho y alto igual al ancho y alto disponible estableciendo un valor de double.infinity.
- Entre los parámetros que podemos emplear está child (es opcional). Es un Widget que tendrá como tamaño el tamaño indicado en su SizedBox.
- Veamos un ejemplo:
import 'package:flutter/material.dart'; class SizedBoxPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Ejemplos de SizedBox'), ), body: _contenido() ); } Widget _contenido(){ return Row( children: [ Expanded(child: _listaElementos(),flex: 1,), Expanded(child: _listaSizedBox(),flex: 2,), ], ); } Widget _listaSizedBox(){ // Ocupa el 66% del ancho => mirar parámetro flex return Column( children: [ SizedBox( width: 50.0, height: 100.0, child: Text('Texto dentro de un sizedbox',style: TextStyle(backgroundColor: Colors.blue),), ), SizedBox( // Para separar uno de otro height: 50.0, ), SizedBox( width: 60.0, height: 200.0, child: Container( child: Text('Texto dentro de un sizedbox con un tamaño más grande',style: TextStyle(backgroundColor: Colors.blue),), color: Colors.pink, ), ), ], ); } Widget _listaElementos(){ return Container( color: Colors.yellow, child: ListView( children: [ Text('Ejemplo de texto de Elemento 1',style: TextStyle(backgroundColor: Colors.green),), SizedBox(height: 50.0, ), Text('Ejemplo de texto de Elemento 2'), SizedBox(height: 50.0, ), Text('Ejemplo de texto de Elemento 3'), ], ), ); } }
Otros Widgets de diseño
Flex
- Más información en https://api.flutter.dev/flutter/widgets/Flex-class.html
- Este Widget tiene el mismo efecto que Row/Column, con la salvedad que podéis establecer la dirección del eje principal (MainAxis) con el parámetro direction el cual puede tener los valores Axis.horizontal / Axis.vertical indicando la dirección de su MainAxis.
- Por lo tanto sólo debería usarse cuando queráis a través del valor de alguna variable, cambiar la dirección del eje principal.
Padding
- Más información en https://api.flutter.dev/flutter/widgets/Padding-class.html
- Permite establecer un padding sobre un widget indicado en el parámetro child.
- El argumento que no puede ser nulo es el padding que debe ser una objeto de una clase que implemente la clase EdgeInsetsGeometry. Normalmente se usa la clase EdgeInsets.
- El widget a visualizar puede ser nulo (argumento child) por lo que sólamente visualizaríamos un espacio (el padding establecido).
- Tiene el mismo efecto que el widget Container con un padding establecido.
Stack
- Más información en https://api.flutter.dev/flutter/widgets/Stack-class.html
- Permite 'colocar' widgets uno encima de otro, produciendo un solape.
Positioned
- Más información en https://api.flutter.dev/flutter/widgets/Positioned-class.html
- Asociado al Widget Stack tenemos este widget con sus respectivos constructores que nos permite indicar la 'posición' del widget child indicado dentro del Stack.
OverflowBar
- Más información: https://api.flutter.dev/flutter/widgets/OverflowBar-class.html
- Permite tener un desiño horizontal (como un row) de Widget´s y en caso de que no quepan (porque rotamos el dispositivo o por la resolución del mismo) pasará a tener un diseño vertical (como un colum).
- Ejemplo de uso: https://medium.com/flutter-community/flutter-responsive-design-size-doesnt-always-matter-6cd88b5e6f2c
SafeArea
- Más información en https://api.flutter.dev/flutter/widgets/SafeArea-class.html
- Permite evitar que el espacio ocupado por partes del dispositivo físico interfiera en la visualización del contenido (como esquinas redondeadas, cámara superior,...)
- El scaffold con AppBar ya se encarga de que no interfiera, pero lo normal es envolver el body con este Widget.
- Podemos especificar que partes de la pantalla queremos que sean 'protegidas' y que no interfieran con el contenido.
SingleChildScrollView
- Más información en https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html
- Permite tener un scroll sobre un único Widget (propiedad child).
- Este widget debería ser empleado cuando tenemos un conjunto de Widget en el diseño de una pantalla que podrían salirse, al aumentar de tamaño, del tamaño permitido y al no tener un scroll, daría un error.
- A diferencia del ListView, este control no debería emplearse cuando se tiene un conjunto de 'items' que queremos que se carguen en forma de lista.
Debugger Diseño
- Más información en https://flutter.dev/docs/development/tools/devtools/inspector
- Cuando visualizamos un Widget cuyo tamaño excede el espacio disponible veréis que aparece esto en pantalla:
- En este vídeo voy a explicar como podemos hacer uso de la herramienta INSPECTOR que forma parte del conjunto de herramientas DEVTOOLS.
Enlaces de interés
- https://flutter.dev/docs/development/ui/layout
- https://esflutter.dev/docs/development/ui/layout#organiza-multiples-widgets-vertical-y-horizontalmente
- https://medium.com/comunidad-flutter/construya-uis-responsivas-en-flutter-f528d16b50a4
- https://medium.com/flutter-community/developing-for-multiple-screen-sizes-and-orientations-in-flutter-fragments-in-flutter-a4c51b849434
- https://medium.com/flutter-community/build-responsive-uis-in-flutter-fd450bd59158
Enlace a la página principal de la UD3
Enlace a la página principal del curso
-- Ángel D. Fernández González -- (2021).
