Flutter StatefulWidget. Formularios

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

Introducción

  • Al manejar cajas de texto, voy a necesitar 'guardar' la información introducida en el formulario en variables, por lo que voy a necesitar hacer uso de StatefulWidget´s.
  • Creamos una nueva página en la carpeta pages de nombre formulario_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 (FormularioPage) que derive de StatefulWidget. Se creará (si lo hacéis con un Snippet) una clase asociada que deriva de State.
  • Modificamos la clase State y en su métod build hacemos que devuelva un Scaffold con una Appbar (hacerlo con un Snippet)
  • Como vamos a añadir múltiples elementos de formulario, vamos a crear un ListView dentro del body del Scaffold, para tener scroll vertical...



TextField

  • Nota: Indicar que este Widget es usado para recuperar información introducida por el usuario, pero en el caso de tener un formulario en el que vamos a recuperar información de múltiples elementos (y no sólo cajas de texto) es más conveniento hacer uso del TextFormField como veremos después. En este caso es posible realizar una validación de los campos del formulario antes de hacer uso de los datos.


  • Propiedades más importantes:
  • autofocus: Permite que al iniciarse la aplicación el foco esté sobre la caja de texto.
  • textCapitalization: Para indicar si las letras, palabras, frases son en mayúscula.
  • maxLength: Número máximo de caracteres.
  • keyboardType: Establece el tipo de teclado en base al tipo de información que se va a introducir (por ejemplo, email, teléfono, núnero,...)
  • decoration: Para indicar la decoración que queremos que tenga la caja de texto alrededor del texto.
  • obscureText y obscuringCharacter: Para cambiar el caracter visible por otro. Empleado para campos password, por ejemplo.


  • Un ejemplo de uso.
Creamos una nueva página en la carpeta pages/stateful de nombre formulario_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 (FormularioPage) que derive de StatefulWidget (al hacerlo se creará una clase _FormularioPageState asociada que deriva de State).
En el método build de la clase State retornamos un Scaffold con AppBar y el body los constituye un ListView
En este ejemplo se visualizan las propiedades descritas anteriormente, creando tres campos, uno para un nombre, otro para un email y otro para un password.
Flutter dart formulario 1.JPG


import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}

class _FormularioPageState extends State<FormularioPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),

          ],
        ));
  }

  Widget _crearEmail(){

    return TextField(
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
        hintText: 'Email del paciente',
        labelText: 'Email',
        suffixIcon: Icon(Icons.email),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(15.0),
        )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      textCapitalization: TextCapitalization.words,
      maxLength: 10,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: 0',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}



Guardando el valor del TextField

  • Para guardar el valor necesitamos capturar algún evento.
En este ejemplo, vamos a capturar el evento de onChanged, que se lanza cada vez que el usuario cambia el contenido de la caja de texto.
Lo que voy a hacer es guardar el valor de cada caja de texto en una variable definida previamente en la clase State.
Nota Importante: Recordar que necesitamos llamar al método setState para que 'redibuje' el StatefulWidget y se visualicen el valor de las variables.


  • Modificamos por tanto el ejemplo anterior para añadirle la nueva funcionalidad.
Flutter dart formulario 2.JPG


import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}

class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),
            Divider(),
            Divider(),
            _visualizarDatos(),

          ],
        ));
  }

  Widget _visualizarDatos(){
    return Column(
      children: [
        Text('Nombre del paciente: $_nombre'),
        Text('Email del paciente: $_email'),
        Text('Password: $_password')
      ],
    );
  }

  Widget _crearEmail(){

    return TextField(
      onChanged: (valor) => setState((){
                                        _email = valor;
                                        print(valor);}
                                    ),   // Fijarse que podemos guardar una cadena vacía
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
        hintText: 'Email del paciente',
        labelText: 'Email',
        suffixIcon: Icon(Icons.email),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(15.0),
        )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      onChanged: (valor) => setState((){_password = valor;}),   // Fijarse que podemos guardar una cadena vacía
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      onChanged: (valor) => setState((){_nombre = valor;}),   // Fijarse que podemos guardar una cadena vacía
      textCapitalization: TextCapitalization.words,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: ${_nombre.length}',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}



Modificando contenido de un TextField. TextEditingController


  • En el ejemplo anterior estoy guardando el valor de los campos de texto a medida que se van escribiendo, gestionando el evento onChange en cada uno de ellos.
Otra aproximación consiste en que, cuando presionamos un botón (por ejemplo), recoger los valores introducidos en las diferentes cajas de texto u otros elementos de la interface (como veremos a continuación).


  • Tendremos que gestionar por tanto, el evento Click sobre un botón. En este caso voy a hacer uso de un ElevatedButton, que será explicado posteriormente en esta misma sección.


  • Para poder acceder al contenido de un TextField necesitamos asociar al Widget, en su propiedad controller un objeto de la clase TextEditingController.
Una vez hecho, a través de ese objeto, podemos hacer refencia a la propiedad text.
  • Voy a modificar el ejemplo anterior. Ahora el texto que muestra el email no se actualiza a medida que vamos escribiendo, sino que se actualizará al presionar el botón.


import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}

