CustomPainter — qué es, métodos paint y shouldRepaint en Flutter

Autor: IT Sectr Publicado: 2026-07-22 Tiempo de lectura: 10 min

CustomPainter es una clase abstracta del SDK de Flutter que brinda a los desarrolladores control total sobre el renderizado de gráficos 2D personalizados en Canvas. El desarrollador crea una subclase de CustomPainter e implementa dos métodos obligatorios: paint(Canvas, Size) y shouldRepaint(covariant CustomPainter oldDelegate). Según la Documentación de la API de Flutter (2026), CustomPainter se utiliza junto con el widget CustomPaint, que delega el renderizado al objeto CustomPainter proporcionado en cada fotograma de animación o cambio de estado.

Puntos Clave

  • CustomPainter — clase abstracta de Flutter que define el renderizado personalizado en Canvas mediante los métodos paint y shouldRepaint
  • CustomPaint — widget que acepta CustomPainter como parámetro painter o foregroundPainter y muestra el resultado en pantalla
  • Canvas — objeto que proporciona métodos de dibujo (drawLine, drawCircle, drawPath, drawRect, drawArc y otros)
  • shouldRepaint — método de optimización que determina si es necesario volver a dibujar el objeto al actualizarse
  • size — tamaño del área de dibujo pasado a paint(Canvas canvas, Size size) desde el widget padre

¿Qué es CustomPainter?

CustomPainter es una clase base de Flutter para implementar gráficos 2D personalizados. Reemplaza el enfoque de sobrescribir onDraw en Android, proporcionando un modelo de renderizado más flexible y declarativo. El desarrollador crea una subclase de CustomPainter, sobrescribe el método paint donde recibe Canvas y Size, y realiza todas las operaciones de dibujo en Canvas. El widget CustomPaint gestiona la llamada a paint e integra el resultado en el árbol de widgets.

La diferencia clave entre CustomPainter y el dibujo directo a través de Canvas es la separación de la lógica de renderizado (paint) y el desencadenante de redibujado (shouldRepaint). El sistema Flutter llama a shouldRepaint cada vez que el widget padre cambia. Si shouldRepaint devuelve false, no se llama a paint, lo que ahorra recursos. Este mecanismo es similar a shouldRebuild en RenderObject y forma parte de la arquitectura de Flutter con sus tres árboles (Widget, Element, RenderObject).

CustomPaint es un widget que acepta los parámetros painter (capa de fondo) y foregroundPainter (capa frontal). Ambos parámetros aceptan objetos CustomPainter. El painter de fondo se dibuja antes del widget hijo de CustomPaint, y el foregroundPainter después. Esto permite usar CustomPaint como contenedor con fondo decorativo o superposición sin perder la posibilidad de incrustar widgets normales en su interior.

Canvas y Paint en Flutter

Canvas es el objeto central de dibujo en Flutter, que encapsula la superficie gráfica. Proporciona alrededor de 40 métodos de dibujo: drawLine, drawCircle, drawRect, drawPath, drawArc, drawImage, drawPicture, drawPoints y otros. Canvas en Flutter corresponde a una clase similar en Android Canvas pero está adaptado a la arquitectura Dart y Flutter Engine. Canvas opera en un sistema de coordenadas donde el origen (0, 0) es la esquina superior izquierda.

Paint — que no debe confundirse con el método paint() — es una clase que define el estilo de dibujo: color, grosor de línea, estilo (fill o stroke), opacidad, modo de mezcla y máscara de suavizado. Paint se configura antes de dibujar y se pasa a los métodos de Canvas. Importante: Paint es un objeto mutable, pero se recomienda crearlo una vez y reutilizarlo, cambiando solo los parámetros necesarios.

Método de CanvasPropósitoParámetros
drawCircleDibuja un círculoOffset center, double radius, Paint paint
drawLineDibuja un segmento de líneaOffset p1, Offset p2, Paint paint
drawRectDibuja un rectánguloRect rect, Paint paint
drawPathDibuja un trazado arbitrarioPath path, Paint paint
drawArcDibuja un arco o sectorRect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint
drawRRectRectángulo con esquinas redondeadasRRect rrect, Paint paint
drawOvalDibuja un óvaloRect rect, Paint paint
drawImageDibuja una imagen desde ImageImage image, Offset p, Paint paint

Transformaciones de Canvas

Canvas admite transformaciones del sistema de coordenadas: translate(double dx, double dy) desplaza el origen; rotate(double radians) rota el sistema; scale(double sx, double sy) escala; skew(double sx, double sy) sesga. Antes de una transformación, se recomienda guardar el estado de Canvas mediante save() y restaurarlo después mediante restore(). Esto evita la acumulación de transformaciones en llamadas posteriores a paint.

