Flutter Widget Componentes

De MediaWiki
Ir a la navegación Ir a la búsqueda

Introducción

  • En esta sección vamos a ver los principales componentes gráficos (widget) que podemos necesitar para diseñar una pantalla en Flutter.
  • Textos
  • Botones
  • Imágenes
  • Listas
  • Card (tarjetas)
  • Combos (dropdown)


  • Partimos de lo aprendido en las secciones anteriores, en las que tenemomos un widget MaterialApp el cual tiene como contenido un Scaffold.
Los dos se encuentran en ficheros diferentes.
Lo que vamos a hacer es crear nuevas páginas que devuelve un scaffold con los contenidos que queramos visualizar.
  • Aún no sabemos cómo 'colocar' los widget, por lo que en esta sección se empezará a hablar de alguno de los widget´s que nos permiten hacer esto, pero que serán explicados en profundidad en la siguiente sección.


Widget Text

  • Creamos una nueva página en la carpeta pages de nombre textos_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 que derive de StatelessWidget (al hacerlo será necesario implementar el método build, ya os da un aviso el Android Studio).
  • Dentro del método build, retornamos un widget Scaffold (recordar que existe un snippet que lo hace automático).


Textos con un estilo único

  • El estilo hace referencia a las características visuales del texto, como puedan ser el color, negrilla, tamaño,...
Veamos ejemplo de código con alguna de sus propiedades aplicadas.
Indicar que en el ejemplo vamos a 'envolver' el texto en otro widget denominado Column. Este widget es uno de los que veremos en la siguiente sección, y nos permite colocar varios widget´s uno debajo de otro, formando filas. Para ello hace uso de la propiedad 'children' la cual espera recibir un conjunto de widget que conformarán cada una de las filas.

Archivo: textos_pages.dart Nota: Recuerda cambiar el archivo app.dart para que cargue la nueva página.

import 'package:flutter/material.dart';

class TextosPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
        appBar: AppBar(
          title: Text('Textos con diferentes estilos'),
        ),
        body: Column(
          children: const<Widget> [
            Text('Texto con los valores por defecto. Ocupa lo que ocupe de ancho del texto escrito. Si no cabe, pasa a la siguiente línea.'),
            Text(
              'Texto centrado en color rojo y negrilla\n Con varias líneas. Lo centra con respecto al ancho mayor de los widget´s que estén en el Column',
              textAlign: TextAlign.center,
              style: TextStyle(color: Colors.red,
                               fontWeight: FontWeight.bold,
                               fontSize: 10.2),
            ),

          ],
        )
    );
  }

}
Flutter dart widget 13.JPG


  • Como podemos ver en el ejemplo anterior, la clase Text tiene la propiedad style, que nos va a servir para dar un estilo al texto.
Veremos en el siguiente punto como podemos crear un 'estilo por defecto' y aplicarlo a varios Text.
Flutter nos permite también declarar una variable local a la clase (un atributo) que sea una instancia de la clase TextStyle, en la que podemos definir el aspecto que tendrán los textos, y después aplicar dicha instancia de clase a todos los textos que lo necesiten.
import 'package:flutter/material.dart';

class TextosPage extends StatelessWidget{
  final _estiloTexto = new TextStyle(fontSize: 35.3,  // Recordar que el new no es obligatorio en Flutter
    backgroundColor: Colors.red,
    color: Colors.black12);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
        appBar: AppBar(
          title: Text('Textos con diferentes estilos'),
        ),
        body: Column(
          children: <Widget> [
            Text('Texto con el estilo predefinido'),
            Text('Texto con el estilo definido en un atributo de la clase', style: _estiloTexto),
            Text('Otro Texto con el estilo definido en un atributo de la clase', style: _estiloTexto), // Se puede dejar la comilla puesta por si queremos añadir nuevos textos
          ],
        )
    );
  }

}
  • NOTA IMPORTANTE: Fijarse que el atributo está marcado como final ya que estamos en un StatelessWidget y por lo tanto todo lo que esté definido dentro del mismo debe ser 'inmutable'. Es decir, en este tipo de Widget no podemos 'redibujar' la interface, de tal forma que si empleamos una variable para visualizar un texto, dicho texto nunca podría redibujarse si por programación cambiamos el valor de la variable.
Si no definimos como final, el AndroidStudio nos avisará de que no tiene sentido.
Flutter dart widget 16.JPG


DefaultTextStyle


  • Es posible 'envolver' un conjunto de widget con un estilo por defecto y hacer que todos los Text que se encuentren dentro de ese estilo 'hereden' las características definidas en el mismo.
