Entrada del usuario
Acepta entrada del usuario con botones y campos de texto.
Aprende a construir entradas de texto, gestionar texto con controladores y manejar las acciones del usuario con botones.
Qué lograrás
Pasos
1
Introducción
Introducción
La aplicación mostrará los intentos del usuario en los widgets Tile,
pero necesita una forma para que el usuario ingrese esos intentos.
En esta lección, construirás esa funcionalidad con dos widgets de interacción:
TextField
y IconButton.
2
Implementa funciones callback
Implementa funciones callback
Para permitir que los usuarios escriban sus intentos,
crearás un widget dedicado llamado GuessInput.
Primero, crea la estructura básica para tu widget GuessInput que
requiera una función callback como argumento.
Nombra la función callback onSubmitGuess.
Agrega el siguiente código a tu archivo main.dart.
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
@override
Widget build(BuildContext context) {
// You'll build the UI in the next steps.
return Container(); // Placeholder
}
}
La línea final void Function(String) onSubmitGuess;
declares a final miembro de la clase llamado onSubmitGuess
que tiene el tipo void Function(String).
Esta función toma un único argumento String (el intento del usuario) y
no devuelve ningún valor (indicado por void).
Esta callback nos indica que la lógica que realmente maneja el intento del usuario se escribirá en otra parte. Es una buena práctica que los widgets interactivos usen funciones callback para mantener el widget que maneja las interacciones reutilizable y desacoplado de cualquier funcionalidad específica.
Para el final de esta lección, la función onSubmitGuess recibida
se llamará cuando un usuario ingrese un intento.
Primero, necesitarás construir las partes visuales de este widget.
Así es como se verá el widget.
3
El widget TextField
El widget TextField
Dado que el campo de texto y el botón se muestran uno al lado del otro,
créalos como un widget Row.
Reemplaza el marcador de posición Container en tu método build
con
un Row que contenga un Expanded TextField:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
),
),
),
],
);
}
}
Ya has visto algunos de estos widgets en lecciones anteriores:
Row y Padding. Sin embargo, el widget Expanded
es nuevo.
Cuando un hijo de un Row (o Column) se envuelve en
Expanded,
se le indica a ese hijo que llene todo el espacio disponible a lo largo del eje principal
(horizontal para Row, vertical para Column) que
no haya sido ocupado por otros hijos.
Esto hace que el TextField se estire para ocupar todo el espacio excepto
el que toman otros widgets en la fila.
El widget TextField también es nuevo en esta lección y es la estrella del espectáculo.
Este es el widget básico de Flutter para la entrada de texto.
Hasta ahora, TextField tiene la siguiente configuración.
- Está decorado con un borde redondeado.
Ten en cuenta que la configuración de la decoración es
muy similar a cómo se decoran un
Containery las cajas. - Su propiedad
maxLengthestá configurada en 5 porque el juego solo permite intentos de palabras de 5 letras.
4
Manejar texto con TextEditingController
Manejar texto con TextEditingController
A continuación, necesitas una forma de gestionar el texto que
el usuario escribe en el campo de entrada.
Para esto, usa un TextEditingController.
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
// NEW
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
),
),
),
//
],
);
}
}
Se utiliza un TextEditingController para
leer, limpiar y modificar el texto en un TextField.
Para usarlo, pásalo al TextField.
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController, // NEW
),
),
),
],
);
}
}
Ahora, cuando un usuario ingresa texto, puedes
capturarlo con el _textEditingController, pero
necesitarás saber cuándo capturarlo.
La forma más sencilla de reaccionar a la entrada es
usando el argumento TextField.onSubmitted.
Este argumento acepta una función callback, y esta callback se activa cada vez que
el usuario presiona la tecla "Enter" en el teclado mientras el campo de texto tiene el foco.
Por ahora, asegúrate de que esto funcione
agregando la siguiente función callback a TextField.onSubmitted:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
onSubmitted: (input) {
// NEW
print(_textEditingController.text); // Temporary
},
),
),
),
],
);
}
}
En este caso,
podrías imprimir directamente el input pasado a la callback onSubmitted,
pero una mejor experiencia de usuario limpia el texto después de cada intento:
Necesitas un TextEditingController para hacer eso. Actualiza el código de la siguiente manera:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
onSubmitted: (_) {
// UPDATED
print(_textEditingController.text); // Temporary
_textEditingController.clear(); // NEW
},
),
),
),
],
);
}
}
5
Obtener el foco de entrada
Obtener el foco de entrada
A menudo, quieres que una entrada o widget específico
obtenga el foco automáticamente sin que el usuario tenga que realizar ninguna acción.
En esta aplicación, por ejemplo, lo único que el usuario puede hacer es ingresar un intento,
por lo que el TextField debería enfocarse automáticamente cuando se inicia la aplicación.
Y después de que el usuario ingresa un intento, el foco debería permanecer
en el TextField para que pueda ingresar su siguiente intento.
Para resolver el primer problema de foco,
configura la propiedad autofocus en el TextField.
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
autofocus: true, // NEW
onSubmitted: (input) {
print(input); // Temporary
_textEditingController.clear();
},
),
),
),
],
);
}
}
El segundo problema requiere que
uses un FocusNode
para gestionar el foco del teclado.
Puedes usar FocusNode para solicitar que un TextField
obtenga el foco
(haciendo que el teclado aparezca en dispositivos móviles),
o para saber cuándo un campo tiene el foco.
Primero, crea un FocusNode en la clase GuessInput:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode(); // NEW
@override
Widget build(BuildContext context) {
// ...
return Container();
}
}
Luego, usa el FocusNode para solicitar el foco cada vez que
se envíe el TextField después de limpiar el controlador:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
autofocus: true,
focusNode: _focusNode, // NEW
onSubmitted: (input) {
print(input); // Temporary
_textEditingController.clear();
_focusNode.requestFocus(); // NEW
},
),
),
),
],
);
}
}
Ahora, cuando presiones Enter después de ingresar texto, podrás continuar escribiendo.
6
Usar la entrada
Usar la entrada
Finalmente, necesitas manejar el texto que ingresa el usuario.
Recuerda que el constructor de GuessInput requiere una
callback llamada onSubmitGuess.
En GuessInput, necesitas usar esa callback.
Reemplaza la instrucción print con una llamada a esa función.
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
autofocus: true,
focusNode: _focusNode,
onSubmitted: (input) {
onSubmitGuess(_textEditingController.text.trim());
_textEditingController.clear();
_focusNode.requestFocus();
},
),
),
),
],
);
}
}
El resto de la funcionalidad se maneja en el widget padre, GamePage.
En el método build de esa clase,
debajo de los widgets Row en los hijos del widget Column,
agrega el widget GuessInput:
class GamePage extends StatelessWidget {
GamePage({super.key});
final Game _game = Game();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
spacing: 5.0,
children: [
for (final guess in _game.guesses)
Row(
spacing: 5.0,
children: [
for (final letter in guess) Tile(letter.char, letter.type),
],
),
GuessInput(
onSubmitGuess: (guess) {
// TODO, handle guess
print(guess); // Temporary
},
),
],
),
);
}
}
Por el momento, esto solo imprime el intento para
demostrar que está conectado correctamente.
Enviar el intento requiere usar la funcionalidad de un StatefulWidget,
lo cual harás en la siguiente lección.
7
Botones
Botones
Para mejorar la UX en dispositivos móviles y reflejar prácticas de UI bien conocidas, también debería haber un botón que pueda enviar el intento.
Hay muchos widgets de botones integrados en Flutter, como TextButton,
ElevatedButton, y el botón que usarás ahora:
IconButton.
Todos estos botones (y muchos otros widgets de interacción) requieren dos
argumentos (además de sus argumentos opcionales):
- Una función callback pasada a
onPressed. - Un widget que compone el contenido del botón (a menudo
Texto unIcon).
Agrega un botón de icono a la lista de hijos del widget row en el widget GuessInput,
y dale un widget Icon
para mostrar.
El widget Icon requiere configuración; en este caso,
la propiedad padding establece en cero el padding entre el
borde del botón y el icono que envuelve.
Esto elimina el padding por defecto y hace que el botón sea más pequeño.
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Container()),
IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.arrow_circle_up),
onPressed: null,
),
],
);
}
}
La callback IconButton.onPressed debería resultarte familiar:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Container()),
IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.arrow_circle_up),
onPressed: () {
onSubmitGuess(_textEditingController.text.trim());
_textEditingController.clear();
_focusNode.requestFocus();
},
),
],
);
}
}
Este método hace lo mismo que la callback onSubmitted en el TextField.
8
Revisión
Revisión
Qué lograste
Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.Construiste un widget de entrada de texto con TextField
Creaste un widget GuessInput con un TextField para la entrada de texto. Lo configuraste con un borde redondeado, límite de caracteres y usaste
Expanded para hacer que llene el espacio disponible en la fila.
Gestionaste el texto con TextEditingController
TextEditingController te permite leer y modificar el contenido del campo de texto. Lo usaste para capturar la entrada del usuario con
.text y limpiar el campo después del envío con .clear().
Controlaste el foco de entrada para una UX pulida
Usaste autofocus para enfocar el campo de texto al iniciarse y FocusNode con requestFocus()
para mantener el foco después de cada intento. Estos detalles hacen que tu aplicación se sienta responsiva y bien construida.
Manejaste las acciones del usuario con callbacks y botones
Para responder a la entrada del usuario, especificaste funciones callback como onSubmitted y onPressed. Pasar funciones callback como argumentos de constructor mantiene tus widgets reutilizables y desacoplados de una lógica específica.
9
Ponte a prueba
Ponte a prueba
Cuestionario sobre Entrada del Usuario
1 / 2Llamar al método TextField.getText().
No exactamente.
TextField no tiene un método getText; usa TextEditingController en su lugar.
Escuchar la callback onChanged y almacenar el valor en una variable.
No exactamente.
Aunque onChanged funciona para leer, limpiar requiere un TextEditingController.
Acceder directamente a la propiedad text de TextField.
No exactamente.
TextField no expone una propiedad text; necesitas un controlador.
Usar el TextEditingController adjunto al TextField.
¡Así es!
TextEditingController proporciona la propiedad text para leer el valor y el método clear() para restablecerlo.
Llamar directamente a
TextField.focus().No exactamente.
TextField no tiene un método focus; usas un FocusNode.
Envolver el TextField en un GestureDetector y hacer tap programáticamente.
No exactamente.
No es así como se gestiona el foco; FocusNode es el enfoque adecuado.
Establecer la propiedad
autofocusen true en tiempo de ejecución.No exactamente.
La propiedad 'autofocus' solo funciona en la construcción inicial, no para mover el foco más tarde.
Usar un FocusNode y llamar a
requestFocus()en él.¡Así es!
Un FocusNode te da control sobre el foco, y llamar a
requestFocus()mueve el foco a su widget asociado.
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.