Agrega interactividad a tu app de Flutter
Cómo implementar un stateful widget que responde a toques.
¿Cómo modificas tu app para hacer que reaccione a las entradas del usuario? En este tutorial, agregarás interactividad a una app que contiene solo widgets no interactivos. Específicamente, modificarás un icono para hacerlo tocable creando un stateful widget personalizado que gestiona dos stateless widgets.
El tutorial para construir layouts te mostró cómo crear el layout para la siguiente captura de pantalla.
La app del tutorial de layout
Cuando la app se inicia por primera vez, la estrella es de color rojo sólido, lo que indica que este lago ha sido marcado como favorito previamente. El número junto a la estrella indica que 41 personas han marcado como favorito este lago. Después de completar este tutorial, tocar la estrella elimina su estado de favorito, reemplazando la estrella sólida con un contorno y disminuyendo el contador. Tocar de nuevo marca como favorito el lago, dibujando una estrella sólida e incrementando el contador.
Para lograr esto, crearás un único widget personalizado que incluye tanto la estrella como el contador, que son widgets en sí mismos. Tocar la estrella cambia el State de ambos widgets, por lo que el mismo widget debería gestionar ambos.
Puedes ir directamente a tocar el código en Paso 2: Hereda de StatefulWidget. Si quieres probar diferentes formas de gestionar el State, salta a Gestionando el State.
Widgets Stateful y stateless
#Un widget es o bien stateful o bien stateless. Si un widget puede cambiar—cuando un usuario interactúa con él, por ejemplo—es stateful.
Un widget stateless nunca cambia.
Icon,
IconButton, y
Text son
ejemplos de widgets stateless. Los widgets stateless
heredan de StatelessWidget.
Un widget stateful es dinámico: por ejemplo,
puede cambiar su apariencia en respuesta a eventos
desencadenados por interacciones del usuario o cuando recibe datos.
Checkbox,
Radio, Slider,
InkWell,
Form, y TextField
son ejemplos de widgets stateful. Los widgets stateful
heredan de StatefulWidget.
El State de un widget se almacena en un objeto State,
separando el State del widget de su apariencia.
El State consiste en valores que pueden cambiar, como el valor
actual de un slider o si un checkbox está marcado.
Cuando el State del widget cambia,
el objeto State llama a setState(),
diciéndole al framework que redibuje el widget.
Creando un stateful widget
#En esta sección, crearás un stateful widget personalizado.
Reemplazarás dos widgets stateless—la estrella roja sólida
y el contador numérico junto a ella—con un solo
stateful widget personalizado que gestiona una fila con dos
widgets hijos: un IconButton y un Text.
Implementar un stateful widget personalizado requiere crear dos clases:
- Una subclase de
StatefulWidgetque define el widget. - Una subclase de
Stateque contiene el State para ese widget y define el métodobuild()del widget.
Esta sección te muestra cómo construir un stateful widget,
llamado FavoriteWidget, para la app de lagos.
Después de configurar, tu primer paso es elegir cómo se gestiona el State
para FavoriteWidget.
Paso 0: Prepárate
#Si ya construiste la app en el tutorial para construir layouts, salta a la siguiente sección.
- Asegúrate de haber configurado tu entorno.
- Crea una nueva aplicación de Flutter.
- Reemplaza el archivo
lib/main.dartconmain.dart. - Reemplaza el archivo
pubspec.yamlconpubspec.yaml. - Crea un directorio
imagesen tu proyecto, y agregalake.jpg.
Una vez que tengas un dispositivo conectado y habilitado, o hayas iniciado el simulador de iOS (parte de la instalación de Flutter) o el emulador de Android (parte de la instalación de Android Studio), ¡estarás listo para comenzar!
Paso 1: Decide qué objeto gestiona el State del widget
#El State de un widget se puede gestionar de varias maneras,
pero en nuestro ejemplo el propio widget,
FavoriteWidget, gestionará su propio State.
En este ejemplo, alternar la estrella es una acción
aislada que no afecta al widget padre ni al resto de
la UI, por lo que el widget puede manejar su State internamente.
Aprende más sobre la separación de widget y State, y cómo se podría gestionar el State, en Gestionando el State.
Paso 2: Hereda de StatefulWidget
#La clase FavoriteWidget gestiona su propio State,
por lo que sobrescribe createState() para crear un objeto State.
El framework llama a createState()
cuando quiere construir el widget.
En este ejemplo, createState() devuelve una
instancia de _FavoriteWidgetState,
que implementarás en el siguiente paso.
class FavoriteWidget extends StatefulWidget {
const FavoriteWidget({super.key});
@override
State<FavoriteWidget> createState() => _FavoriteWidgetState();
}
Paso 3: Hereda de State
#La clase _FavoriteWidgetState almacena los datos mutables
que pueden cambiar a lo largo de la vida útil del widget.
Cuando la app se inicia por primera vez, la UI muestra una estrella
roja sólida, indicando que el lago tiene estado "favorito",
junto con 41 me gusta. Estos valores se almacenan en los
campos _isFavorited y _favoriteCount:
class _FavoriteWidgetState extends State<FavoriteWidget> {
bool _isFavorited = true;
int _favoriteCount = 41;
La clase también define un método build(),
que crea una fila que contiene un IconButton rojo,
y Text. Usas IconButton
(en lugar de Icon)
porque tiene una propiedad onPressed que define
la función callback (_toggleFavorite) para manejar un toque.
Definirás la función callback a continuación.
class _FavoriteWidgetState extends State<FavoriteWidget> {
// ···
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(0),
child: IconButton(
padding: const EdgeInsets.all(0),
alignment: Alignment.center,
icon: (_isFavorited
? const Icon(Icons.star)
: const Icon(Icons.star_border)),
color: Colors.red[500],
onPressed: _toggleFavorite,
),
),
SizedBox(width: 18, child: SizedBox(child: Text('$_favoriteCount'))),
],
);
}
// ···
}
El método _toggleFavorite(), que se llama cuando se presiona el
IconButton, llama a setState().
Llamar a setState() es crítico, porque esto
le dice al framework que el State del widget ha
cambiado y que el widget debe ser redibujado.
El argumento de la función para setState() alterna la
UI entre estos dos estados:
- Un icono de
estrellay el número 41 - Un icono
star_bordery el número 40
void _toggleFavorite() {
setState(() {
if (_isFavorited) {
_favoriteCount -= 1;
_isFavorited = false;
} else {
_favoriteCount += 1;
_isFavorited = true;
}
});
}
Paso 4: Conecta el stateful widget en el árbol de widgets
#Agrega tu stateful widget personalizado al árbol de widgets en
el método build() de la app. Primero, localiza el código que
crea el Icon y el Text, y elimínalo.
En la misma ubicación, crea el stateful widget:
child: Row(
children: [
// ...
Icon(
Icons.star,
color: Colors.red[500],
),
const Text('41'),
const FavoriteWidget(),
],
),
¡Eso es todo! Cuando realices un Hot Reload de la app, el icono de estrella debería responder ahora a los toques.
¿Problemas?
#Si no consigues ejecutar tu código, busca en tu IDE posibles errores. Depurar apps de Flutter podría ayudar. Si aún no encuentras el problema, compara tu código con el ejemplo interactivo de lagos en GitHub.
Si aún tienes preguntas, consulta cualquiera de los canales de la comunidad de desarrolladores.
El resto de esta página cubre varias formas en que se puede gestionar el State de un widget, y enumera otros widgets interactivos disponibles.
Gestionar el state
#¿Quién gestiona el State del stateful widget? ¿El widget mismo? ¿El widget padre? ¿Ambos? ¿Otro objeto? La respuesta es... depende. Hay varias formas válidas de hacer que tu widget sea interactivo. Tú, como diseñador del widget, tomas la decisión en función de cómo esperas que se use tu widget. Aquí están las formas más comunes de gestionar el State:
- El widget gestiona su propio state
- El padre gestiona el State del widget
- Un enfoque híbrido (mix-and-match)
¿Cómo decides qué enfoque utilizar? Los siguientes principios deberían ayudarte a decidir:
-
Si el State en cuestión son datos del usuario, por ejemplo el modo marcado o desmarcado de un checkbox, o la posición de un slider, entonces es mejor que el State sea gestionado por el widget padre.
-
Si el State en cuestión es estético, por ejemplo una animación, entonces es mejor que el State sea gestionado por el propio widget.
En caso de duda, comienza gestionando el State en el widget padre.
Daremos ejemplos de las diferentes formas de gestionar el State
creando tres ejemplos sencillos: TapboxA, TapboxB,
y TapboxC. Todos los ejemplos funcionan de manera similar—cada uno
crea un contenedor que, al tocarlo, alterna entre una
caja verde o gris. El booleano _active determina el
color: verde para activo o gris para inactivo.
Estos ejemplos usan GestureDetector
para capturar la actividad
en el Container.
El widget gestiona su propio state
#A veces tiene más sentido que el widget
gestione su State internamente. Por ejemplo,
ListView
se desplaza automáticamente cuando su
contenido supera la caja de renderizado. La mayoría de los desarrolladores
que usan ListView no quieren gestionar el comportamiento de desplazamiento de ListView,
por lo que ListView en sí gestiona su offset de desplazamiento.
La clase _TapboxAState:
- Gestiona el State para
TapboxA. - Define el booleano
_activeque determina el color actual de la caja. - Define la función
_handleTap(), que actualiza_activecuando se toca la caja y llama a la funciónsetState()para actualizar la UI. - Implementa todo el comportamiento interactivo para el widget.
import 'package:flutter/material.dart';
// TapboxA manages its own state.
//------------------------- TapboxA ----------------------------------
class TapboxA extends StatefulWidget {
const TapboxA({super.key});
@override
State<TapboxA> createState() => _TapboxAState();
}
class _TapboxAState extends State<TapboxA> {
bool _active = false;
void _handleTap() {
setState(() {
_active = !_active;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: _active ? Colors.lightGreen[700] : Colors.grey[600],
),
child: Center(
child: Text(
_active ? 'Active' : 'Inactive',
style: const TextStyle(fontSize: 32, color: Colors.white),
),
),
),
);
}
}
//------------------------- MyApp ----------------------------------
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Flutter Demo')),
body: const Center(child: TapboxA()),
),
);
}
}
El widget padre gestiona el state del widget
#A menudo tiene más sentido que el widget padre
gestione el State y le diga a su widget hijo cuándo actualizarse.
Por ejemplo, IconButton
te permite tratar
un icono como un botón tocable. IconButton es un
stateless widget porque decidimos que el widget padre
necesita saber si el botón ha sido tocado,
para poder tomar las medidas adecuadas.
En el siguiente ejemplo, TapboxB exporta su State a su padre a través de un callback. Debido a que TapboxB no gestiona ningún State, hereda de StatelessWidget.
La clase ParentWidgetState:
- Gestiona el State
_activepara TapboxB. - Implementa
_handleTapboxChanged(), el método llamado cuando se toca la caja. - Cuando el State cambia, llama a
setState()para actualizar la UI.
La clase TapboxB:
- Extiende StatelessWidget porque todo el State es manejado por su padre.
- Cuando se detecta un toque, notifica al padre.
import 'package:flutter/material.dart';
// ParentWidget manages the state for TapboxB.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return SizedBox(
child: TapboxB(active: _active, onChanged: _handleTapboxChanged),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
const TapboxB({super.key, this.active = false, required this.onChanged});
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
child: Center(
child: Text(
active ? 'Active' : 'Inactive',
style: const TextStyle(fontSize: 32, color: Colors.white),
),
),
),
);
}
}
Un enfoque híbrido (mix-and-match)
#Para algunos widgets, un enfoque de combinación tiene más sentido. En este escenario, el stateful widget gestiona parte del State, y el widget padre gestiona otros aspectos del State.
En el ejemplo de TapboxC, al presionar,
aparece un borde verde oscuro alrededor de la caja. Al soltar,
el borde desaparece y el color de la caja cambia. TapboxC
exporta su State _active a su padre pero gestiona su State
_highlight internamente. Este ejemplo tiene dos objetos State,
_ParentWidgetState y _TapboxCState.
El objeto _ParentWidgetState:
- Gestiona el State
_active. - Implementa
_handleTapboxChanged(), el método llamado cuando se toca la caja. - Llama a
setState()para actualizar la UI cuando ocurre un toque y el State_activecambia.
El objeto _TapboxCState:
- Gestiona el State
_highlight. - El
GestureDetectorescucha todos los eventos de toque. A medida que el usuario presiona, agrega el resaltado (implementado como un borde verde oscuro). A medida que el usuario suelta el toque, elimina el resaltado. - Llama a
setState()para actualizar la UI al presionar, soltar o cancelar el toque, y el State_highlightcambia. - En un evento de toque, pasa ese cambio de State al widget padre para que tome
la acción adecuada utilizando la propiedad
widget.
import 'package:flutter/material.dart';
//---------------------------- ParentWidget ----------------------------
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return SizedBox(
child: TapboxC(active: _active, onChanged: _handleTapboxChanged),
);
}
}
//----------------------------- TapboxC ------------------------------
class TapboxC extends StatefulWidget {
const TapboxC({super.key, this.active = false, required this.onChanged});
final bool active;
final ValueChanged<bool> onChanged;
@override
State<TapboxC> createState() => _TapboxCState();
}
class _TapboxCState extends State<TapboxC> {
bool _highlight = false;
void _handleTapDown(TapDownDetails details) {
setState(() {
_highlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
setState(() {
_highlight = false;
});
}
void _handleTapCancel() {
setState(() {
_highlight = false;
});
}
void _handleTap() {
widget.onChanged(!widget.active);
}
@override
Widget build(BuildContext context) {
// This example adds a green border on tap down.
// On tap up, the square changes to the opposite state.
return GestureDetector(
onTapDown: _handleTapDown, // Handle the tap events in the order that
onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
onTap: _handleTap,
onTapCancel: _handleTapCancel,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: _highlight
? Border.all(color: Colors.teal[700]!, width: 10)
: null,
),
child: Center(
child: Text(
widget.active ? 'Active' : 'Inactive',
style: const TextStyle(fontSize: 32, color: Colors.white),
),
),
),
);
}
}
Una implementación alternativa podría haber exportado el State de resaltado al padre mientras mantenía el State activo interno, pero si le pidieras a alguien que use esa caja de toques, probablemente se quejaría de que no tiene mucho sentido. Al desarrollador le importa si la caja está activa. Al desarrollador probablemente no le importa cómo se gestiona el resaltado, y prefiere que la caja de toques maneje esos detalles.
Otros widgets interactivos
#Flutter ofrece una variedad de botones y widgets interactivos similares. La mayoría de estos widgets implementan las guías de Material Design, que definen un conjunto de componentes con una UI con opinión propia.
Si lo prefieres, puedes usar GestureDetector
para incorporar
interactividad en cualquier widget personalizado.
Puedes encontrar ejemplos de GestureDetector en
Gestionando el State. Aprende más sobre el GestureDetector
en Manejar toques, una receta en el libro de recetas de Flutter.
Cuando necesitas interactividad, lo más fácil es usar uno de los widgets prefabricados. Aquí hay una lista parcial:
Widgets estándar
#Material Components
#Checkbox-
DropdownButton TextButton-
FloatingActionButton IconButtonRadio-
ElevatedButton SliderSwitchTextField
Recursos
#Los siguientes recursos pueden ser de ayuda al agregar interactividad a tu app.
Gestos, una sección en el libro de recetas de Flutter.
- Manejo de gestos
Cómo crear un botón y hacer que responda a las entradas.
- Gestos en Flutter
Una descripción del mecanismo de gestos de Flutter.
- Documentación de la API de Flutter
Documentación de referencia para todas las librerías de Flutter.
- Aplicación Wonderous aplicación en ejecución , repo
App de demostración de Flutter con un diseño personalizado e interacciones atractivas.
- Diseño por capas de Flutter (video)
-
Este video incluye información sobre widgets stateful y stateless. Presentado por el ingeniero de Google, Ian Hickson.
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.