Métodos paint y shouldRepaint

paint(Canvas canvas, Size size) es el método obligatorio que implementa toda la lógica de dibujo. El parámetro canvas es la superficie gráfica para dibujar. El parámetro size es el tamaño del área asignada para el dibujo (ancho y alto en píxeles lógicos). Canvas está preconfigurado para que el origen (0,0) corresponda a la esquina superior izquierda del área de dibujo. Todas las coordenadas se especifican en relación con este origen.

shouldRepaint(covariant CustomPainter oldDelegate) es un método obligatorio que optimiza la frecuencia de redibujado. Flutter llama a shouldRepaint en cada actualización del árbol de widgets que afecta a CustomPainter. Si el método devuelve false, la imagen ya renderizada se considera válida y no se llama a paint. Una implementación típica es: return oldDelegate.someField != someField — comparación de los campos del painter antiguo y nuevo para determinar la necesidad de redibujar.

Para imágenes animadas o en constante cambio (relojes, indicadores de carga, gráficos) shouldRepaint debe devolver true en cada cambio de estado. Si el objeto es estático (logotipo, icono, fondo), shouldRepaint devuelve false después del primer renderizado. Flutter almacena automáticamente en caché el resultado de paint y vuelve a dibujar solo cuando cambia el tamaño, la opacidad o la llamada a shouldRepaint.

Ejemplo de dibujo personalizado con CustomPainter en Dart

Creemos GaugePainter — un indicador circular personalizado con relleno animado. CustomPainter acepta el valor actual (progreso de 0.0 a 1.0) y dibuja un arco con degradado. El método shouldRepaint compara el valor de progreso para optimizar. CustomPaint se coloca en el árbol de widgets con el parámetro size para limitar el área de dibujo.

dart
class GaugePainter extends CustomPainter {
    final double progress;
    final Color startColor;
    final Color endColor;

    GaugePainter({
        required this.progress,
        this.startColor = Colors.blue,
        this.endColor = Colors.cyan,
    });

    @override
    void paint(Canvas canvas, Size size) {
        final center = Offset(size.width / 2,
            size.height / 2);
        final radius = center.dx - 12;
        final bgPaint = Paint()
            ..color = Colors.grey.withOpacity(0.3)
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        final progressPaint = Paint()
            ..shader = LinearGradient(
                colors: [startColor, endColor],
            ).createShader(
                Rect.fromCircle(
                    center, radius))
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        canvas.drawCircle(center, radius, bgPaint);
        canvas.drawArc(
            Rect.fromCircle(center, radius),
            -math.pi / 2,
            2 * math.pi * progress,
            false, progressPaint);
    }

    @override
    bool shouldRepaint(
        GaugePainter oldDelegate) {
        return oldDelegate.progress != progress;
    }
}

CustomPainter con Path para formas complejas

El ejemplo dibuja un gráfico de onda utilizando Path. Path permite construir contornos arbitrarios a partir de líneas, curvas Bezier y arcos. Después de construir el contorno, se rellena con color o se delinea con una línea. En el ejemplo, el gráfico se construye a partir de una matriz de puntos que simulan una forma de onda.

dart
class WavePainter extends CustomPainter {
    final List<double> values;

    WavePainter({required this.values});

    @override
    void paint(Canvas canvas, Size size) {
        final path = Path();
        final dx = size.width /
            (values.length - 1);

        path.moveTo(0, size.height);
        for (var i = 0; i < values.length; i++) {
            final x = i * dx;
            final y = size.height *
                (1 - values[i]);
            path.lineTo(x, y);
        }
        path.lineTo(size.width, size.height);
        path.close();

        final fillPaint = Paint()
            ..color = Colors.blue.withOpacity(0.3)
            ..style = PaintingStyle.fill;

        canvas.drawPath(path, fillPaint);
    }

    @override
    bool shouldRepaint(
        WavePainter oldDelegate) {
        return oldDelegate.values != values;
    }
}

ForegroundPainter y backgroundPainter: diferencia

painter (fondo) se dibuja antes del widget hijo de CustomPaint. Si CustomPaint tiene un widget hijo (parámetro child), el painter de fondo se muestra debajo de él. Esto es adecuado para decoraciones: barras de progreso, patrones de fondo, marcas de agua. foregroundPainter se dibuja después del widget hijo, es decir, encima de él. Se usa para superposiciones: reflejos, máscaras, reglas de medición, anotaciones.

La diferencia entre painter y foregroundPainter es crítica para el orden Z. Si necesita dibujar algo detrás del contenido — use painter. Si encima del contenido — foregroundPainter. En algunos casos, puede usar ambos painters simultáneamente: fondo mediante painter, superposición de etiqueta mediante foregroundPainter. Esto permite separar la lógica de diferentes capas gráficas en dos clases CustomPainter separadas.

