Diferencia entre revisiones de «Flutter StatefulWidget. Conceptos»
Ir a la navegación
Ir a la búsqueda
| Línea 397: | Línea 397: | ||
: ¿ Se os ocurre alguna idea ? :) | : ¿ Se os ocurre alguna idea ? :) | ||
| + | |||
| + | |||
| + | <br /> | ||
| + | : '''Posible Solución:''' Una forma (que no la única) con lo que sabemos hasta ahora, podría ser guardar los valores 'locales' de cada botón en una lista. En ese caso, el botón 1 tendría que modificar la posición 0 de la lista, el botón 2, la posición segunda de la lista y el botón 3 la tercera posición. Por lo tanto, cuando llamo a la función que representa el Widget botón, podría enviarle la posición a modificar. | ||
| + | |||
| + | |||
:<syntaxhighlight lang="java" enclose="div" highlight="33,37,41,11,24-26" > | :<syntaxhighlight lang="java" enclose="div" highlight="33,37,41,11,24-26" > | ||
| Línea 445: | Línea 451: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | |||
| − | |||
| − | |||
| − | |||
| Línea 459: | Línea 461: | ||
* '''Objetivo:''' Al pulsar sobre un elemento de la lista, este queda seleccionado visualmente y una etiqueta muestra el valor seleccionado. | * '''Objetivo:''' Al pulsar sobre un elemento de la lista, este queda seleccionado visualmente y una etiqueta muestra el valor seleccionado. | ||
| + | : Al pulsar durante unos segundos sobre un elemento de la lista, el elemento desaparece. | ||
| + | : Emplear un ListTile como Widget de cada elemento y poder gestionar los dos eventos y recordar que podemos 'envolverlo' en un Widget Ink para darle un color de fondo y un efecto al pulsar. | ||
| + | : Indicar que en cualquier propiedad de cualquier Widget podéis llamar a una función o tener un código que devuelva el tipo de dato que espera dicha propiedad. | ||
| − | [[Imagen: | + | [[Imagen:Flutter_dart_exemplo_3_stateful.JPG|250px|center]] |
* '''Solución:''' Los datos de la lista van a estar en un List. Necesitamos guardar el elemento seleccionado (o su posición) para que cuando se 'dibuje' comprobar si el elemeto que se está dibujando es el elemento seleccionado, y en ese caso, poner un color de fondo al mismo. | * '''Solución:''' Los datos de la lista van a estar en un List. Necesitamos guardar el elemento seleccionado (o su posición) para que cuando se 'dibuje' comprobar si el elemeto que se está dibujando es el elemento seleccionado, y en ese caso, poner un color de fondo al mismo. | ||
| + | : La propiedad que queremos cambiar es color del Widget Ink. | ||
| + | : Por otro lado, al pulsar dos veces necesitamos eliminar el elemento de la lista y redibujar la lista. | ||
| + | : Vamos a tener que 'redibujar' el Widget tanto cuando pulsemos un elemento de la lista, como cuando | ||
| + | |||
| + | |||
| + | :<syntaxhighlight lang="java" enclose="div" highlight="11,12,35,37-48" > | ||
| + | import 'package:flutter/material.dart'; | ||
| + | |||
| + | class Aprender3Page extends StatefulWidget { | ||
| + | const Aprender3Page({Key? key}) : super(key: key); | ||
| + | |||
| + | @override | ||
| + | _Aprender3PageState createState() => _Aprender3PageState(); | ||
| + | } | ||
| + | |||
| + | class _Aprender3PageState extends State<Aprender3Page> { | ||
| + | List<String> _listaElementos = ['Elemento1','Elemento2','Elemento3']; | ||
| + | String _elemSeleccionado=''; | ||
| + | |||
| + | @override | ||
| + | Widget build(BuildContext context) { | ||
| + | return Scaffold( | ||
| + | appBar: AppBar( | ||
| + | title: Text('Aprender3 Statefulwidget'), | ||
| + | ), | ||
| + | body: Center( | ||
| + | child: _obtenerLista() | ||
| + | ) | ||
| + | ); | ||
| + | } | ||
| + | Widget _obtenerLista(){ | ||
| − | : | + | return ListView( |
| + | children: _listaElementos.map((e) => _elementoLista(e)).toList(), | ||
| + | ); | ||
| + | } | ||
| + | |||
| + | Widget _elementoLista(String elemento){ | ||
| + | return Ink( | ||
| + | color: _elemSeleccionado == elemento ? Colors.red : Colors.indigo, | ||
| + | child: ListTile( | ||
| + | title: Text('$elemento'), | ||
| + | onTap: () { | ||
| + | setState(() { | ||
| + | _elemSeleccionado = elemento; | ||
| + | }); | ||
| + | }, | ||
| + | onLongPress: (){ | ||
| + | setState(() { | ||
| + | _listaElementos.remove(elemento); | ||
| + | _elemSeleccionado = ''; | ||
| + | }); | ||
| + | }, | ||
| + | ), | ||
| + | ); | ||
| + | } | ||
| + | |||
| + | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Línea 473: | Línea 534: | ||
<br /> | <br /> | ||
| − | ===Ejemplo 3=== | + | ===Ejemplos Stateless-Stateful combinados=== |
| + | |||
| + | * Hasta ahora toda nuestra pantalla era un StatefulWidget, en la que guardábamos de forma 'global' las variables que necesitábamos para los Widgets que conformaban la pantalla, y dichos Widgets podían acceder a estas variables y llamar al método setState al estar definidos 'dentro' de la clase State del Stateful Widget. | ||
| + | : Pero en una situación real, esto normalmente no sucede. Los Widget´s los tendremos definidos en archivos diferentes y en clases diferentes. | ||
| + | |||
| + | * Las combinaciones entre Widget´s pueden ser: | ||
| + | :* StatelessWidget - StatefulWidget | ||
| + | :* StatelessWidget - StatelessWidget (ya vista) | ||
| + | :* StatefulWidget - StatelessWidget | ||
| + | :* StatefulWidget - StatefulWidget | ||
| + | |||
| + | * Y además, estas combinaciones se pueden dar entre widgets que conforman diferentes pantallas, que conforman la misma pantalla y se encuentran al mismo nivel en el árbol de jerarquía de Widget, o que se encuentra una dentro de otro en el árbol. | ||
| + | |||
| + | <br /> | ||
| + | ====Ejemplo 4==== | ||
| + | |||
| + | '''CASO <u>StatelessWidget -> StatefulWidget</u>''': Un StatefulWiget dentro de un StatelessWidget en la jerarquía. | ||
| + | |||
| + | |||
| + | * En este ejemplo, partimos que la pantalla principal (el Scaffold) es un StatelessWidget y que empleamos un Widget Stateful para visualizar una lista de elementos. | ||
| + | : Los datos están en el Stateless y necesitamos pasarlos al StatefulWidget. | ||
| + | : El Stateful hará uso de dichos datos y podrá modificarlos redibujándose cuando lo necesite (sólo se redibujará | ||
| + | |||
| + | : Para ello, empleamos el constructor de StatefulWidget. | ||
| + | : <u>Nota:</u> [https://wiki.cifprodolfoucha.es/index.php?title=Flutter_StatefulWidget._Usos#Paso_de_datos_entre_Widgets Veremos posteriormente] las diferentes formas que tiene Flutter de compartir información entre Widget. | ||
| + | |||
| + | * '''Objetivo:''' Al pulsar sobre un elemento de la lista, este queda seleccionado visualmente y una etiqueta muestra el valor seleccionado. | ||
| + | : Al pulsar durante unos segundos sobre un elemento de la lista, el elemento desaparece. | ||
| + | : Emplear un ListTile como Widget de cada elemento y poder gestionar los dos eventos y recordar que podemos 'envolverlo' en un Widget Ink para darle un color de fondo y un efecto al pulsar. | ||
| + | : La lista debe estar definida en un StatefulWidget y el Scaffold en un StatelessWidget. | ||
| + | |||
| + | [[Imagen:Flutter_dart_exemplo_3_stateful.JPG|250px|center]] | ||
| + | |||
| + | :<syntaxhighlight lang="java" enclose="div" highlight="3,5,25,27,34,41,58" > | ||
| + | import 'package:flutter/material.dart'; | ||
| + | |||
| + | class Aprender4Page extends StatelessWidget { | ||
| + | // Fijarse que al estar en un StatelessWidget, los datos deben ser guarados en una constante. | ||
| + | final List<String> _listaElementos = ['Elemento1','Elemento2','Elemento3']; | ||
| + | |||
| + | Aprender4Page({Key? key}) : super(key: key); | ||
| + | |||
| + | @override | ||
| + | Widget build(BuildContext context) { | ||
| + | print('Dibuja el Scaffold'); | ||
| + | return Scaffold( | ||
| + | appBar: AppBar( | ||
| + | title: Text('Aprender3 Statefulwidget'), | ||
| + | ), | ||
| + | body: Center( | ||
| + | child: WidgetLista(listaElementosConstante: _listaElementos,) | ||
| + | ) | ||
| + | ); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class WidgetLista extends StatefulWidget { | ||
| + | // Un statefulwidget también es inmutable. Sólo admite variables en la clase State | ||
| + | final List<String> listaElementosConstante; // A paser que la lista es final, se puede modificar. | ||
| + | |||
| + | const WidgetLista({Key? key, required this.listaElementosConstante}) : super(key: key); | ||
| + | |||
| + | @override | ||
| + | WidgetListaState createState() => WidgetListaState(); | ||
| + | } | ||
| + | |||
| + | class WidgetListaState extends State<WidgetLista> { | ||
| + | String _elemSeleccionado=''; | ||
| + | |||
| + | @override | ||
| + | Widget build(BuildContext context) { | ||
| + | print('Dibuja la lista:${widget.listaElementosConstante}'); | ||
| − | + | return ListView( | |
| − | : | + | children: widget.listaElementosConstante.map((e) => _elementoLista(e)).toList(), |
| + | ); | ||
| + | } | ||
| − | |||
| − | |||
| − | : | + | Widget _elementoLista(String elemento){ |
| + | return Ink( | ||
| + | color: _elemSeleccionado == elemento ? Colors.red : Colors.indigo, | ||
| + | child: ListTile( | ||
| + | title: Text('$elemento'), | ||
| + | onTap: () { | ||
| + | setState(() { | ||
| + | _elemSeleccionado = elemento; | ||
| + | }); | ||
| + | }, | ||
| + | onLongPress: (){ | ||
| + | setState(() { | ||
| + | widget.listaElementosConstante.remove(elemento); | ||
| + | _elemSeleccionado = ''; | ||
| + | }); | ||
| + | }, | ||
| + | ), | ||
| + | ); | ||
| + | } | ||
| + | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Revisión del 19:16 4 nov 2021
Sumario
Introducción
- Más información en:
- Hasta ahora hemos usado los StatelessWidget para todo.
- Este tipo de Widget:
- No permite guardar ningún tipo de dato en forma de variable y cuando definimos algo a nivel de clase siempre debe estar asociado al modificador final o static const.
- Si no se pone dicho modificador, no impide compilar la aplicación y aparecerá un Warning en la definición de la Clase (el nombre de la clase aparecerá subrayada).
- Un ejemplo de uso sería cuando tenemos un formulario y queremos guardar lo escrito por el usuario en una variable. Esto no lo podríamos realizar con este tipo de Widget.
- No se pueden 'redibujar' para hacer que reflejen un cambio que pueda darse por programación. Imagináos el caso de que busquéis datos por Internet y se carguen nuevos datos en la lista en la que se basa un ListView para visualizar el contenido...
- En Flutter, no funciona igual que la programación tradicional con entornos gráficos, en los que se tiene una pantalla con componentes (cajas de texto, listas,...) y cambiando sus valores automáticamente se reflejan dichos cambios en el componente visual.
- En Flutter, los componentes visuales (Widget´s) si visualizan en base a los datos definidos en el Widget. Si dichos datos son cambiados, es necesario redibujar el Widget para que se vuelva a visualizar con los datos modificados.
- Así, si por ejemplo, tengo que una etiqueta (Widget Text) visualiza el contenido de una variable, y cambio el valor de dicha variable, es necesario redibujar el Widget etiqueta para que vuelva a visualizar el contenido cambiado.
Minuto 10: Indico que se debe devolver un EjemploStateful cuando debería ser un _EjemploStatefulState
Código explicativo del vídeo.
import 'package:flutter/material.dart'; class EjemploStateLess extends StatelessWidget { int _valor = 0; // Debería ser final ya que un StatelessWidget está definido como inmutable @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('StateLessWidget'), ), body: _boton() ); } Widget _boton(){ return Center(child: TextButton(child:Text('Presiona $_valor'), onPressed: (){ print('pulsado $_valor'); _valor = _valor + 1 ; // Aunque su valor se modifica nunca podríamos 'visualizar' su contenido actualizado. }, style: TextButton.styleFrom(backgroundColor: Colors.amber), ) ); } } class EjemploStateful extends StatefulWidget { @override _EjemploStatefulState createState() => _EjemploStatefulState(); } class _EjemploStatefulState extends State<EjemploStateful> { int _valor = 10; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('appbarTitle'), ), body: _boton() ); } Widget _boton(){ return Center(child: TextButton(child:Text('Presiona valores $_valor'), onPressed: (){ print('pulsado $_valor'); setState(() { _valor++; }); }, style: TextButton.styleFrom(backgroundColor: Colors.amber), ) ); } }
Accediendo a las propiedes del StatefulWidget desde el State
- Cuando definimos una propiedad local dentro del StatefulWidget, podremos acceder a dicha variable dentro de la clase State empleado la palabra widget.propiedad.
- Veamos un ejemplo en el que definimos la variable contador dentro del StatefulWidget botón.
import 'package:flutter/material.dart'; class EjemploStateLess extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('StateLessWidget'), ), body: Column( children: [ _BotonStateful() // Recordar que esto es equivalente a new _BotonStateless ], ) ); } } class _BotonStateful extends StatefulWidget { final colorAzul = Colors.blue; @override _BotonStatefulState createState() => _BotonStatefulState(); } class _BotonStatefulState extends State<_BotonStateful> { int _contador = 0; @override Widget build(BuildContext context) { return Center(child: TextButton(child:Text('Botón Stateful ${widget._contador}'), style: TextButton.styleFrom(backgroundColor: widget.colorAzul), onPressed: () { setState(() { _contador++; }); }, ) ); } }
- Nota: Lo 'normal' es que las constantes estén definidas en el Stateful (partes invariantes) y las variables en el State asociado.
Combinando Widgets
- Indicar que dentro de una clase podemos tener diferentes Widget de tipo Stateless o Stateful.
- En el siguiente ejemplo, dentro de un StatelessWidget creo dos Widgets botones, uno de tipo StatelessWidget y otro de tipo StatefulWidget.
- Ya veremos posteriormente como podemos pasar datos entre Widgets y entre pantallas diferentes.
- En el ejemplo creo una clase (Datos) que guarda el valor que quiero enviar a los dos botones.
- Al pulsar sobre el StatelesWidget, puedo incrementar el valor de dicha variable, pero no puedo redibujar el Widget, por lo que el contador aumentará pero no se visualizará hasta que pulse sobre el StatefulWidget el cual se redibujará con el nuevo valor.
import 'package:flutter/material.dart'; class _Datos{ int valor = 0; } final dato = new _Datos(); class EjemploStateLess extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('StateLessWidget'), ), body: Column( children: [ _BotonStateless(), // Recordar que esto es equivalente a new _BotonStateless _BotonStateful() ], ) ); } } class _BotonStateful extends StatefulWidget { @override _BotonStatefulState createState() => _BotonStatefulState(); } class _BotonStatefulState extends State<_BotonStateful> { @override Widget build(BuildContext context) { return Center(child: TextButton(child:Text('Botón Stateful ${dato.valor}'), style: TextButton.styleFrom(backgroundColor: Colors.amber), onPressed: () { setState(() { dato.valor++; }); }, ) ); } } class _BotonStateless extends StatelessWidget { @override Widget build(BuildContext context) { return Center(child: TextButton(child:Text('Botón Stateless ${dato.valor}'), style: TextButton.styleFrom(backgroundColor: Colors.amber), onPressed: () { dato.valor++; }, ) ); } }
Ejemplos de uso
- Voy a explicar a continuación varios ejemplos que irán completando la explicación anterior...
- En todos los ejemplos, salvo que se indique lo contrario, debéis hacer lo mismo:
- Creamos una nueva página en la carpeta pages/stateful/aprender de nombre aprenderNUM_page.dart (crear los directorios dentro de lib si no están creados previamente). NUM es el número de ejemplo.
- 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 (AprenderNUMPage) que derive de StatefulWidget. Se creará (si lo hacéis con un Snippet) una clase asociada que deriva de State. NUM es el número de ejemplo.
- Modificamos la clase State y en su métod build hacemos que devuelva un Scaffold con una Appbar (hacerlo con un Snippet)
Ejemplo 1
- En este ejemplo, partimos que todo nuestro Widget Scaffold es un StatefulWidget.
- Dentro del mismo, todos los Widget que definamos 'localmente', van poder llamar al método setState para redibujar el StatefulWidget y por tanto redibujará toda la pantalla.
- Objetivo: Modificar el tamaño (ancho) de un container el pulsar sobre el botón.
- Solución: Para lograr este objetivo, necesitamos emplear una variable (ya que su valor va a aumentar al pulsar el botón) y asociar dicha variable al ancho del contenedor.
- Por lo tanto, al pulsar sobre el botón, es necesario modificar el valor de la variable y redibujar el contenedor (en este momento no sabemos hacer eso, sabemos redibujar el StatefulWidget que es la pantalla entera, y por tanto se va a redibujar el botón, el contenedor, la AppBar,es decir, toda la pantalla.
import 'package:flutter/material.dart'; /** * Neste exemplo aprendemos a diferenza entre Stateless e Stateful * Temos que entender como funcionan os Widgets en Flutter. */ class Aprender1Page extends StatefulWidget { const Aprender1Page({Key? key}) : super(key: key); @override _Aprender1PageState createState() => _Aprender1PageState(); } class _Aprender1PageState extends State<Aprender1Page> { double _ancho = 50; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Aprender1 Statefulwidget'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: EdgeInsets.all(10), width: _ancho, height: 50, color: Colors.indigo, ), TextButton( child: Text('PRESIONAME'), style: TextButton.styleFrom(backgroundColor: Colors.black), onPressed: () { setState(() { _ancho+=20; }); }, ) ], ), ) ); } }
Ejemplo 2
- En este ejemplo, partimos que todo nuestro Widget Scaffold es un StatefulWidget.
- Dentro del mismo, todos los Widget que definamos 'localmente', van poder llamar al método setState para redibujar el StatefulWidget y por tanto redibujará toda la pantalla.
- Objetivo: En este ejemplo, disponemos de 3 botones. Cada botón va a llevar un contador de cuantos veces se ha pulsado, y también cuantas veces se han pulsado todos los botones.
- El número de veces pulsado / total, se visualizará en el texto de botón.
- Solución: Necesitamos emplear 4 variables. Una para cada botón, y que guardará información de cuantas veces se ha pulsado dicho botón, y otra que guardará la suma de todos los botones pulsados.
import 'package:flutter/material.dart'; class Aprender2Page extends StatefulWidget { const Aprender2Page({Key? key}) : super(key: key); @override _Aprender2PageState createState() => _Aprender2PageState(); } class _Aprender2PageState extends State<Aprender2Page> { int _cont1=0, _cont2=0,_cont3=0; int _contTotal = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Aprender2 Statefulwidget'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _boton1(), _boton2(), _boton3() ], ), ) ); } Widget _boton1(){ return TextButton( onPressed: () { setState(() { _cont1++; _contTotal++; }); }, child: Text('Pulsado $_cont1. Total veces $_contTotal') ); } Widget _boton2(){ return TextButton( onPressed: () { setState(() { _cont2++; _contTotal++; }); }, child: Text('Pulsado $_cont2. Total veces $_contTotal') ); } Widget _boton3(){ return TextButton( onPressed: () { setState(() { _cont3++; _contTotal++; }); }, child: Text('Pulsado $_cont3. Total veces $_contTotal') ); } }
- Si analizáis un poco el código es daréis cuenta que no está muy 'optimizado' ya que por cada botón tenemos el mismo código, variando el número de veces que se pulsa ese botón.
- Lo ideal sería, de alguna forma, que el botón 'supiera' cual es la variable contador asociada al mismo.
- ¿ Se os ocurre alguna idea ? :)
- Posible Solución: Una forma (que no la única) con lo que sabemos hasta ahora, podría ser guardar los valores 'locales' de cada botón en una lista. En ese caso, el botón 1 tendría que modificar la posición 0 de la lista, el botón 2, la posición segunda de la lista y el botón 3 la tercera posición. Por lo tanto, cuando llamo a la función que representa el Widget botón, podría enviarle la posición a modificar.
import 'package:flutter/material.dart'; class Aprender2Page extends StatefulWidget { const Aprender2Page({Key? key}) : super(key: key); @override _Aprender2PageState createState() => _Aprender2PageState(); } class _Aprender2PageState extends State<Aprender2Page> { List<int> _listaPulsaciones = [0,0,0]; int _contTotal = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Aprender2 Statefulwidget'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _boton(0), _boton(1), _boton(2), ], ), ) ); } Widget _boton(int posLista){ return TextButton( onPressed: () { setState(() { _listaPulsaciones[posLista]++; _contTotal++; }); }, child: Text('Pulsado ${_listaPulsaciones[posLista]} Total veces $_contTotal') ); } }
Ejemplo 3
- En este ejemplo, partimos que todo nuestro Widget Scaffold es un StatefulWidget.
- Dentro del mismo, todos los Widget que definamos 'localmente', van poder llamar al método setState para redibujar el StatefulWidget y por tanto redibujará toda la pantalla.
- Objetivo: Al pulsar sobre un elemento de la lista, este queda seleccionado visualmente y una etiqueta muestra el valor seleccionado.
- Al pulsar durante unos segundos sobre un elemento de la lista, el elemento desaparece.
- Emplear un ListTile como Widget de cada elemento y poder gestionar los dos eventos y recordar que podemos 'envolverlo' en un Widget Ink para darle un color de fondo y un efecto al pulsar.
- Indicar que en cualquier propiedad de cualquier Widget podéis llamar a una función o tener un código que devuelva el tipo de dato que espera dicha propiedad.
- Solución: Los datos de la lista van a estar en un List. Necesitamos guardar el elemento seleccionado (o su posición) para que cuando se 'dibuje' comprobar si el elemeto que se está dibujando es el elemento seleccionado, y en ese caso, poner un color de fondo al mismo.
- La propiedad que queremos cambiar es color del Widget Ink.
- Por otro lado, al pulsar dos veces necesitamos eliminar el elemento de la lista y redibujar la lista.
- Vamos a tener que 'redibujar' el Widget tanto cuando pulsemos un elemento de la lista, como cuando
import 'package:flutter/material.dart'; class Aprender3Page extends StatefulWidget { const Aprender3Page({Key? key}) : super(key: key); @override _Aprender3PageState createState() => _Aprender3PageState(); } class _Aprender3PageState extends State<Aprender3Page> { List<String> _listaElementos = ['Elemento1','Elemento2','Elemento3']; String _elemSeleccionado=''; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Aprender3 Statefulwidget'), ), body: Center( child: _obtenerLista() ) ); } Widget _obtenerLista(){ return ListView( children: _listaElementos.map((e) => _elementoLista(e)).toList(), ); } Widget _elementoLista(String elemento){ return Ink( color: _elemSeleccionado == elemento ? Colors.red : Colors.indigo, child: ListTile( title: Text('$elemento'), onTap: () { setState(() { _elemSeleccionado = elemento; }); }, onLongPress: (){ setState(() { _listaElementos.remove(elemento); _elemSeleccionado = ''; }); }, ), ); } }
Ejemplos Stateless-Stateful combinados
- Hasta ahora toda nuestra pantalla era un StatefulWidget, en la que guardábamos de forma 'global' las variables que necesitábamos para los Widgets que conformaban la pantalla, y dichos Widgets podían acceder a estas variables y llamar al método setState al estar definidos 'dentro' de la clase State del Stateful Widget.
- Pero en una situación real, esto normalmente no sucede. Los Widget´s los tendremos definidos en archivos diferentes y en clases diferentes.
- Las combinaciones entre Widget´s pueden ser:
- StatelessWidget - StatefulWidget
- StatelessWidget - StatelessWidget (ya vista)
- StatefulWidget - StatelessWidget
- StatefulWidget - StatefulWidget
- Y además, estas combinaciones se pueden dar entre widgets que conforman diferentes pantallas, que conforman la misma pantalla y se encuentran al mismo nivel en el árbol de jerarquía de Widget, o que se encuentra una dentro de otro en el árbol.
Ejemplo 4
CASO StatelessWidget -> StatefulWidget: Un StatefulWiget dentro de un StatelessWidget en la jerarquía.
- En este ejemplo, partimos que la pantalla principal (el Scaffold) es un StatelessWidget y que empleamos un Widget Stateful para visualizar una lista de elementos.
- Los datos están en el Stateless y necesitamos pasarlos al StatefulWidget.
- El Stateful hará uso de dichos datos y podrá modificarlos redibujándose cuando lo necesite (sólo se redibujará
- Para ello, empleamos el constructor de StatefulWidget.
- Nota: Veremos posteriormente las diferentes formas que tiene Flutter de compartir información entre Widget.
- Objetivo: Al pulsar sobre un elemento de la lista, este queda seleccionado visualmente y una etiqueta muestra el valor seleccionado.
- Al pulsar durante unos segundos sobre un elemento de la lista, el elemento desaparece.
- Emplear un ListTile como Widget de cada elemento y poder gestionar los dos eventos y recordar que podemos 'envolverlo' en un Widget Ink para darle un color de fondo y un efecto al pulsar.
- La lista debe estar definida en un StatefulWidget y el Scaffold en un StatelessWidget.
import 'package:flutter/material.dart'; class Aprender4Page extends StatelessWidget { // Fijarse que al estar en un StatelessWidget, los datos deben ser guarados en una constante. final List<String> _listaElementos = ['Elemento1','Elemento2','Elemento3']; Aprender4Page({Key? key}) : super(key: key); @override Widget build(BuildContext context) { print('Dibuja el Scaffold'); return Scaffold( appBar: AppBar( title: Text('Aprender3 Statefulwidget'), ), body: Center( child: WidgetLista(listaElementosConstante: _listaElementos,) ) ); } } class WidgetLista extends StatefulWidget { // Un statefulwidget también es inmutable. Sólo admite variables en la clase State final List<String> listaElementosConstante; // A paser que la lista es final, se puede modificar. const WidgetLista({Key? key, required this.listaElementosConstante}) : super(key: key); @override WidgetListaState createState() => WidgetListaState(); } class WidgetListaState extends State<WidgetLista> { String _elemSeleccionado=''; @override Widget build(BuildContext context) { print('Dibuja la lista:${widget.listaElementosConstante}'); return ListView( children: widget.listaElementosConstante.map((e) => _elementoLista(e)).toList(), ); } Widget _elementoLista(String elemento){ return Ink( color: _elemSeleccionado == elemento ? Colors.red : Colors.indigo, child: ListTile( title: Text('$elemento'), onTap: () { setState(() { _elemSeleccionado = elemento; }); }, onLongPress: (){ setState(() { widget.listaElementosConstante.remove(elemento); _elemSeleccionado = ''; }); }, ), ); } }
Ejemplo contador
- Podemos analizar ahora el ejemplo 'Hola Mundo' que aparece cuando se instala Flutter y que podemos visualizar en la página dartpad.dev
Ejemplo de varios StatefulWidget
- Creamos una nueva página en la carpeta pages/stateful/ de nombre varios_stateful.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).
- En este ejemplo podemos ver como disponemos de 3 Containers, y al pulsarlos, la variable local a dichos contenedores se incrementa.
- Dentro del child del Scaffold retornamos un widget Row, donde situaremos los 3 contenedores.
- Para controlar el evento de pulsar sobre un contenedor hago uso de la clase InkWell que permite tener un efecto al pulsar sobre el contenedor.
import 'package:flutter/material.dart'; class VariosStateFull extends StatelessWidget { const VariosStateFull({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Varios StatefulWidget'), ), body: Center( child: _variosContenedores(), )); } Widget _variosContenedores(){ return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Contenedor(color: Colors.green), Contenedor(color: Colors.red), Contenedor(color: Colors.yellow), ], ); } } class Contenedor extends StatefulWidget { final Color color; const Contenedor({Key? key, required this.color}) : super(key: key); @override _ContenedorState createState() => _ContenedorState(); } class _ContenedorState extends State<Contenedor> { int _contador=0; _ContenedorState(){ } @override Widget build(BuildContext context) { return InkWell( child: Container( child: Text('$_contador'), color: widget.color, width: 50, height: 50, margin: EdgeInsets.all(12.0), ), onTap: (){ setState(() { _contador++; });}, ); } }
- Nota: Veremos más adelante en esta Wiki como pasar datos entre los diferentes Stateful Widget´s.
Ejercicios propuestos
- Crea una variable global que guarde en un lista los valores de los tres contenedores.
- Haz que cada contenedor esté identificado por un id (0,1,2) que se corresponda con la posición de la lista a la que tendrá que acceder a modificar su valor.
- En base al id (0,1,2) haz que al presionar el contenedor, aumente el valor del elemento de la lista y que se visualice en el texto que hay en el interior.
- ¿ Cómo harías para que en el caso de que se actualizara en contador de todos los contenedores al pulsar en cualquiera de ellos ?
Solución ejercicio propuesto:
import 'package:flutter/material.dart'; var datos = <int>[1,2,3]; class VariosStateFull extends StatelessWidget { const VariosStateFull({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Varios StatefulWidget'), ), body: Center( child: _variosContenedores(), )); } Widget _variosContenedores(){ return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Contenedor(color: Colors.green,id: 0), Contenedor(color: Colors.red, id: 1), Contenedor(color: Colors.yellow,id: 2), ], ); } } class Contenedor extends StatefulWidget { final Color color; final int id; const Contenedor({Key? key, required this.color,required this.id}) : super(key: key); @override _ContenedorState createState() => _ContenedorState(); } class _ContenedorState extends State<Contenedor> { int _contador=0; _ContenedorState(){ } @override Widget build(BuildContext context) { return InkWell( child: Container( child: Text('${datos[widget.id]}'), color: widget.color, width: 50, height: 50, margin: EdgeInsets.all(12.0), ), onTap: (){ setState(() { datos[widget.id]++; });}, ); } }
- Pregunta: ¿ Cómo harías para que en el caso de que se actualizara en contador de todos los contenedores al pulsar en cualquiera de ellos ?
- Respuesta: Habría que crear un único StatefulWidget que 'envuelva' a los 3 contenedores.
Enlace a la página principal de la UD4
Enlace a la página principal del curso
-- Ángel D. Fernández González -- (2021).