class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  final TextEditingController controllerInputEmail = TextEditingController();  // Controlador asociado a texto Email donde se escribe

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),
            Divider(),
            _crearBoton(),
            Divider(),
            Divider(),
            _visualizarDatos(),

          ],
        ));
  }

  Widget _visualizarDatos(){
    return Column(
      children: [
        Text('Nombre del paciente: $_nombre'),
        Text(
          'Email del paciente: $_email'
        ),
        Text('Password: $_password')
      ],
    );
  }

  Widget _crearBoton(){
    final estiloBoton = ElevatedButton.styleFrom(textStyle: TextStyle(color: Colors.blue,fontSize: 30.0));
    return ElevatedButton(
        style: estiloBoton,
        onPressed: (){
            print('El email es:${controllerInputEmail.text}');
            setState(() {   // Necesitamos redibujar para que el campo Text que visualiza el email lo muestre
              _email = controllerInputEmail.text;
            });
        },
        child: Text('Presiona')
    );
  
  }
  
  
  Widget _crearEmail(){

    return TextField(
      controller: controllerInputEmail,
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
          hintText: 'Email del paciente',
          labelText: 'Email',
          suffixIcon: Icon(Icons.email),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      onChanged: (valor) => setState((){_password = valor;}),   // Fijarse que podemos guardar una cadena vacía
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      onChanged: (valor) => setState((){_nombre = valor;}),   // Fijarse que podemos guardar una cadena vacía
      textCapitalization: TextCapitalization.words,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: ${_nombre.length}',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}




DropDownButton / DropDownMenuItem

  • El dropdown es un componente gráfico que permite guardar un conjunto de opciones que son visualizadas al presionar en dicho control. De esas opciones, queda visualmente seleccionada una de ellas.
En otro lenguajes de conoce como combobox.
  • Parémtros del constructor más importantes:
Los DropDownMenuITem pueden ser de cualquier tipo (en la definición pone dynamic) y por tanto podemos pasar objetos de clases, visualizándose el contenido del método toString().
El constructor tiene dos parámetros importantes:
  • child: El widget que se va a visualizar.
  • value: El valor asociado al elemento de la lista.
  • onChanged: Si no definimos esta función, los elementos del DropDown estarán inhabilitados.
  • value: El valor que representa el elemento seleccionado. Cada elemento de la lista (DropDownMenuItem) va a tener la propiedad 'value' que indica el 'valor' de esa opción. Al cambiar esta variable, cambiamos el elemento seleccionado de la lista.


  • Veamos un ejemplo siguiendo con el ejercicio anterior:
class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  int _valorEscojidoDropDown = 1;


  .............

 Widget _crearDropDown(){
    return DropdownButton(
      value: _valorEscojidoDropDown,
      onChanged: (int? valor) { setState(() {

        _valorEscojidoDropDown = valor ?? 1;  // Como puede que no se seleccione un valor, debemos de dar un valor por defecto
      });},
      items: [
        DropdownMenuItem<int>(child: Text('Opción 1'),value: 1),
        DropdownMenuItem<int>(child: Text('Opción 2'),value: 2),
        DropdownMenuItem<int>(child: Text('Opción 3'),value: 3),

      ],
    );
  }
  • Fijarse en los siguientes aspectos:
  • Cada elemento de la lista (DropDownMenuItem) visualizar un Text como Widget y asocia a cada elemento de la lista un núnero (value)
Por eso, en la definición del DropdownMenuItem<int> especificamos <int> como tipo de dato. Este tendremos que cambiarlo dependiendo del tipo de value que guardemos.
  • Como comenté anteriormente es necesario definir el método onChanged para que se pueda 'seleccionar' el elemento de la lista. Se define una función que recibe como parámetro el value del elemento seleccionado (recordar que es un int en este ejemplo). Puede suceder que no haya ningún elemento seleccionado y por tanto pueda tener valor nulo, por lo que la variable 'valor' está definida como 'int? valor'.
Al pulsar un elemento de la lista queremos 'redibujar' el statefulwidget, por lo que llamamos al método setState(). Dentro del mismo cambiamos un valor de una variable definida previamente de nombre '_valorEscojidoDropDown '.
  • La variable '_valorEscojidoDropDown' es usada en el parámetro 'value' para indicar cual es el elememto seleccionado de la lista.



  • Veamos ahora el mismo ejemplo, pero cambiando los datos del DropdownButton. Ahora están definidos en una variable (una lista) en la clase State.
  • Además he envuelto el DropdownMenu en un Container para que tenga un borde redondeado y así se parezca al resto de campos.
Flutter dart formulario 3.JPG


El código de la página Furmulario_page.dart

import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}


class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  List<String> _optTipoPaciente = ['Crónico', 'Terminal', 'Observación'];
  String _valorEscojidoTipoPaciente = 'Crónico';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),
            Divider(),
            _crearDropDown(),
            Divider(),
            Divider(),
            _visualizarDatos(),

          ],
        ));
  }

  Widget _crearDropDown(){
    return Container(
      height: 60.0,
      padding: EdgeInsets.only(left: 8.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(15.0),
        border: Border.all(
          color: Colors.grey
        )
      ),
      child: Row(
        children: [
          DropdownButton(
            value: _valorEscojidoTipoPaciente,
            onChanged: (String? valor) { setState(() { _valorEscojidoTipoPaciente = valor!; });},
            items: _crearItemsDropDown(),

          ),
          Spacer(),
          Container(child: Icon(Icons.api),
                    padding: EdgeInsets.only(right: 10.0),
          ),
        ],
      ),
    );
  }

  List<DropdownMenuItem<String>> _crearItemsDropDown(){

    List<DropdownMenuItem<String>> lista = [];
    _optTipoPaciente.forEach((element) {
       lista.add(DropdownMenuItem(child: Text('$element'),value: element,));
    });

    return lista;
  }


  Widget _visualizarDatos(){
    return Column(
      children: [
        Text('Nombre del paciente: $_nombre'),
        Text('Email del paciente: $_email'),
        Text('Password: $_password'),
        Text('Tipo de paciente: $_valorEscojidoTipoPaciente')
      ],
    );
  }

  Widget _crearEmail(){

    return TextField(
      onChanged: (valor) => setState((){
                                        _email = valor;
                                        print(valor);}
                                    ),   // Fijarse que podemos guardar una cadena vacía
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
        hintText: 'Email del paciente',
        labelText: 'Email',
        suffixIcon: Icon(Icons.email),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(15.0),
        )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      onChanged: (valor) => setState((){_password = valor;}),   // Fijarse que podemos guardar una cadena vacía
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      onChanged: (valor) => setState((){_nombre = valor;}),   // Fijarse que podemos guardar una cadena vacía
      textCapitalization: TextCapitalization.words,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: ${_nombre.length}',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}



Checkbox / CheckboxListTile

  • Más información:


  • Los parámtros más importantes del constructor son:
  • value: Obligatorio. Normalmente se pone una variable que guardará el valor del checkbox (seleccionado o no)
  • onChanged: Obligatorio. Método al que se llamará cuando se produce un cambio en el checkbox.


  • Visualmente un Chekbox visualiza un check sin texto asociado.
Si queremos añadir un texto al mismo, podemos hacer uso del CheckboxListTile, con el parámetro titled. Podemos emplear los mismos parámetro que con el Checkbox.


  • Veamos un ejemplo sobre la página creada previamente empleando un CheckboxListTile:
Flutter dart formulario 4.JPG
import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}


