Construir interfaces de usuario con Flutter
Introducción al desarrollo de interfaces de usuario en Flutter.
Los widgets de Flutter se construyen utilizando un framework moderno que se inspira en React. La idea central es que construyes tu interfaz de usuario a partir de widgets. Los widgets describen cómo debería lucir su vista dada su configuración y State actuales. Cuando el State de un widget cambia, el widget reconstruye su descripción, la cual el framework compara con la descripción anterior para determinar los cambios mínimos necesarios en el árbol de renderizado subyacente para pasar de un estado al siguiente.
Hola mundo
#La app mínima de Flutter simplemente llama a la función runApp()
con un Widget:
La función runApp() toma el Widget
dado y lo convierte en la raíz del árbol de widgets.
En este ejemplo, el árbol de widgets consta de dos widgets,
el widget Center
y su hijo, el widget Text
.
El framework obliga al widget raíz a cubrir la pantalla,
lo que significa que el texto "Hola, mundo" termina centrado en la pantalla.
La dirección del texto debe especificarse en este caso;
cuando se utiliza el widget MaterialApp,
esto se soluciona automáticamente, como se demuestra más adelante.
Al escribir una app, comúnmente crearás nuevos widgets que
sean subclases de StatelessWidget
o StatefulWidget,
dependiendo de si tu widget gestiona algún State.
El trabajo principal de un widget es implementar una función build()
,
que describe el widget en términos de otros widgets de más bajo nivel.
El framework construye esos widgets a su vez hasta que el proceso
desemboca en widgets que representan el RenderObject subyacente,
el cual calcula y describe la geometría del widget.
Widgets básicos
#Flutter viene con una suite de potentes widgets básicos, de los cuales los siguientes son comúnmente utilizados:
Text-
El widget
Textte permite crear un fragmento de texto con estilo dentro de tu aplicación. -
Row,Column -
Estos widgets flexibles te permiten crear layouts flexibles en tanto en dirección horizontal (
Row) como vertical (Column). El diseño de estos objetos se basa en el modelo de layout flexbox de la web. Stack-
En lugar de estar orientado linealmente (ya sea horizontal o verticalmente), un widget
Stackte permite colocar widgets uno encima de otro en orden de pintado. Luego puedes usar el widgetPositioneden los hijos de unStackpara posicionarlos en relación con el borde superior, derecho, inferior o izquierdo del stack. Los Stacks se basan en el modelo de layout de posicionamiento absoluto de la web. Container-
El widget
Containerte permite crear un elemento visual rectangular. Un contenedor se puede decorar con unBoxDecoration, como un fondo, un borde o una sombra. UnContainertambién puede tener márgenes, padding y restricciones aplicadas a su tamaño. Además, unContainerse puede transformar en el espacio tridimensional mediante una matriz.
A continuación se muestran algunos widgets simples que combinan estos y otros widgets:
Asegúrate de tener una entrada uses-material-design: true en la sección flutter
de tu archivo pubspec.yaml. Te permite usar el conjunto predefinido
de iconos de Material. Por lo general, es una buena idea incluir esta línea
si estás utilizando la librería Material.
name: my_app
flutter:
uses-material-design: true
Muchos widgets de Material Design necesitan estar dentro de un MaterialApp
para mostrarse correctamente, con el fin de heredar los datos del tema.
Por lo tanto, ejecuta la aplicación con un MaterialApp.
El widget MyAppBar crea un Container
con una altura de 56
píxeles independientes de la pantalla con un padding interno de 8 píxeles,
tanto a la izquierda como a la derecha. Dentro del contenedor,
MyAppBar utiliza un layout Row
para organizar a sus hijos.
El hijo central, el widget title, está marcado como Expanded,
lo que significa que se expande para llenar cualquier espacio disponible restante
que no haya sido consumido por los otros hijos.
Puedes tener múltiples hijos Expanded y determinar la
proporción en la que consumen el espacio disponible utilizando el
argumento flex
de Expanded.
El widget MyScaffold organiza a sus hijos en una columna vertical.
En la parte superior de la columna coloca una instancia de MyAppBar,
pasando a la barra de la app un widget Text
para usar como su título.
Pasar widgets como argumentos a otros widgets es una técnica potente
que te permite crear widgets genéricos que se pueden reutilizar de muchas
maneras. Finalmente, MyScaffold usa un
Expanded
para llenar el espacio restante con su cuerpo,
que consiste en un mensaje centrado.
Para más información, consulta Layouts.
Uso de componentes Material
#Flutter proporciona una serie de widgets que te ayudan a construir apps
que siguen Material Design. Una app Material comienza con el
widget MaterialApp
, que construye una serie de widgets útiles
en la raíz de tu app, incluido un Navigator,
que gestiona una pila de widgets identificados por cadenas de texto,
también conocidas como "rutas". El Navigator te permite transicionar suavemente
entre pantallas de tu aplicación. El uso del widget MaterialApp
es totalmente opcional pero es una buena práctica.
Ahora que el código ha cambiado de MyAppBar y MyScaffold a los
widgets AppBar
y Scaffold
, y desde material.dart,
la app empieza a verse un poco más Material.
Por ejemplo, la barra de la app tiene una sombra y el texto del título hereda el
estilo correcto automáticamente. También se añade un botón de acción flotante (floating action button).
Observa que los widgets se pasan como argumentos a otros widgets.
El widget Scaffold
toma una serie de widgets diferentes como
argumentos con nombre, cada uno de los cuales se coloca en el layout de Scaffold
en el lugar adecuado. Del mismo modo, el widget
AppBar
te permite pasar widgets para el widget
leading
, y para las actions
del widget title
.
Este patrón se repite en todo el framework y es algo que
podrías considerar al diseñar tus propios widgets.
Para más información, consulta Widgets de Componentes Material.
Manejo de gestos
#La mayoría de las aplicaciones incluyen alguna forma de interacción del usuario con el sistema. El primer paso para construir una aplicación interactiva es detectar los gestos de entrada. Mira cómo funciona creando un botón simple:
El widget GestureDetector
no tiene una representación
visual, sino que detecta los gestos realizados por el
usuario. Cuando el usuario toca el Container,
el GestureDetector llama a su callback onTap()
, en este
caso imprimiendo un mensaje en la consola. Puedes usar
GestureDetector para detectar una variedad de gestos de entrada,
incluidos toques, arrastres y escalados.
Muchos widgets usan un GestureDetector
para proporcionar
callbacks opcionales para otros widgets. Por ejemplo, los widgets
IconButton,
ElevatedButton, y
FloatingActionButton
tienen callbacks onPressed()
que se activan cuando el usuario toca el widget.
Para más información, consulta Gestos en Flutter.
Cambiar widgets en respuesta a la entrada
#Hasta ahora, esta página solo ha utilizado widgets stateless.
Los widgets stateless reciben argumentos de su widget padre,
los cuales almacenan en variables miembro final
.
Cuando se le pide a un widget que ejecute build(), utiliza estos valores
almacenados para derivar nuevos argumentos para los widgets que crea.
Para construir experiencias más complejas (por ejemplo,
para reaccionar de formas más interesantes a la entrada del usuario), las aplicaciones
normalmente conservan algún estado. Flutter utiliza StatefulWidgets para capturar
esta idea. Los StatefulWidgets son widgets especiales que saben cómo generar
objetos State, que luego se utilizan para mantener el estado.
Considera este ejemplo básico, utilizando el ElevatedButton
mencionado anteriormente:
Te preguntarás por qué StatefulWidget y State son objetos separados.
En Flutter, estos dos tipos de objetos tienen ciclos de vida diferentes.
Los Widgets son objetos temporales, utilizados para construir una presentación de
la aplicación en su estado actual. Los objetos State, por otro
lado, son persistentes entre llamadas a
build(), lo que les permite recordar información.
El ejemplo anterior acepta la entrada del usuario y utiliza directamente
el resultado en su método build(). En aplicaciones más complejas,
diferentes partes de la jerarquía de widgets podrían ser
responsables de diferentes tareas; por ejemplo, un
widget podría presentar una interfaz de usuario compleja
con el objetivo de recopilar información específica,
como una fecha o ubicación, mientras que otro widget podría
usar esa información para cambiar la presentación general.
En Flutter, las notificaciones de cambio fluyen "hacia arriba" en la jerarquía
de widgets mediante callbacks, mientras que el estado actual fluye
"hacia abajo" hacia los widgets stateless que hacen la presentación.
El padre común que redirige este flujo es el State.
El siguiente ejemplo un poco más complejo muestra cómo
funciona esto en la práctica:
Observa la creación de dos nuevos widgets stateless,
separando claramente las responsabilidades de mostrar el contador
(CounterDisplay) e incrementar el contador (CounterIncrementor).
Aunque el resultado neto es el mismo que en el ejemplo anterior,
la separación de responsabilidades permite encapsular una mayor complejidad en los widgets individuales,
manteniendo la simplicidad en el padre.
Para obtener más información, consulta:
Integrando todo
#A continuación se presenta un ejemplo más completo que reúne
estos conceptos: Una hipotética aplicación de compras muestra varios
productos ofrecidos para la venta y mantiene un carrito de compras para las
compras previstas. Comienza definiendo la clase de presentación,
ShoppingListItem:
El widget ShoppingListItem sigue un patrón común
para widgets stateless. Almacena los valores que recibe
en su constructor en variables miembro final
,
que luego utiliza durante su función build()
.
Por ejemplo, el booleano inCart conmuta entre dos apariencias
visuales: una que utiliza el color primario del tema actual
y otra que utiliza gris.
Cuando el usuario toca el elemento de la lista, el widget no modifica
su valor inCart directamente. En su lugar, el widget llama a la
función onCartChanged que recibió de su widget padre.
Este patrón te permite almacenar el estado más arriba en la jerarquía
de widgets, lo que hace que el estado persista durante períodos de tiempo más largos.
En el caso extremo, el estado almacenado en el widget pasado a
runApp()
persiste durante toda la vida útil de la
aplicación.
Cuando el padre recibe la devolución de llamada onCartChanged,
el padre actualiza su estado interno, lo que activa
al padre para reconstruir y crear una nueva instancia
de ShoppingListItem con el nuevo valor de inCart.
Aunque el padre crea una nueva instancia de
ShoppingListItem cuando se reconstruye, esa operación es económica
porque el framework compara los widgets recién construidos con los previamente
construidos y aplica solo las diferencias al RenderObject subyacente.
Aquí hay un ejemplo de widget padre que almacena un estado mutable:
La clase ShoppingList extiende de StatefulWidget,
lo que significa que este widget almacena un estado mutable.
Cuando el widget ShoppingList se inserta por primera vez
en el árbol, el framework llama a la función createState()
para crear una nueva instancia de _ShoppingListState que se asociará con esa
ubicación en el árbol. (Observa que las subclases de
State
suelen nombrarse con guiones bajos iniciales para
indicar que son detalles de implementación privados).
Cuando el padre de este widget se reconstruye, el padre crea una nueva instancia
de ShoppingList, pero el framework reutiliza la instancia de _ShoppingListState
que ya está en el árbol en lugar de llamar a
createState nuevamente.
Para acceder a las propiedades del ShoppingList actual,
el _ShoppingListState puede usar su propiedad widget
.
Si el padre se reconstruye y crea un nuevo ShoppingList,
el _ShoppingListState se reconstruye con el nuevo valor de widget.
Si deseas ser notificado cuando la propiedad widget cambie,
sobrescribe la función didUpdateWidget()
, a la que se le pasa
un oldWidget para permitirte comparar el widget antiguo con
el widget actual.
Al manejar la devolución de llamada onCartChanged, el _ShoppingListState
muta su estado interno agregando o eliminando un producto de
_shoppingCart. Para señalar al framework que ha cambiado su estado
interno, envuelve esas llamadas en una llamada a setState()
.
Llamar a setState marca este widget como sucio (dirty) y programa su reconstrucción
la próxima vez que tu app necesite actualizar la pantalla.
Si olvidas llamar a setState al modificar el estado
interno de un widget, el framework no sabrá que tu widget está
sucio y podría no llamar a la función build() del widget,
lo que significa que la interfaz de usuario podría no actualizarse para reflejar
el estado cambiado. Al gestionar el estado de esta manera,
no necesitas escribir código separado para crear y
actualizar widgets hijos. En su lugar, simplemente implementas la función build,
que maneja ambas situaciones.
Responder a eventos del ciclo de vida del widget
#Después de llamar a createState()
en el StatefulWidget,
el framework inserta el nuevo objeto de estado en el árbol y
luego llama a initState()
en el objeto de estado.
Una subclase de State
puede sobrescribir initState para realizar un trabajo
que solo necesita ocurrir una vez. Por ejemplo, sobrescribe initState
para configurar animaciones o para suscribirte a servicios de la plataforma.
Las implementaciones de initState deben comenzar
llamando a super.initState.
Cuando un objeto de estado ya no es necesario,
el framework llama a dispose()
en el objeto de estado.
Sobrescribe la función dispose para realizar tareas de limpieza.
Por ejemplo, sobrescribe dispose para cancelar temporizadores o para
cancelar la suscripción a los servicios de la plataforma. Las implementaciones de
dispose típicamente terminan llamando a super.dispose.
Para obtener más información, consulta State.
Keys
#Usa keys para controlar qué widgets empareja el framework
con otros widgets cuando un widget se reconstruye. Por defecto, el
framework empareja widgets en la compilación actual y anterior
de acuerdo con su runtimeType
y el orden en que aparecen.
Con las keys, el framework requiere que los dos widgets tengan
la misma key
así como el mismo runtimeType.
Las keys son más útiles en widgets que construyen muchas instancias del
mismo tipo de widget. Por ejemplo, el widget ShoppingList,
que construye solo las instancias suficientes de ShoppingListItem para
llenar su región visible:
-
Sin keys, la primera entrada en la compilación actual siempre se sincronizaría con la primera entrada en la compilación anterior, incluso si, semánticamente, la primera entrada en la lista simplemente se desplazó fuera de la pantalla y ya no es visible en el viewport.
-
Al asignar a cada entrada de la lista una key "semántica", la lista infinita puede ser más eficiente porque el framework sincroniza entradas con keys semánticas coincidentes y, por lo tanto, apariciones visuales similares (o idénticas). Además, sincronizar las entradas semánticamente significa que el estado retenido en widgets hijos stateful permanece adjunto a la misma entrada semántica en lugar de a la entrada en la misma posición numérica en el viewport.
Para más información, consulta la API de Key
.
Global keys
#Usa global keys para identificar de manera única los widgets hijos. Las global keys deben ser globalmente únicas en toda la jerarquía de widgets, a diferencia de las local keys que solo necesitan ser únicas entre hermanos. Debido a que son globalmente únicas, se puede usar una global key para recuperar el estado asociado con un widget.
Para más información, consulta la API de GlobalKey
.
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.