Implementing Animated Gradients with CustomPainter
Oct 22, 2025



Summary
Summary
Summary
Summary
This tutorial demonstrates how to build performant animated gradients in Flutter using CustomPainter. It covers creating shaders in paint(), driving animations via AnimationController and AnimatedBuilder, and practical performance optimizations for mobile development, including reusing objects, restricting repaint areas, and integrating with widget trees.
This tutorial demonstrates how to build performant animated gradients in Flutter using CustomPainter. It covers creating shaders in paint(), driving animations via AnimationController and AnimatedBuilder, and practical performance optimizations for mobile development, including reusing objects, restricting repaint areas, and integrating with widget trees.
This tutorial demonstrates how to build performant animated gradients in Flutter using CustomPainter. It covers creating shaders in paint(), driving animations via AnimationController and AnimatedBuilder, and practical performance optimizations for mobile development, including reusing objects, restricting repaint areas, and integrating with widget trees.
This tutorial demonstrates how to build performant animated gradients in Flutter using CustomPainter. It covers creating shaders in paint(), driving animations via AnimationController and AnimatedBuilder, and practical performance optimizations for mobile development, including reusing objects, restricting repaint areas, and integrating with widget trees.
Key insights:
Key insights:
Key insights:
Key insights:
Creating A CustomPainter: Build shaders in paint(), pass animation state in, and avoid per-frame allocations by reusing Paint and Rect objects.
Animating The Gradient: Drive animations with AnimationController and update gradient endpoints, stops, or colors; keep logic outside the painter.
Performance And Optimization: Limit painted areas, reuse instances, profile on device, and use RepaintBoundary to contain raster costs.
Integrating In Mobile UI: Use AnimatedBuilder/ValueListenableBuilder to minimize rebuilds and feed theme or interaction changes into the painter.
Reusable Painter Patterns: Design small, parameterized painter APIs to reuse animated gradient logic across screens and adapt to different UI needs.
Introduction
Animated gradients add motion and polish to Flutter apps with relatively little runtime cost when implemented correctly. This tutorial shows how to implement animated gradients using CustomPainter — giving you precise control of shader creation, animation parameters, and painting performance. We target Flutter for mobile development, but techniques apply to any Flutter platform.
Creating A CustomPainter
CustomPainter exposes a low-level paint callback where you can create shaders and draw shapes directly to the canvas. For animated gradients you typically create a Gradient shader in paint() and use canvas.drawRect or canvas.drawPath to fill the area. Keep painters stateless: pass the animation value in via the constructor and mark shouldRepaint to reflect changes.
Key points:
Use ui.Gradient (LinearGradient/RadialGradient) to generate a Shader via createShader(rect).
Limit allocations in paint(): reuse Rects, Paths, and Paint objects where possible and avoid creating large objects every frame.
Example paint method inside a CustomPainter:
import 'dart:ui' as ui;
@override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
final shader = ui.Gradient.linear(
rect.topLeft,
rect.bottomRight,
[Color(0xff3366ff), Color(0xff00ccff)],
[0.0, 1.0],
);
final paint = Paint()..shader = shader;
canvas.drawRect(rect, paint);
}
That snippet shows a static linear gradient. To animate you’ll adjust the stops, colors, or the shader transform on each frame.
Animating The Gradient
Animation can be driven by an AnimationController in a StatefulWidget. Feed the animated value(s) into the CustomPainter so paint() can recompute the shader each frame. There are several animation strategies:
Interpolate between two sets of colors using Color.lerp and update the gradient’s colors.
Animate the gradient’s stops array to move color boundaries.
Apply a matrix transform to the shader using shader.toImage? (limited) — instead, shift endpoints or recompute shader with offset.
Keep the animation logic outside the painter and provide only the minimal state required. Here’s a compact example that shifts the gradient endpoints over time:
class AnimatedGradientWidget extends StatefulWidget {
@override
_AnimatedGradientWidgetState createState() => _AnimatedGradientWidgetState();
}
// In build: use AnimatedBuilder to pass animation.value into CustomPaint
(See full sample projects for expanded implementations. The important pattern is: controller -> AnimatedBuilder -> CustomPaint(painter: MyPainter(value))).
Performance And Optimization
Painting on every frame can be cheap if you avoid allocations and complex operations. Follow these optimizations:
Reuse Paint and Rect objects as instance fields on the painter and only update shader and color properties per frame.
Limit the painted area. If only a header needs animation, restrict the CustomPaint size instead of painting the whole screen.
Use low-cost gradients. LinearGradient with a small number of color stops is faster than complex shaders.
Offload expensive precomputation (like generating intermediate palettes) to a separate isolate if it becomes a bottleneck.
Profile on device using Flutter DevTools to watch GPU and raster thread performance; adjust FPS and animation complexity accordingly for mobile development requirements.
shouldRepaint should compare the incoming animation value to the old one; avoid returning true unconditionally when unnecessary.
Integrating In Mobile UI
Use AnimatedBuilder or ValueListenableBuilder to minimize widget rebuilds and to keep repaint scope small. When placing a CustomPaint in a layout, set isComplex and willChange hints appropriately if you use RepaintBoundary. For example, wrap the animated gradient area with a RepaintBoundary to prevent raster work spilling into unrelated widgets.
For touch interactions, combine GestureDetector with a controller to pause or change direction of the animation. Make the gradient reactive to app state (dark mode, accessibility settings) by feeding theme colors into the painter.
Reusable painters are useful: design a small API (e.g., AnimatedGradientPainter({double t, List colors, bool horizontal})) so you can reuse the same painter with different parameters across screens.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
Implementing animated gradients with CustomPainter gives you pixel-level control for expressive, efficient visuals in Flutter mobile development. Keep animation logic separate, minimize allocations in paint(), and adjust complexity for device performance. With careful reuse of Paint/Rect and targeted repaint boundaries, animated gradients become a performant part of your UI toolkit. Start simple—animate stops or endpoints first—then layer more complexity only when you profile and confirm capability.
Introduction
Animated gradients add motion and polish to Flutter apps with relatively little runtime cost when implemented correctly. This tutorial shows how to implement animated gradients using CustomPainter — giving you precise control of shader creation, animation parameters, and painting performance. We target Flutter for mobile development, but techniques apply to any Flutter platform.
Creating A CustomPainter
CustomPainter exposes a low-level paint callback where you can create shaders and draw shapes directly to the canvas. For animated gradients you typically create a Gradient shader in paint() and use canvas.drawRect or canvas.drawPath to fill the area. Keep painters stateless: pass the animation value in via the constructor and mark shouldRepaint to reflect changes.
Key points:
Use ui.Gradient (LinearGradient/RadialGradient) to generate a Shader via createShader(rect).
Limit allocations in paint(): reuse Rects, Paths, and Paint objects where possible and avoid creating large objects every frame.
Example paint method inside a CustomPainter:
import 'dart:ui' as ui;
@override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
final shader = ui.Gradient.linear(
rect.topLeft,
rect.bottomRight,
[Color(0xff3366ff), Color(0xff00ccff)],
[0.0, 1.0],
);
final paint = Paint()..shader = shader;
canvas.drawRect(rect, paint);
}
That snippet shows a static linear gradient. To animate you’ll adjust the stops, colors, or the shader transform on each frame.
Animating The Gradient
Animation can be driven by an AnimationController in a StatefulWidget. Feed the animated value(s) into the CustomPainter so paint() can recompute the shader each frame. There are several animation strategies:
Interpolate between two sets of colors using Color.lerp and update the gradient’s colors.
Animate the gradient’s stops array to move color boundaries.
Apply a matrix transform to the shader using shader.toImage? (limited) — instead, shift endpoints or recompute shader with offset.
Keep the animation logic outside the painter and provide only the minimal state required. Here’s a compact example that shifts the gradient endpoints over time:
class AnimatedGradientWidget extends StatefulWidget {
@override
_AnimatedGradientWidgetState createState() => _AnimatedGradientWidgetState();
}
// In build: use AnimatedBuilder to pass animation.value into CustomPaint
(See full sample projects for expanded implementations. The important pattern is: controller -> AnimatedBuilder -> CustomPaint(painter: MyPainter(value))).
Performance And Optimization
Painting on every frame can be cheap if you avoid allocations and complex operations. Follow these optimizations:
Reuse Paint and Rect objects as instance fields on the painter and only update shader and color properties per frame.
Limit the painted area. If only a header needs animation, restrict the CustomPaint size instead of painting the whole screen.
Use low-cost gradients. LinearGradient with a small number of color stops is faster than complex shaders.
Offload expensive precomputation (like generating intermediate palettes) to a separate isolate if it becomes a bottleneck.
Profile on device using Flutter DevTools to watch GPU and raster thread performance; adjust FPS and animation complexity accordingly for mobile development requirements.
shouldRepaint should compare the incoming animation value to the old one; avoid returning true unconditionally when unnecessary.
Integrating In Mobile UI
Use AnimatedBuilder or ValueListenableBuilder to minimize widget rebuilds and to keep repaint scope small. When placing a CustomPaint in a layout, set isComplex and willChange hints appropriately if you use RepaintBoundary. For example, wrap the animated gradient area with a RepaintBoundary to prevent raster work spilling into unrelated widgets.
For touch interactions, combine GestureDetector with a controller to pause or change direction of the animation. Make the gradient reactive to app state (dark mode, accessibility settings) by feeding theme colors into the painter.
Reusable painters are useful: design a small API (e.g., AnimatedGradientPainter({double t, List colors, bool horizontal})) so you can reuse the same painter with different parameters across screens.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
Implementing animated gradients with CustomPainter gives you pixel-level control for expressive, efficient visuals in Flutter mobile development. Keep animation logic separate, minimize allocations in paint(), and adjust complexity for device performance. With careful reuse of Paint/Rect and targeted repaint boundaries, animated gradients become a performant part of your UI toolkit. Start simple—animate stops or endpoints first—then layer more complexity only when you profile and confirm capability.
Build Flutter Apps Faster with Vibe Studio
Build Flutter Apps Faster with Vibe Studio
Build Flutter Apps Faster with Vibe Studio
Build Flutter Apps Faster with Vibe Studio
Vibe Studio is your AI-powered Flutter development companion. Skip boilerplate, build in real-time, and deploy without hassle. Start creating apps at lightning speed with zero setup.
Vibe Studio is your AI-powered Flutter development companion. Skip boilerplate, build in real-time, and deploy without hassle. Start creating apps at lightning speed with zero setup.
Vibe Studio is your AI-powered Flutter development companion. Skip boilerplate, build in real-time, and deploy without hassle. Start creating apps at lightning speed with zero setup.
Vibe Studio is your AI-powered Flutter development companion. Skip boilerplate, build in real-time, and deploy without hassle. Start creating apps at lightning speed with zero setup.