El parámetro size en el método paint es el mismo para ambos painters y está determinado por el tamaño del área asignada para CustomPaint. El tamaño se puede establecer explícitamente mediante el parámetro size en CustomPaint o implícitamente a través del contenedor padre. Si el tamaño es Size.zero, no se llama a paint — Flutter omite el renderizado para un área de tamaño cero. Este comportamiento debe tenerse en cuenta al animar la aparición de elementos.

Optimización del rendimiento de CustomPainter

La regla principal del rendimiento de CustomPainter es no crear objetos dentro del método paint. Canvas, Paint, Path, Rect, Offset y otros objetos de dibujo deben crearse en el constructor de CustomPainter o almacenarse en caché. Crear objetos dentro de paint provoca asignaciones en cada fotograma, lo que a 60 fotogramas por segundo causa recolección de basura frecuente y tirones en la animación (jank).

El almacenamiento en caché de Path acelera significativamente el renderizado de trazados complejos repetidos. Si el trazado no cambia entre fotogramas (por ejemplo, una cuadrícula de fondo), cree el Path una vez y reutilícelo múltiples veces. RepaintBoundary es un widget envoltorio que evita el redibujado de CustomPainter cuando cambia el árbol de widgets superior. Si CustomPainter es estático, envuélvalo en un RepaintBoundary para aislarlo.

Para CustomPainter animados con redibujados frecuentes, use Ticker o AnimationController con shouldRepaint devolviendo true en cada cambio del valor animado. Es óptimo comparar el campo animado en shouldRepaint en lugar de volver a dibujar en cada fotograma innecesariamente. Almacenamiento en caché de Rect para degradados complejos: si los colores del degradado no cambian, puede almacenar en caché el Shader mediante Paint.shader.

Preguntas Frecuentes

¿Cuál es la diferencia entre CustomPainter y Canvas en Flutter?

CustomPainter es una clase de contrato que define qué y cómo dibujar en Canvas. Canvas es el propio objeto de dibujo, que proporciona métodos (drawCircle, drawLine, etc.). CustomPainter recibe Canvas a través del método paint y controla su uso. Un mismo Canvas puede ser utilizado por diferentes CustomPainter en distintos momentos.

¿Se puede usar CustomPainter sin CustomPaint?

No, CustomPainter no tiene sentido sin CustomPaint. CustomPaint es el widget que llama al método paint de CustomPainter y muestra el resultado en pantalla. Sin CustomPaint, no se creará Canvas ni se ejecutará paint. Sin embargo, puede implementar su propio RenderObject con Canvas, pero este es un enfoque de nivel inferior.

¿Cómo animar un CustomPainter?

Use un AnimationController con un Ticker, pasando el valor animado a CustomPainter a través de los parámetros del constructor. Cuando el valor cambie, llame a setState() en el widget que contiene CustomPaint, lo que desencadena la llamada a shouldRepaint. Si shouldRepaint devuelve true, el sistema llama a paint para el nuevo fotograma de animación.

¿Por qué CustomPainter no se actualiza cuando los datos cambian?

La causa es casi siempre shouldRepaint devolviendo false. Verifique si los campos de oldDelegate se comparan correctamente con los nuevos valores. Si se usa un objeto mutable, shouldRepaint puede no detectar los cambios porque la referencia al objeto sigue siendo la misma. Use == u objetos inmutables para una comparación correcta.

¿Se puede usar CustomPainter para animación en ReorderableListView?

, CustomPainter se usa para renderizar decoraciones personalizadas en listas complejas. Sin embargo, tenga en cuenta que al reordenar elementos, RepaintBoundary dentro de ListView almacena en caché el renderizado de cada elemento. CustomPainter debe ser ligero y no contener operaciones pesadas en paint para no ralentizar la animación de arrastre.

Resumen

  • CustomPainter — clase abstracta de Flutter para renderizado 2D personalizado con los métodos paint y shouldRepaint
  • CustomPaint — widget que acepta painter (fondo) y foregroundPainter (primer plano) para mostrar gráficos
  • Canvas — objeto con métodos de dibujo: drawLine, drawCircle, drawPath, drawRect, drawArc y otros
  • Paint — clase que define el estilo de dibujo: color, grosor, estilo (fill/stroke), opacidad y modo de mezcla
  • shouldRepaint — método de optimización que devuelve true solo cuando cambian los campos que afectan el renderizado
  • Crear objetos dentro de paint está prohibido — todos los Paint, Path, Rect deben almacenarse en caché para 60 fps
  • RepaintBoundary — widget para aislar CustomPainter de los redibujados del árbol de widgets superior

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también