Nota: Recordar que en AndroidStudio es posible 'envolver' un Widget con otro, pulsando las teclas Alt+Enter y escogiendo la opción wrap.
En el ejemplo envolvemos el Widget Column con un Widget DefaultTextStyle al cual al que pasarle dos parámatros de forma obligatoria (required):
  • child: Donde iré el widget que va a visualizar (en nuestro caso el Column)
  • style: Donde se instancia la clase TextStyle y donde se define el estile que van a 'heredar' los textos que estén dentro de DefaultTextStyle.
import 'package:flutter/material.dart';

class TextosPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
        appBar: AppBar(
          title: Text('Textos con diferentes estilos'),
        ),
        body: DefaultTextStyle(
          style: TextStyle(fontSize: 20.3,
                           backgroundColor: Colors.amber,
                           color: Colors.blue,
                           fontStyle: FontStyle.italic),
          child: Column(
            children: <Widget> [
              Text('Texto con el estilo definido en DefaultTextStyle'),
              Text(
                'Texto centrado en color rojo y negrilla\n Con varias líneas. Lo centra con respecto al ancho mayor de los widget´s que estén en el Column',
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.red,
                    fontWeight: FontWeight.bold,
                    fontSize: 10.2),
              ),

            ],
          ),
        )
    );
  }

}

Nota: Fijarse como los widget´s Text que tengan un estilo propio, sobreescribirán el DefaultTextStyle

Flutter dart widget 14.JPG



RichText


  • Permite que un mismo texto tenga diferentes estilos.
Para ello hace uso de la clase TextSpan que representa un texto inmutable.
Cada TextSpan posee la propiedad 'children' que espera recibir una lista de TextSpan, cada uno de ellos con su propio TextStyle. Si no se pone un estilo que lo sobreescriba, el TextSpan 'heredará' el estilo del TextSpan que lo contenga.
import 'package:flutter/material.dart';

class TextosPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
        appBar: AppBar(
          title: Text('Textos con diferentes estilos'),
        ),
        body: DefaultTextStyle(
          style: TextStyle(fontSize: 20.3,
                           backgroundColor: Colors.amber,
                           color: Colors.blue,
                           fontStyle: FontStyle.italic),
          child: Column(
            children: <Widget> [
              Text('Texto con el estilo definido en DefaultTextStyle'),
              Text(
                'Texto centrado en color rojo y negrilla\n Con varias líneas. Lo centra con respecto al ancho mayor de los widget´s que estén en el Column',
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.red,
                    fontWeight: FontWeight.bold,
                    fontSize: 10.2),
              ),
              RichText(
                text: TextSpan(text: 'Este es un texto TextSpan',style: TextStyle(color: Colors.green),
                      children: [
                        TextSpan(text: ' con un estilo diferente', style: TextStyle(color:Colors.brown)),
                        TextSpan(text: ' en cada parte del texto.', style: TextStyle(color:Colors.yellow)),
                        TextSpan(text: ' Si no se pone nada, hereda el estilo del TextSpan que lo contenga'),
                      ]),
              )

            ],
          ),
        )
    );
  }

}
Flutter dart widget 15.JPG



Html en el interior de textos




import 'package:flutter/material.dart';

import 'package:flutter_html/flutter_html.dart' as flutterhtml;
import 'package:url_launcher/url_launcher.dart';

class TextosPage extends StatelessWidget{
  TextStyle _estiloTexto = new TextStyle(fontSize: 35.3,  // Recordar que el new no es obligatorio en Flutter
    backgroundColor: Colors.red,
    color: Colors.black12);


  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
        appBar: AppBar(
          title: Text('Textos con diferentes estilos'),
        ),
        body: Column(
          children: <Widget> [
            flutterhtml.Html(data: "<b>Ejemplo de etiqueta</b> html <a href = 'https://www.google.es'>Enlace</a>",
                onLinkTap: (String? url, _,__,___) {
                  _lanzar(url!);
                }),
            Text('Texto con el estilo predefinido'),
            Text('Texto con el estilo definido en un atributo de la clase', style: _estiloTexto),
            Text('Otro Texto con el estilo definido en un atributo de la clase', style: _estiloTexto), // Se puede dejar la comilla puesta por si queremos añadir nuevos textos
          ],
        )
    );
  }
  _lanzar(String url) async{
    await launch(url);
  }

}



Botones


  • En esta sección vamos a ver como añadir los diferentes tipos de botones que podemos emplear en Flutter.
No vamos a ver como gestionar el evento de Click sobre los mismos. Esto lo veremos en secciones posteriores.
  • Creamos una nueva página en la carpeta pages de nombre botones_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 (BotonesPage) que derive de StatelessWidget (al hacerlo será necesario implementar el método build, ya os da un aviso el Android Studio).
Flutter dart widget 18.JPG


Float Action Button

  • Los FAB son botones con un aspecto circular que se sitúa 'por encima' del contenido de la página y que normalmente son reservados para implementar 'la acción principal' de la página en la que nos encontramos.
