In Flutter, the Canvas
widget provides a powerful and flexible way to draw custom graphics, text, and shapes. One of the most interesting capabilities of the Canvas
is the ability to apply transformations and effects to these elements. This allows developers to create complex and dynamic visual experiences.
Understanding Canvas Transformations and Effects
Canvas transformations modify the coordinate space in which drawing occurs, allowing you to move, rotate, scale, and skew the canvas. Effects, on the other hand, alter the appearance of drawn elements through methods like applying shadows, colors, and gradients.
Why Use Transformations and Effects?
- Enhance Visual Appeal: Create dynamic and eye-catching UIs.
- Custom Animations: Build complex animations with ease.
- Unique Designs: Implement creative and non-standard layouts.
Implementing Transformations
Transformations in Flutter involve manipulating the canvas’s transformation matrix. Common transformation methods include translate
, rotate
, and scale
.
Translate Transformation
The translate
method moves the origin of the canvas to a new point, affecting all subsequent draw operations.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class TranslateExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Translate Example'),
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: TranslatePainter(),
),
),
);
}
}
class TranslatePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
// Translate the canvas by 50 pixels in the X and Y axes
canvas.translate(50, 50);
canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
In this example, the rectangle is drawn at (50, 50) instead of (0, 0) because the canvas origin has been translated.
Rotate Transformation
The rotate
method rotates the canvas around its origin by a specified angle (in radians).
import 'package:flutter/material.dart';
import 'dart:math' as math;
class RotateExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Rotate Example'),
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: RotatePainter(),
),
),
);
}
}
class RotatePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.green
..style = PaintingStyle.fill;
// Rotate the canvas by 45 degrees (π/4 radians)
canvas.rotate(math.pi / 4);
canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Here, the rectangle is rotated 45 degrees clockwise around the canvas origin.
Scale Transformation
The scale
method scales the coordinate space of the canvas, making drawn elements appear larger or smaller.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class ScaleExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Scale Example'),
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: ScalePainter(),
),
),
);
}
}
class ScalePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.orange
..style = PaintingStyle.fill;
// Scale the canvas by a factor of 1.5 in both axes
canvas.scale(1.5);
canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
In this case, the rectangle is drawn 1.5 times larger than its original size.
Implementing Effects
Effects in Flutter can be achieved through various properties of the Paint
object, such as shaders, color filters, and blend modes.
Applying Shadows
Shadows can be added using the drawShadow
method or by utilizing the MaskFilter
on the Paint
object.
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class ShadowExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Shadow Example'),
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: ShadowPainter(),
),
),
);
}
}
class ShadowPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black
..style = PaintingStyle.fill
..maskFilter = MaskFilter.blur(BlurStyle.normal, 5); // Adjust the blur radius as needed
canvas.drawRect(Rect.fromLTWH(50, 50, 100, 100), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
The MaskFilter
creates a blur effect that simulates a shadow around the rectangle.
Using Gradients
Gradients can be applied using Shader
objects, providing smooth color transitions.
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class GradientExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Gradient Example'),
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: GradientPainter(),
),
),
);
}
}
class GradientPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..shader = ui.Gradient.linear(
Offset(0, 0),
Offset(100, 100),
[Colors.red, Colors.blue],
)
..style = PaintingStyle.fill;
canvas.drawRect(Rect.fromLTWH(50, 50, 100, 100), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
This example applies a linear gradient to the rectangle, transitioning from red to blue.
Blend Modes
Blend modes define how a new drawing is composited with existing drawings on the canvas. They are set using the blendMode
property of the Paint
object.
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class BlendModeExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Blend Mode Example'),
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: BlendModePainter(),
),
),
);
}
}
class BlendModePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint1 = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final paint2 = Paint()
..color = Colors.red
..blendMode = BlendMode.srcOver // Try different blend modes
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(100, 100), 50, paint1);
canvas.drawCircle(Offset(150, 100), 50, paint2);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
In this example, the red circle is drawn over the blue circle using the srcOver
blend mode. You can experiment with different blend modes to achieve various effects.
Saving and Restoring Canvas State
When applying multiple transformations, it’s essential to save and restore the canvas state using save()
and restore()
methods to avoid unintended side effects.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class SaveRestoreExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Save Restore Example'),
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: SaveRestorePainter(),
),
),
);
}
}
class SaveRestorePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.purple
..style = PaintingStyle.fill;
// Save the current canvas state
canvas.save();
canvas.translate(50, 50);
canvas.rotate(math.pi / 4);
canvas.drawRect(Rect.fromLTWH(0, 0, 50, 50), paint);
// Restore the canvas to its previous state
canvas.restore();
canvas.drawRect(Rect.fromLTWH(150, 50, 50, 50), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
In this example, the first rectangle is translated and rotated. After calling restore()
, the canvas reverts to its original state, and the second rectangle is drawn without any transformations.
Conclusion
Applying transformations and effects to canvas elements in Flutter provides a vast array of creative possibilities. By understanding and utilizing methods such as translate
, rotate
, scale
, gradients, shadows, and blend modes, developers can create visually stunning and highly interactive Flutter applications. Remember to use save()
and restore()
appropriately to manage canvas states and avoid unexpected drawing behavior.