class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  List<String> _optTipoPaciente = ['Crónico', 'Terminal', 'Observación'];
  String _valorEscojidoTipoPaciente = 'Crónico';

  bool _checkEnfermedadTerminal = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),
            Divider(),
            _crearDropDown(),
            Divider(),
            _crearCheckBox(),
            Divider(),
            Divider(),
            _visualizarDatos(),

          ],
        ));
  }

  Widget _visualizarDatos(){
    return Column(
      children: [
        Text('Nombre del paciente: $_nombre'),
        Text('Email del paciente: $_email'),
        Text('Password: $_password'),
        Text('Tipo de paciente: $_valorEscojidoTipoPaciente'),
        Text('Enfermedad terminal: $_checkEnfermedadTerminal')
      ],
    );
  }
  Widget _crearCheckBox(){
    return CheckboxListTile(
        title: Text('Enfermedad terminal'),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0),side: BorderSide(color: Colors.grey)),
        value: _checkEnfermedadTerminal,
        onChanged: (nuevoValor) {   // Hay que llamar a setState para que redibuje el CheckBox
          setState(() { _checkEnfermedadTerminal = nuevoValor! ;});
        }

    );
  }
  Widget _crearDropDown(){
    return Container(
      height: 60.0,
      padding: EdgeInsets.only(left: 8.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(15.0),
        border: Border.all(
          color: Colors.grey
        )
      ),
      child: Row(
        children: [
          DropdownButton(
            value: _valorEscojidoTipoPaciente,
            onChanged: (String? valor) { setState(() { _valorEscojidoTipoPaciente = valor!; });},
            items: _crearItemsDropDown(),

          ),
          Spacer(),
          Container(child: Icon(Icons.api),
                    padding: EdgeInsets.only(right: 10.0),
          ),
        ],
      ),
    );
  }

  List<DropdownMenuItem<String>> _crearItemsDropDown(){

    List<DropdownMenuItem<String>> lista = [];
    _optTipoPaciente.forEach((element) {
       lista.add(DropdownMenuItem(child: Text('$element'),value: element,));
    });

    return lista;
  }

  Widget _crearEmail(){

    return TextField(
      onChanged: (valor) => setState((){
                                        _email = valor;
                                        print(valor);}
                                    ),   // Fijarse que podemos guardar una cadena vacía
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
        hintText: 'Email del paciente',
        labelText: 'Email',
        suffixIcon: Icon(Icons.email),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(15.0),
        )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      onChanged: (valor) => setState((){_password = valor;}),   // Fijarse que podemos guardar una cadena vacía
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      onChanged: (valor) => setState((){_nombre = valor;}),   // Fijarse que podemos guardar una cadena vacía
      textCapitalization: TextCapitalization.words,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: ${_nombre.length}',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}



Switch / SwitchListTile

  • Más información:
  • Crea un control gráfico para guardar un valor true/false en forma de barra de desplazamiento.
La diferencia entre los dos, es que el 'SwitchListTile' permite representar el switch con un texto asociado y también un icono, igual que sucedía con el Checkbox y CheckboxListTile.


  • Veamos un ejemplo siguiendo con el ejercicio anterior, haciendo uso de un SwitchListTile:
Flutter dart formulario 7.JPG


import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}


