Saltar al contenido principal

Crear widgets

Aprende sobre los widgets stateless y cómo construir los tuyos propios.

Aprende a crear widgets personalizados y a usar los widgets más comunes de la SDK como Container, Center y Text.

Qué lograrás

Crea un StatelessWidget personalizado
Haz que los widgets sean reutilizables con parámetros de constructor
Aplica estilos a los widgets usando Container y BoxDecoration

Pasos

1

Antes de comenzar

Esta aplicación se basa en un poco de lógica de juego que no está relacionada con la UI y, por lo tanto, queda fuera del alcance de este tutorial. Antes de continuar, debes agregar esta lógica a tu aplicación.

  1. Descarga el siguiente archivo Dart y guárdalo como lib/game.dart en el directorio de tu proyecto.

  2. Para permitir el acceso a los tipos definidos en la biblioteca game.dart, agrega una importación a ella desde tu archivo lib/main.dart:

    main.dart
    dart
    import 'package:flutter/material.dart';
    
    import 'game.dart';
    
2

Anatomía de un widget stateless

Un Widget es una clase Dart que extiende una de las clases de widgets de Flutter, en este caso StatelessWidget.

Abre tu archivo main.dart y agrega este código debajo de la clase MainApp, el cual define un nuevo widget llamado Tile.

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Constructor

#

La clase Tile tiene un constructor que define qué datos deben pasarse al widget para renderizarlo. En este caso, el constructor acepta dos parámetros:

  • Un String que representa la letra intentada de la casilla.
  • Un valor de enum HitType que representa el resultado del intento y se usa para determinar el color de la casilla. Por ejemplo, HitType.hit da como resultado una casilla verde.

Pasar datos a los constructores de los widgets es fundamental para hacer que los widgets sean reutilizables.

Método build

#

Finalmente, está el importantísimo método build, que debe definirse en cada widget y siempre devolverá otro widget.

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    // TODO: Replace Container with widgets.
    return Container();
  }
}
3

Usar el widget personalizado

Cuando la aplicación esté terminada, habrá 25 instancias de este widget en la pantalla. Por ahora, sin embargo, muestra solo uno para que puedas ver las actualizaciones a medida que se realizan. En el método MainApp.build, reemplaza el widget Text con el siguiente:

dart
class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Tile('A', HitType.hit), // NEW
        ),
      ),
    );
  }
}

En este momento, tu aplicación estará en blanco, porque el widget Tile devuelve un Container vacío, el cual no muestra nada por defecto.

4

El widget Container

El widget Tile consta de tres de los widgets principales más comunes: Container, Center, y Text. Container es un widget de conveniencia que envuelve varios widgets de estilo principales, como Padding, ColoredBox, SizedBox, y DecoratedBox.

Dado que la UI terminada contiene 25 widgets Tile en columnas y filas ordenadas, debería tener un tamaño explícito. Configura las propiedades de ancho y alto en el Container. (También podrías hacer esto con un widget SizedBox, pero a continuación usaremos más propiedades del Container).

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    // NEW
    return Container(
      width: 60,
      height: 60,
      // TODO: Add needed widgets
    );
  }
}
5

BoxDecoration

A continuación, agrega un Border a la caja con el siguiente código:

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    // NEW
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        // TODO: add background color
      ),
    );
  }
}

BoxDecoration es un objeto que sabe cómo agregar cualquier cantidad de decoraciones a un widget, desde el color de fondo hasta bordes, sombras de caja y más. En este caso, has agregado un borde. Cuando realices un hot reload, debería haber un borde de color claro alrededor del cuadrado blanco.

Cuando este juego esté terminado, el color de la casilla dependerá del intento del usuario. La casilla será verde cuando el usuario haya acertado, amarilla cuando la letra sea correcta pero la posición sea incorrecta, y gris si el intento es incorrecto en ambos aspectos.

La siguiente figura muestra las tres posibilidades.

Una captura de pantalla de una casilla verde, amarilla y gris.

Para lograr esto en la UI, usa una switch expression para establecer el color del BoxDecoration.

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (hitType) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
        // TODO: add children
      ),
    );
  }
}
6

Widgets hijos

Finalmente, agrega los widgets Center y Text a la propiedad Container.child.

La mayoría de los widgets en el SDK de Flutter tienen una propiedad child o children que está destinada a recibir un widget o una lista de widgets, respectivamente. Es una buena práctica usar la misma convención de nomenclatura en tus propios widgets personalizados.

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (hitType) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
      ),
      child: Center(
        child: Text(
          letter.toUpperCase(),
          style: Theme.of(context).textTheme.titleLarge,
        ),
      ),
    );
  }
}

Haz un hot reload y aparecerá una caja verde. Para alternar el color, actualiza y haz hot reload del HitType pasado al Tile que creaste:

dart
// main.dart line ~16
// green
Tile('A', HitType.hit);
// grey
Tile('A', HitType.miss);
// yellow
Tile('A', HitType.partial);

Pronto, esta pequeña caja será uno de los muchos widgets en la pantalla. En la próxima lección, comenzarás a construir la cuadrícula del juego en sí.

7

Revisión

Qué lograste

Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.
Construiste un StatelessWidget personalizado

Creaste un nuevo widget Tile extendiendo StatelessWidget. Cada widget tiene un constructor para aceptar datos y un build método que devuelve otros widgets. Este patrón es fundamental para construir interfaces de usuario con Flutter.

Hiciste que los widgets sean reutilizables con parámetros de constructor

Al aceptar letter y hitType como parámetros del constructor, tu widget Tile puede mostrar contenido y colores diferentes. Pasar datos a través de constructores es cómo puedes crear componentes flexibles y reutilizables.

Aplicaste estilos a los widgets usando Container y BoxDecoration

Usaste Container para establecer el tamaño del widget y BoxDecoration para agregar bordes y colores de fondo. Luego, para aplicar estilos condicionales al color de la casilla, usaste una switch expression sobre el valor hitType.

8

Ponte a prueba

Cuestionario sobre Fundamentos de Widgets

1 / 2
What must every Flutter widget's build method return?
  1. Otro widget.

    ¡Así es!

    El método build siempre devuelve otro widget, el cual forma parte del árbol de widgets.

  2. Un booleano que indica éxito o fallo.

    No exactamente.

    Los widgets no indican éxito; devuelven otros widgets para ser renderizados.

  3. Null si no hay nada que mostrar.

    No exactamente.

    El método build no puede devolver null; debe devolver un widget válido.

  4. Un String que describe el widget.

    No exactamente.

    El método build devuelve un widget, no un String.

Which object is used to add decorations like borders, background colors, and shadows to a Container?
  1. EdgeInsets

    No exactamente.

    EdgeInsets sirve para especificar el padding o margen, no decoraciones visuales.

  2. ThemeData

    No exactamente.

    ThemeData sirve para aplicar estilos a nivel de aplicación, no para decoraciones de contenedores individuales.

  3. TextStyle

    No exactamente.

    TextStyle sirve para dar formato al texto, no para decoraciones de contenedores.

  4. BoxDecoration

    ¡Así es!

    BoxDecoration puede agregar bordes, colores de fondo, gradientes, sombras y más a un Container.