Por ejemplo, en una pantalla de contactos, dicho botón sería empleado para dar de alta a nuevos contactos.


Nota: De hecho tenéis un snippet para crear un Scaffold junto con un FAB.
  • Dentro del método build, de la página 'botones_page.dart', retornamos un widget Scaffold con un Float Action Button (recordar que existe un snippet que lo hace automático).


  • Al implementar el Snippet aparecerá una serie de código que tendréis que eliminar, ya que está pensado para ejecutarse en un StatefulWidget.
Nota: Recordar cambiar el código del fichero app.dart e importar el fichero 'botones_page.dart' y cambiar la clase que se instancia en la propiedad home del widget MaterialApp.
import 'package:flutter/material.dart';

class BotonesPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('appbarTitle'),
      ),
      body: Text('Ejemplo de FAB'),
      floatingActionButton: FloatingActionButton(
        onPressed: null,  // Equivalente a disable. Tendríamos que enviar una función anónima para que aparezca el efecto de click
        tooltip: 'Presiona para hacer algo',
        child: Icon(Icons.add),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
    );
  }
}
  • Estamos instanciando la clase FloationActionButton.
  • Es necesario implementar el método onPressed (mirar la definición del constructor donde el parámetro aparece definido como 'required').
  • Normalmente empleamos una función (puede ser anónima) donde escribimos el código que queremos que se ejecute cuando presionamos el botón. Por ejemplo, podemos poner () {}. De esta forma no hace nada pero podemos visualizar el efecto de Click sobre el botón.
  • Si escribimos null (como en el ejemplo) tiene el mismo efecto que si inhabilitármos el botón.
  • Tiene un parámetro child que espera recibir un Widget. Puede ser cualquier Widget, pero normalmente se pone un icono. Para ello podemos emplear la clase Icons.
Tenéis en este enlace todos los iconos predefinidos de la clase Icons.


  • Nota: Recordar que un FloatActionButton no es más que un Widget que podemos colocar como hicimos con los textos en la sección anterior, pero que normalmente se suele emplear el Scaffold para hacelo.
  • Si necesitamos colocar varios FAB empleando las propiedades del Scaffold, podemos 'envolver' el Widget FAB del ejemplo anterior en un Row (o Column):
import 'package:flutter/material.dart';

class BotonesPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('appbarTitle'),
      ),
      body: Text('Ejemplo de FAB'),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          FloatingActionButton(
            onPressed: () { print ('Presionamos el botón ADD'); },
            tooltip: 'Presiona para añadir',
            child: Icon(Icons.add),
          ),
          FloatingActionButton(
            onPressed: () { print ('Presionamos el botón DELETE'); },
            tooltip: 'Presiona para borrar',
            child: Icon(Icons.delete),
          ),
        ],
      ),
    );
  }
}


  • También podemos asociar un texto al icono con el parámetro 'label', pero para ello debemos de hacer uso de un FloatActionButton extendido de la forma:
FloatActionButton.extended
  • Hace uso del parámetro 'label' para asociar un texto al botón.
  • No tiene el parámetro child para asociar un icono. Ya dispone de un parámetro 'icon' para indicar el icono asociado.
import 'package:flutter/material.dart';

class BotonesPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('appbarTitle'),
      ),
      body: Text('Ejemplo de FAB'),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          FloatingActionButton.extended(
            icon: Icon(Icons.add),
            onPressed: () { print ('Presionamos el botón ADD'); },
            tooltip: 'Presiona para añadir',
            label: Text('Añadir'),
            backgroundColor: Colors.pink,
          ),
          FloatingActionButton(
            onPressed: () { print ('Presionamos el botón DELETE'); },
            tooltip: 'Presiona para borrar',
            child: Icon(Icons.delete),
          ),
        ],
      ),
    );
  }
}


Flutter dart widget 17.JPG



TextButton

  • Son botones que no tienen borde y suelen usarse en cajas de diálogo, toobar o junto con otros contenidos pero separados de ellos para que se note que no forman parte del contenido que se está visualizando.
En las cajas de diálogos y cards (tarjetas) deberían estar todos agrupados en una esquina inferior.
Fijarse que entre los parámetros que podemos emplear estarían la gestión de eventos con onPressed y onLongPress.
De forma obligatoria hay que enviarle un widget (child) que será lo que visualice. Normalmente un texto o un icono.
Para modificar el estilo se recomienda hacer uso del método static styleFrom.
  • Veamos un ejemplo:
import 'package:flutter/material.dart';

class BotonesPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('appbarTitle'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () { print ('Presionamos el botón DELETE'); },
        tooltip: 'Presiona para borrar',
        child: Icon(Icons.delete),
      ),

        body: Row(
        children: [
          TextButton(onPressed: () => {}, child: const Text('Botón sin borders')),
          TextButton(onPressed: null, child: const Icon (Icons.access_alarm), // Botón inhabilitado
                    style: TextButton.styleFrom(backgroundColor: Colors.red)),
        ]
      )
    );
  }
}



IconButton

  • Son iconos que reaccionan al hacer click sobre ellos.
Visualmente se tintan de un color al hacer click sobre ellos.
Normalmente se utilizan en el AppBar (barra superior de iconos que conforman el menú principal).
Posee un parámetro icon donde se indica el icono que se va a utilizar.
  • Veamos un ejemplo:
import 'package:flutter/material.dart';

class BotonesPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('appbarTitle'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () { print ('Presionamos el botón DELETE'); },
        tooltip: 'Presiona para borrar',
        child: Icon(Icons.delete),
      ),

        body: Row(
        children: [
          IconButton(icon: const Icon(Icons.account_tree),onPressed: () => {},color: Colors.yellow,
                                      iconSize: 50.5),
          TextButton(onPressed: () => {}, child: const Text('Botón sin borders')),
          TextButton(onPressed: null, child: const Icon (Icons.access_alarm), // Botón inhabilitado
                    style: TextButton.styleFrom(backgroundColor: Colors.red)),
        ]
      )
    );
  }
}



Imágenes

  • Más información en:


  • Creamos una nueva página en la carpeta pages de nombre imagenes_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 de nombre ImagenesPage que derive de StatelessWidget (al hacerlo será necesario implementar el método build, ya os da un aviso el Android Studio).
  • Dentro del método build, retornamos un widget Scaffold (recordar que existe un snippet que lo hace automático).



  • Flutter suporta los siguientes tipos de imágenes: JPEG, WebP, GIF, WebP/GIF animados, PNG, BMP y WBMP
Las imágenes pueden ser cargadas localmente o desde un recurso de Internet.



Cargando imágenes desde Internet

  • En el siguiente ejemplo veremos como cargar una imagen de Internet de dos formas diferentes:
import 'package:flutter/material.dart';

class ImagenesPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
        appBar: AppBar(
          title: Text('Ejemplos de imágenes'),
        ),
        body: Column(
          children: <Widget>[
            Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg',scale: 3,),
            Image(image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),height: 100,)
          ],
        )
    );
  }
  
}


  • Las dos son equivalentes (menos en el tamaño que uno hace uso del parámetro scale y otro de height).
La diferencia estriba en que Image.network devuelve un Widget mientras que NetworkImage devuelve un objeto que no se visualiza en pantalla, por eso debemos pasarlo como dato al parámetro 'image' de la clase Image.


Cargando imágenes localmente

  • A nivel interno, las aplicaciones Android (y también IOS) disponen de unos 'directorios especiales' donde guardan los recursos que son 'compilados' con el proyecto y se referencian internamente mediante un identificador numérico.
En el caso de Android, estos recursos se guardan en /android/app/src/main/res
En el caso de IOS, estos recursos se guardan en /ios/Runner/Assets.xcasetts


  • Normalmente se guardan los iconos de la aplicación, como el icono que representa la aplicación en el teléfono, iconos de la barra de acción (ActionBar, lo que viene a ser el menú superior de opciones) e imágenes.
El problema que tiene utilizar estos directorios es que:
  • No deja establecer una estructura de directorios para 'organizar' los recursos como queramos.
  • Son recursos que sólamente son visibles para el S.O. donde se encuentren (IOS / Android) por lo que nos obligaría a tener una copia de los recursos en los dos directorios.
  • Flutter resuelve este problema permitiendo crear un directorio assets en el directorio raíz del proyecto Flutter.
Dentro de dicho directorio podremos crear las carpetas y añadir los recursos de cualquier tipo (audio, imágenes, vídeos, iconos,...) que queramos usar y dichos recursos estarán disponibles para cualquiera de las plataformas sobre las que generemos la aplicación (IOS, Android, PC, Web...)



Escalando las imágenes



Esperando a descargar

  • Cuando las imágenes a descargar 'tardan' debido a que son obtenidas de Internet, visualmente no queda bien ya que los recursos locales aparecerán instantáneamente mientras que los recursos que vengan de Internet irán cargándose poco a poco y ocupando su espacio.
Flutter nos ofrece un Widget que permite mostrar un recurso local mientras se carga el recurso de Internet. Normalmente tendremos en pantalla algún gif animado indicando que se está descargando el recurso y cuando ya está descargado se sustituye por la imagen.


En el ejemplo que voy a explicar he descargado este gif: https://www.gifsanimados.org/img-reloj-imagen-animada-0141-82234.htm



Enlace a la página principal de la UD3

Enlace a la página principal del curso




-- Ángel D. Fernández González -- (2021).