class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  List<String> _optTipoPaciente = ['Crónico', 'Terminal', 'Observación'];
  String _valorEscojidoTipoPaciente = 'Crónico';

  bool _checkEnfermedadTerminal = false;
  bool _switchHospitalizado = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),
            Divider(),
            _crearDropDown(),
            Divider(),
            _crearCheckBox(),
            Divider(),
            _crearSwitch(),
            Divider(),
            Divider(),
            _visualizarDatos(),

          ],
        ));
  }

  Widget _visualizarDatos(){
    return Column(
      children: [
        Text('Nombre del paciente: $_nombre'),
        Text('Email del paciente: $_email'),
        Text('Password: $_password'),
        Text('Tipo de paciente: $_valorEscojidoTipoPaciente'),
        Text('Enfermedad terminal: $_checkEnfermedadTerminal'),
        Text('Hospitalizado: $_switchHospitalizado'),

      ],
    );
  }

  Widget _crearSwitch(){
    return SwitchListTile(
        title: Text('Hospitalizado'),
        subtitle: Text('Indica si está hospitalizado'),
        secondary: Icon(Icons.accessibility),
        value: _switchHospitalizado,
        onChanged: (valor) { setState(() {
          _switchHospitalizado = valor;
        }); });
  }
  Widget _crearCheckBox(){
    return CheckboxListTile(
        title: Text('Enfermedad terminal'),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0),side: BorderSide(color: Colors.grey)),
        value: _checkEnfermedadTerminal,
        onChanged: (nuevoValor) {   // Hay que llamar a setState para que redibuje el CheckBox
          setState(() { _checkEnfermedadTerminal = nuevoValor! ;});
        }

    );
  }
  Widget _crearDropDown(){
    return Container(
      height: 60.0,
      padding: EdgeInsets.only(left: 8.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(15.0),
        border: Border.all(
          color: Colors.grey
        )
      ),
      child: Row(
        children: [
          DropdownButton(
            value: _valorEscojidoTipoPaciente,
            onChanged: (String? valor) { setState(() { _valorEscojidoTipoPaciente = valor!; });},
            items: _crearItemsDropDown(),

          ),
          Spacer(),
          Container(child: Icon(Icons.api),
                    padding: EdgeInsets.only(right: 10.0),
          ),
        ],
      ),
    );
  }

  List<DropdownMenuItem<String>> _crearItemsDropDown(){

    List<DropdownMenuItem<String>> lista = [];
    _optTipoPaciente.forEach((element) {
       lista.add(DropdownMenuItem(child: Text('$element'),value: element,));
    });

    return lista;
  }

  Widget _crearEmail(){

    return TextField(
      onChanged: (valor) => setState((){
                                        _email = valor;
                                        print(valor);}
                                    ),   // Fijarse que podemos guardar una cadena vacía
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
        hintText: 'Email del paciente',
        labelText: 'Email',
        suffixIcon: Icon(Icons.email),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(15.0),
        )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      onChanged: (valor) => setState((){_password = valor;}),   // Fijarse que podemos guardar una cadena vacía
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      onChanged: (valor) => setState((){_nombre = valor;}),   // Fijarse que podemos guardar una cadena vacía
      textCapitalization: TextCapitalization.words,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: ${_nombre.length}',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}


Radio / RadioListTile

  • Más información:


  • Un radiogroup es un elemento gráfico que permite agrupo varios radiobutton en un conjunto y hacer que sólo uno de ellos esté seleccionado.
  • El 'problema' que tiene el Radio es que no tiene un texto asociado, por lo que tendremos que 'componer' algún Widget (como un Row o Column) que pueda tener un Text al lado del Radio.
Otra forma (más sencillas) es emplear un RadioListTile que es como un Radio pero con una etiqueta asociada.
  • Los parámetros más importantes del constructor son los siguientes:
  • value: Obligatorio. Normalmente se pone una variable que guardará el valor del RadioButton (seleccionado o no)
  • onChanged: Obligatorio. Método al que se llamará cuando se selecciona el RadioButton.
  • groupValue: Obligatorio. Es el valor que tiene el radiogroup en su conjunto. Cuando coincide el valor del groupValue con el value, el radiobutton se selecciona.
Aquí emplearemos la variable que guarda el valor del radio seleccionado.


  • Siguiendo con el ejemplo, voy a hacer uso de un RadioListTile.
Como 'fuente de datos' de los diferentes Radio´s uso un Set, pero podría ser cualquier tipo como un List, Enum,....
Flutter dart formulario 8.JPG


import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}


class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  List<String> _optTipoPaciente = ['Crónico', 'Terminal', 'Observación'];
  String _valorEscojidoTipoPaciente = 'Crónico';

  bool _checkEnfermedadTerminal = false;
  bool _switchHospitalizado = false;
  String _radioAtenciomInicial='';
  Set<String> _valoresRadio = {'Cabecera', 'Uci', 'Urgencias'};

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),
            Divider(),
            _crearDropDown(),
            Divider(),
            _crearCheckBox(),
            Divider(),
            _crearSwitch(),
            Divider(),
            _crearRadio(),
            Divider(),
            Divider(),
            _visualizarDatos(),

          ],
        ));
  }

  Widget _visualizarDatos(){
    return Column(
      children: [
        Text('Nombre del paciente: $_nombre'),
        Text('Email del paciente: $_email'),
        Text('Password: $_password'),
        Text('Tipo de paciente: $_valorEscojidoTipoPaciente'),
        Text('Enfermedad terminal: $_checkEnfermedadTerminal'),
        Text('Hospitalizado: $_switchHospitalizado'),
        Text('Atendido inicialmente en: $_radioAtenciomInicial'),
      ],
    );
  }

  Widget _crearRadio(){

    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: _elemsRadio(),
    ) ;
  }

  List<Widget>_elemsRadio(){
    List<Widget> lista = [];
    _valoresRadio.forEach((element) {
      RadioListTile r = RadioListTile(
          title: Text(element),
          value: element,
          groupValue: _radioAtenciomInicial,
          onChanged: (valor){ setState(() {
            _radioAtenciomInicial = valor;
          });},
        );
        lista.add(r);
    });
    return lista;
  }

  Widget _crearSwitch(){
    return SwitchListTile(
        title: Text('Hospitalizado'),
        subtitle: Text('Indica si está hospitalizado'),
        secondary: Icon(Icons.accessibility),
        value: _switchHospitalizado,
        onChanged: (valor) { setState(() {
          _switchHospitalizado = valor;
        }); });
  }
  Widget _crearCheckBox(){
    return CheckboxListTile(
        title: Text('Enfermedad terminal'),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0),side: BorderSide(color: Colors.grey)),
        value: _checkEnfermedadTerminal,
        onChanged: (nuevoValor) {   // Hay que llamar a setState para que redibuje el CheckBox
          setState(() { _checkEnfermedadTerminal = nuevoValor! ;});
        }

    );
  }
  Widget _crearDropDown(){
    return Container(
      height: 60.0,
      padding: EdgeInsets.only(left: 8.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(15.0),
        border: Border.all(
          color: Colors.grey
        )
      ),
      child: Row(
        children: [
          DropdownButton(
            value: _valorEscojidoTipoPaciente,
            onChanged: (String? valor) { setState(() { _valorEscojidoTipoPaciente = valor!; });},
            items: _crearItemsDropDown(),

          ),
          Spacer(),
          Container(child: Icon(Icons.api),
                    padding: EdgeInsets.only(right: 10.0),
          ),
        ],
      ),
    );
  }

  List<DropdownMenuItem<String>> _crearItemsDropDown(){

    List<DropdownMenuItem<String>> lista = [];
    _optTipoPaciente.forEach((element) {
       lista.add(DropdownMenuItem(child: Text('$element'),value: element,));
    });

    return lista;
  }

  Widget _crearEmail(){

    return TextField(
      onChanged: (valor) => setState((){
                                        _email = valor;
                                        print(valor);}
                                    ),   // Fijarse que podemos guardar una cadena vacía
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
        hintText: 'Email del paciente',
        labelText: 'Email',
        suffixIcon: Icon(Icons.email),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(15.0),
        )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      onChanged: (valor) => setState((){_password = valor;}),   // Fijarse que podemos guardar una cadena vacía
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      onChanged: (valor) => setState((){_nombre = valor;}),   // Fijarse que podemos guardar una cadena vacía
      textCapitalization: TextCapitalization.words,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: ${_nombre.length}',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}



Slider


  • Gráficamente es una barra de desplazamiento para seleccionar un valor sobre un rango de valores posibles.
  • Parámetros más importantes dentro del constructor:
  • value: Obligatorio. Normalmente se pone una variable que guardará el valor del slider
  • onChanged: Obligatorio. Método al que se llamará cuando se produce un cambio en la barra de desplazamiento.
  • min: El valor mínimo que el usuario puede seleccionar en el slider.
  • max: El valor máximo que el usuario puede seleccionar en el slider.
  • divisions: El número de divisiones 'enteras' que tiene el Slider. Si no se pone nada, el Slider podrá tener cualquier valor entre los rangos indicados.
EL poner divisions no impide que guarda un valor decimal. Para hacer que guarde un entero, tendremos que convertir el valor recibido en onChanged a un entero y guardarlo en la variable de value.
  • label: Texto que se muestra junto al Slider al cambiar su valor.


  • Veamos un ejemplo en el que dividimos en Slider en 24 partes y convertimos el valor a enteros para que no guarde valores con decimales.
Flutter dart formulario 5.JPG
import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}


class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  List<String> _optTipoPaciente = ['Crónico', 'Terminal', 'Observación'];
  String _valorEscojidoTipoPaciente = 'Crónico';

  bool _checkEnfermedadTerminal = false;
  double _sliderValor = 1.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),
            Divider(),
            _crearDropDown(),
            Divider(),
            _crearCheckBox(),
            Divider(),
            _crearSlider(),
            
            Divider(),
            Divider(),
            _visualizarDatos(),

          ],
        ));
  }

  Widget _visualizarDatos(){
    return Column(
      children: [
        Text('Nombre del paciente: $_nombre'),
        Text('Email del paciente: $_email'),
        Text('Password: $_password'),
        Text('Tipo de paciente: $_valorEscojidoTipoPaciente'),
        Text('Enfermedad terminal: $_checkEnfermedadTerminal'),
        Text('Meses hospitalizado: ${_sliderValor.toInt()}'),

      ],
    );
  }
  
  Widget _crearSlider(){
    
    return Row(
      children: [
        Expanded(
          child: Slider(
              value: _sliderValor,
              onChanged: (nuevoValor){ setState(() {
                _sliderValor = (nuevoValor.toInt()).toDouble();
              }); },
            label: 'Número de meses hospitalizado:',
            divisions: 24,
            min: 1,
            max:24
          ),
        )
      ],
    );
    
  }
  
  Widget _crearCheckBox(){
    return CheckboxListTile(
        title: Text('Enfermedad terminal'),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0),side: BorderSide(color: Colors.grey)),
        value: _checkEnfermedadTerminal,
        onChanged: (nuevoValor) {   // Hay que llamar a setState para que redibuje el CheckBox
          setState(() { _checkEnfermedadTerminal = nuevoValor! ;});
        }

    );
  }
  Widget _crearDropDown(){
    return Container(
      height: 60.0,
      padding: EdgeInsets.only(left: 8.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(15.0),
        border: Border.all(
          color: Colors.grey
        )
      ),
      child: Row(
        children: [
          DropdownButton(
            value: _valorEscojidoTipoPaciente,
            onChanged: (String? valor) { setState(() { _valorEscojidoTipoPaciente = valor!; });},
            items: _crearItemsDropDown(),

          ),
          Spacer(),
          Container(child: Icon(Icons.api),
                    padding: EdgeInsets.only(right: 10.0),
          ),
        ],
      ),
    );
  }

  List<DropdownMenuItem<String>> _crearItemsDropDown(){

    List<DropdownMenuItem<String>> lista = [];
    _optTipoPaciente.forEach((element) {
       lista.add(DropdownMenuItem(child: Text('$element'),value: element,));
    });

    return lista;
  }

  Widget _crearEmail(){

    return TextField(
      onChanged: (valor) => setState((){
                                        _email = valor;
                                        print(valor);}
                                    ),   // Fijarse que podemos guardar una cadena vacía
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
        hintText: 'Email del paciente',
        labelText: 'Email',
        suffixIcon: Icon(Icons.email),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(15.0),
        )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      onChanged: (valor) => setState((){_password = valor;}),   // Fijarse que podemos guardar una cadena vacía
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      onChanged: (valor) => setState((){_nombre = valor;}),   // Fijarse que podemos guardar una cadena vacía
      textCapitalization: TextCapitalization.words,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: ${_nombre.length}',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}


  • Ejercicio propuesto: Colocar en la parte superior una imagen y haz que al modificar el Slider se modifique el tamaño de la imagen (en la pantalla, detrás de la imagen, tiene un Container de fondo color rojo, para que no desplace todo el contenido al cambiar de tamaño la imagen)
Flutter dart formulario 6.JPG



Solución al ejercicio propuesto:
import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}


class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  List<String> _optTipoPaciente = ['Crónico', 'Terminal', 'Observación'];
  String _valorEscojidoTipoPaciente = 'Crónico';

  bool _checkEnfermedadTerminal = false;
  double _sliderValor = 1.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _imagenPaciente(),
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),
            Divider(),
            _crearDropDown(),
            Divider(),
            _crearCheckBox(),
            Divider(),
            _crearSlider(),
            
            Divider(),
            Divider(),
            _visualizarDatos(),

          ],
        ));
  }

  Widget _visualizarDatos(){
    return Column(
      children: [
        Text('Nombre del paciente: $_nombre'),
        Text('Email del paciente: $_email'),
        Text('Password: $_password'),
        Text('Tipo de paciente: $_valorEscojidoTipoPaciente'),
        Text('Enfermedad terminal: $_checkEnfermedadTerminal'),
        Text('Meses hospitalizado: ${_sliderValor.toInt()}'),

      ],
    );
  }
  
  Widget _imagenPaciente(){
    return Container(
      decoration: BoxDecoration(color: Colors.red),
      margin: EdgeInsets.only(bottom: 10.0),
      width: 200,
      height: 200,
      alignment: Alignment.center,  // Necesario para que haga caso del width y height del Image.network
      child: Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg',
          width: _sliderValor*10,
          height: _sliderValor*10,
          fit: BoxFit.scaleDown
      ),
    );
  }
  
  Widget _crearSlider(){
    
    return Row(
      children: [
        Expanded(
          child: Slider(
              value: _sliderValor,
              onChanged: (nuevoValor){ setState(() {
                _sliderValor = (nuevoValor.toInt()).toDouble();
              }); },
            label: 'Número de meses hospitalizado:',
            divisions: 24,
            min: 1,
            max:24
          ),
        ),
      ],
    );
    
  }
  
  Widget _crearCheckBox(){
    return CheckboxListTile(
        title: Text('Enfermedad terminal'),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0),side: BorderSide(color: Colors.grey)),
        value: _checkEnfermedadTerminal,
        onChanged: (nuevoValor) {   // Hay que llamar a setState para que redibuje el CheckBox
          setState(() { _checkEnfermedadTerminal = nuevoValor! ;});
        }

    );
  }
  Widget _crearDropDown(){
    return Container(
      height: 60.0,
      padding: EdgeInsets.only(left: 8.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(15.0),
        border: Border.all(
          color: Colors.grey
        )
      ),
      child: Row(
        children: [
          DropdownButton(
            value: _valorEscojidoTipoPaciente,
            onChanged: (String? valor) { setState(() { _valorEscojidoTipoPaciente = valor!; });},
            items: _crearItemsDropDown(),

          ),
          Spacer(),
          Container(child: Icon(Icons.api),
                    padding: EdgeInsets.only(right: 10.0),
          ),
        ],
      ),
    );
  }

  List<DropdownMenuItem<String>> _crearItemsDropDown(){

    List<DropdownMenuItem<String>> lista = [];
    _optTipoPaciente.forEach((element) {
       lista.add(DropdownMenuItem(child: Text('$element'),value: element,));
    });

    return lista;
  }

  Widget _crearEmail(){

    return TextField(
      onChanged: (valor) => setState((){
                                        _email = valor;
                                        print(valor);}
                                    ),   // Fijarse que podemos guardar una cadena vacía
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
        hintText: 'Email del paciente',
        labelText: 'Email',
        suffixIcon: Icon(Icons.email),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(15.0),
        )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      onChanged: (valor) => setState((){_password = valor;}),   // Fijarse que podemos guardar una cadena vacía
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      onChanged: (valor) => setState((){_nombre = valor;}),   // Fijarse que podemos guardar una cadena vacía
      textCapitalization: TextCapitalization.words,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: ${_nombre.length}',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}



Botones



  • En Flutter disponemos de diferentes tipos de botones con diferente apareciencia visual.
Veremos en esta sección alguno de ellos.
Flutter dart formulario 11.JPG



ElevetedButton


  • En la vesión actual de Flutter es el Widget 'recomendado' para visualizar un botón elevado (que aparentemente está 'por encima' del formulario).
En versiones anteriores se hacía uso del Widget RaisedButton.
A medida que cambian las versiones, Flutter incorpora nuevos Widget´s y deja de dar soporte a los anteriores. En esos casos aparecerá un mensaje de deprecated y en el IDE aparecerá la clase tachada:
Flutter dart formulario 10.JPG
En esos casos es mejor reemplazar el Widget por el recomendado, como en este caso, por el ElevatedButton.


  • Las propiedades principales:
  • onPressed: Obligatorio. Para gestionar el evento de Click sobre el botón.
  • child: Obligatorio. El widget que va a visualizar el botón. Normalmente un texto, pero podría ser cualquier cosa.
  • style: Estilo asociado al botón. En principio podría llevar un objeto de la clase ButtonStyle si queremos tener un control total del mismo, pero normalmente se parte de un estido 'predefinido' para este tipo de botón, y después se modifica lo que se quiera, por eso se hace uso de la clase ElevatedButton.styleFrom().
 
import 'package:flutter/material.dart';

class FormularioPage extends StatefulWidget {
  const FormularioPage({Key? key}) : super(key: key);

  @override
  _FormularioPageState createState() => _FormularioPageState();
}

class _FormularioPageState extends State<FormularioPage> {
  String _nombre = '';
  String _email = '';
  String _password = '';

  List<String> _optTipoPaciente = ['Crónico', 'Terminal', 'Observación'];
  String _valorEscojidoTipoPaciente = 'Crónico';

  bool _checkEnfermedadTerminal = false;
  double _sliderValor = 1.0;

  String _botonPulsado = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Formulario'),
        ),
        body: ListView(
          padding: EdgeInsets.symmetric(horizontal: 5.0,vertical: 10.0),
          children: [
            _crearInput(),    // TextField con múltiples opciones de decoración
            Divider(),
            _crearEmail(),
            Divider(),
            _crearPassword(),
            Divider(),
            _crearDropDown(),
            Divider(),
            _crearCheckBox(),
            Divider(),
            _crearSlider(),
            Divider(),
            _crearElevatedButton(),

            Divider(),
            Divider(),
            _visualizarDatos(),

          ],
        ));
  }

  Widget _visualizarDatos(){
    return Column(
      children: [
        Text('Nombre del paciente: $_nombre'),
        Text('Email del paciente: $_email'),
        Text('Password: $_password'),
        Text('Tipo de paciente: $_valorEscojidoTipoPaciente'),
        Text('Enfermedad terminal: $_checkEnfermedadTerminal'),
        Text('Meses hospitalizado: ${_sliderValor.toInt()}'),
        Text('Botones pulsados: $_botonPulsado'),

      ],
    );
  }

  Widget _crearElevatedButton(){
    final estiloBotonElevado = ElevatedButton.styleFrom(
        textStyle: TextStyle(color: Colors.blue, fontSize: 20)
    );  // Podríamos definir los estilos a nivel global y aplicarlos a mas botones

    return ElevatedButton(
        style: estiloBotonElevado,
        onPressed: () { setState(() {
          _botonPulsado = 'Pulsado ElevatedButton';
        });},
        child: ListTile(
          title: Text('ELEVATED BUTTON'),
          trailing: Icon(Icons.smart_button),
        )
    );

  }

  Widget _crearSlider(){

    return Row(
      children: [
        Expanded(
          child: Slider(
              value: _sliderValor,
              onChanged: (nuevoValor){ setState(() {
                _sliderValor = (nuevoValor.toInt()).toDouble();
              }); },
              label: 'Número de meses hospitalizado:',
              divisions: 24,
              min: 1,
              max:24
          ),
        )
      ],
    );

  }

  Widget _crearCheckBox(){
    return CheckboxListTile(
        title: Text('Enfermedad terminal'),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0),side: BorderSide(color: Colors.grey)),
        value: _checkEnfermedadTerminal,
        onChanged: (nuevoValor) {   // Hay que llamar a setState para que redibuje el CheckBox
          setState(() { _checkEnfermedadTerminal = nuevoValor! ;});
        }

    );
  }
  Widget _crearDropDown(){
    return Container(
      height: 60.0,
      padding: EdgeInsets.only(left: 8.0),
      decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(15.0),
          border: Border.all(
              color: Colors.grey
          )
      ),
      child: Row(
        children: [
          DropdownButton(
            value: _valorEscojidoTipoPaciente,
            onChanged: (String? valor) { setState(() { _valorEscojidoTipoPaciente = valor!; });},
            items: _crearItemsDropDown(),

          ),
          Spacer(),
          Container(child: Icon(Icons.api),
            padding: EdgeInsets.only(right: 10.0),
          ),
        ],
      ),
    );
  }

  List<DropdownMenuItem<String>> _crearItemsDropDown(){

    List<DropdownMenuItem<String>> lista = [];
    _optTipoPaciente.forEach((element) {
      lista.add(DropdownMenuItem(child: Text('$element'),value: element,));
    });

    return lista;
  }

  Widget _crearEmail(){

    return TextField(
      onChanged: (valor) => setState((){
        _email = valor;
        print(valor);}
      ),   // Fijarse que podemos guardar una cadena vacía
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
          hintText: 'Email del paciente',
          labelText: 'Email',
          suffixIcon: Icon(Icons.email),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }


  Widget _crearPassword(){

    return TextField(
      onChanged: (valor) => setState((){_password = valor;}),   // Fijarse que podemos guardar una cadena vacía
      obscureText: true,
      obscuringCharacter: '-',
      maxLength: 20,
      decoration: InputDecoration(
          hintText: 'Password de entrada',
          labelText: 'Password',
          suffixIcon: Icon(Icons.password),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(15.0),
          )
      ),
    );
  }

  Widget _crearInput() {

    return TextField(
      onChanged: (valor) => setState((){_nombre = valor;}),   // Fijarse que podemos guardar una cadena vacía
      textCapitalization: TextCapitalization.words,
      keyboardType: TextInputType.name,
      decoration: InputDecoration(
        border: OutlineInputBorder(     // Establece un borde cicular/otro  alrededor de la caja de texto
          borderRadius: BorderRadius.circular(15.0),
        ),
        counterText: 'Número de caracteres: ${_nombre.length}',   // Texto que aparece a la derecha y debajo de la caja
        hintText: 'Nombre del paciente',          // Texto que aparece dentro de la caja y desaparece al escribir
        labelText: 'Nombre',                      // Texto qye aparece encima de la caja AL PULSAR EN EL INTERIOR PARA ESCRIBIR
        helperText: 'Introduce el nombre del paciente',  // Texto que aparece a la izquierda y abajo de la caja
        suffixIcon: Icon(Icons.access_alarm),     // Widget que aparece a la derecha dentro de la caja
        icon: Icon(Icons.accessibility),          // Widget que aparece a la izquierda fuera de la caja

      ),
    );

  }

}



TextButton


  • En la vesión actual de Flutter es el Widget 'recomendado' para visualizar un botón que se recomienda usar en la barra de herramientas (toolbar), diálogos y en donde queramos visualizar un botón que se integre con el resto de contenido que le rodea pero separado suficientemento del resto de contenido para que se sepa que es un botón.
En versiones anteriores se hacía uso del [hhttps://api.flutter.dev/flutter/material/FlatButton-class.html Widget FlatButton].
A medida que cambian las versiones, Flutter incorpora nuevos Widget´s y deja de dar soporte a los anteriores. En esos casos aparecerá un mensaje de deprecated y en el IDE aparecerá la clase tachada:
Flutter dart formulario 10.JPG
En esos casos es mejor reemplazar el Widget por el recomendado, como en este caso, por el TextButton.


  • Las propiedades principales:
  • onPressed: Obligatorio. Para gestionar el evento de Click sobre el botón.
  • child: Obligatorio. El widget que va a visualizar el botón. Normalmente un texto, pero podría ser cualquier cosa.
  • style: Estilo asociado al botón. En principio podría llevar un objeto de la clase ButtonStyle si queremos tener un control total del mismo, pero normalmente se parte de un estido 'predefinido' para este tipo de botón, y después se modifica lo que se quiera, por eso se hace uso de la clase TextButton.styleFrom().


  Widget _crearTextButton(){
    final estiloBotonText = TextButton.styleFrom(
        textStyle: TextStyle(fontSize: 30),
        backgroundColor: Colors.green,
        primary: Colors.red // Este botón no hace caso a cambiar el color en el textStyle
    );  // Podríamos definir los estilos a nivel global y aplicarlos a mas botones

    return TextButton(
        style: estiloBotonText,
        onPressed: () { setState(() {
          _botonPulsado = 'Pulsado TextButton';
        });},
        child: Text('TEXT BUTTON'),
    );

  }


  • NOTA: Disponemos de otro botón que es el OutlineButton que es igual al TextButton pero visualmente tiene un borde redondeado rodeando la forma del botón.



Float Action Button


  • Son botones de aspecto circular que aparecen visualmente por encima del contenido y que normalmente son reservados para realizar la 'acción principal' de la pantalla.
Por ejemplo, en una pantalla en la que se listan los clientes, podrímaos poner un FAB para dar de alta a nuevos clientes.
Como ya llevamos visto, suelen usarse conjuntamente con un Scaffold Widget.


  • Las propiedades principales:
  • onPressed: Obligatorio. Para gestionar el evento de Click sobre el botón.
  • child: Widget que se va a visualizar dentro del botón. Normalmente un icono.


  Widget _crearFAB(){

    return FloatingActionButton(
        onPressed: () {
          setState(() {
            _botonPulsado = 'Pulsado FAB';
          });
        },
        child: Icon(Icons.add),
      foregroundColor: Colors.amber,
      backgroundColor: Colors.brown,
    );
  }



Formularios



Generando Widget´s dinámicamente con Mapas

  • Un mapa (o una lista) puede ser muy útil para generar un conjunto de Widget a partir de los datos que se guardan en el mismo.
  • Imagenemos que tenemos el siguiente mapa:
  Map<String, bool> valores = {
    'Buenos': false,
    'Altos': false,
    'Generosos' : true,
  };


Ahora queremos generar un conjunto de CheckBox´s en función de los datos de este mapa...
Para ello accederemos al conjunto de keys del mapa de la forma 'valores.keys' y lanzar el método map de la forma valores.keys.maps
Dicho método va a recibir como parámetro una key (ya que estamos accediendo a valores.keys) y va a devolver lo que nosotros queramos (en este caso vamos a devolver un CheckBoxLisTile). Esto lo va a repetir por cada elemento key del mapa. Por lo tanto, cuando termine tendremos un conjunto de CheckBoxLitTile (es un objeto de clase Iterable), pero para poder visualizar este conjunto de widget tendremos que convertir todo a una lista, empleando el método toList().


Widget _listaCheckBoxes(){
    return Column(
      children: valores.keys.map((String key) {
      return new CheckboxListTile(
        title: new Text(key),
        value: valores[key],
        onChanged: (bool? value) {
          setState(() {
            valores[key] = value!;
          });
          print(valores);
        },
      );
    }).toList()

    );
}
Nota: Indicar que con este código, el mapa valores tendrá actualizado el estado de cada uno de los checkboxes al cambiar su valor visualmente.




Enlace a la página principal de la UD4

Enlace a la página principal del curso




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