Mobile Development 26 min read

Mastering Flutter Canvas: Drawing Basics, Shapes, Text Effects, and Animated Waves

This comprehensive guide walks you through Flutter's drawing fundamentals, covering Canvas, Paint, Path, Color, and advanced techniques such as custom text outlines, image shaders, and animated wave effects, with clear code examples and practical tips for creating dynamic graphics in mobile apps.

BaiPing Technology
BaiPing Technology
BaiPing Technology
Mastering Flutter Canvas: Drawing Basics, Shapes, Text Effects, and Animated Waves

Flutter Drawing (Basic Application)

In mobile development, drawing is essential for buttons, animations, and custom graphics. This article introduces the core concepts of Flutter's drawing API, including Canvas, Paint, Path, and Color, and shows how to use them to create various visual effects.

Drawing API

All examples are based on Flutter 2.2. The Dart UI library can be found at https://api.flutter-io.cn/flutter/dart-ui/dart-ui-library.html .

Drawing Elements

Canvas : The drawing surface that records operations.

Paint : Describes style information such as color, stroke width, and shader.

Path : A collection of lines and curves that define shapes.

Color : Immutable 32‑bit ARGB values.

Canvas

Canvas provides methods like drawLine , drawRect , drawPath , clipRect , save , and restore . It also maintains a current transformation matrix and clipping region.

Paint

Paint is used to describe how shapes are rendered. Important properties include color , strokeWidth , style , blendMode , shader , and others.

Path

A Path consists of sub‑paths made of line, quadratic, cubic, and conic segments. Methods such as moveTo , lineTo , quadraticBezierTo , and close build the geometry.

Color

Colors can be created with constructors like Color(0xFF42A5F5) or Color.fromARGB(255, 66, 165, 245) . Be careful to provide eight hex digits; otherwise the color becomes fully transparent.

Simple Applications

Drawing Points and Lines

class Paper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      alignment: Alignment.center,
      child: CustomPaint(painter: MyPainter()),
    );
  }
}

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paintLine = Paint()
      ..color = Colors.black
      ..strokeWidth = 3;
    canvas.drawLine(Offset(-100, 0), Offset(100, 0), paintLine);
    canvas.drawLine(Offset(0, 100), Offset(0, -100), paintLine);
    final Paint paintPoint = Paint()
      ..color = Colors.red;
    canvas.drawCircle(Offset(0, 0), 10, paintPoint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Drawing a Five‑Pointed Star

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = Colors.red
      ..strokeWidth = 3
      ..style = PaintingStyle.stroke;
    final Path path = Path();
    path.moveTo(0, -100);
    path.lineTo(59, 81);
    path.lineTo(-95, -31);
    path.lineTo(95, -31);
    path.lineTo(-59, 81);
    path.lineTo(0, -100);
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Text Outline Using Paint

Text(
  'BillionBottle',
  style: TextStyle(
    fontSize: 100,
    fontWeight: FontWeight.bold,
    fontStyle: FontStyle.italic,
    foreground: Paint()
      ..style = PaintingStyle.stroke
      ..strokeWidth = 6
      ..color = Colors.blue[700]!,
  ),
),
Text(
  'BillionBottle',
  style: TextStyle(
    fontSize: 100,
    fontWeight: FontWeight.bold,
    fontStyle: FontStyle.italic,
    color: Colors.white,
  ),
),

Image Shader for Text Stroke

Paint()
  ..style = PaintingStyle.stroke
  ..strokeWidth = 14
  ..color = Colors.black
  ..shader = ImageShader(
    _image,
    TileMode.repeated,
    TileMode.repeated,
    Float64List.fromList([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]),
  );

Wave Loading Effect

Wave animations are common loading indicators. By using quadratic Bézier curves and animating the canvas translation, a smooth moving wave can be created.

Basic Wave with Bézier Curve

final double waveWidth = 80;
final double waveHeight = 40;

void paint(Canvas canvas, Size size) {
  canvas.translate(ScreenUtils.screenWidth / 2, ScreenUtils.screenHeight / 2);
  final Paint paint = Paint()
    ..color = Colors.red
    ..style = PaintingStyle.fill;
  final Path path = Path();
  path.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0);
  path.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0);
  canvas.drawPath(path, paint);
}

Animated Wave Using AnimationController

class _PaperState extends State<Paper> with SingleTickerProviderStateMixin {
  late final AnimationController _controllerX;

  @override
  void initState() {
    super.initState();
    _controllerX = AnimationController(
      duration: const Duration(milliseconds: 600),
      vsync: this,
    )..repeat();
  }

  @override
  void dispose() {
    _controllerX.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: CustomPaint(painter: MyPainter(_controllerX)),
    );
  }
}

class MyPainter extends CustomPainter {
  final Animation<double> repaintX;
  const MyPainter(this.repaintX) : super(repaint: repaintX);

  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(ScreenUtils.screenWidth / 2, ScreenUtils.screenHeight / 2);
    canvas.translate(2 * waveWidth * repaintX.value, 0);
    // draw wave path (same as above)
  }

  @override
  bool shouldRepaint(covariant MyPainter oldDelegate) =>
      oldDelegate.repaintX != repaintX;
}

Clipping the Wave Inside a Rectangle

canvas.clipRect(Rect.fromCenter(
  center: Offset(waveWidth * 3, -60),
  width: waveWidth * 2,
  height: 200.0,
));

Two‑Axis Animation with TickerProviderStateMixin

class _PaperState extends State<Paper2> with TickerProviderStateMixin {
  late final AnimationController _controllerX;
  late final AnimationController _controllerY;

  @override
  void initState() {
    super.initState();
    _controllerX = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    )..repeat();
    _controllerY = AnimationController(
      duration: const Duration(milliseconds: 5000),
      vsync: this,
    )..repeat();
  }

  @override
  void dispose() {
    _controllerX.dispose();
    _controllerY.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: CustomPaint(
        painter: MyPainter(
          CurveTween(curve: Curves.linear).animate(_controllerX),
          _controllerY,
        ),
      ),
    );
  }
}

Applying the Wave to a Logo

The final example combines background and foreground waves, clipping, and custom text to create a logo with a moving water effect.

// Inside MyPainter.paint()
canvas.translate(ScreenUtils.screenWidth / 2, ScreenUtils.screenHeight / 2);
canvas.clipRect(Rect.fromCenter(
  center: Offset(waveWidth, -60),
  width: waveWidth * 2,
  height: 200.0,
));

// draw background wave (faster)
canvas.save();
canvas.translate(-4 * waveWidth * repaintX.value, -220 * repaintY.value);
canvas.drawPath(path, paint..color = Colors.orange.withAlpha(88));
canvas.restore();

// draw foreground wave
canvas.save();
canvas.translate(-2 * waveWidth * repaintX.value, -220 * repaintY.value);
canvas.drawPath(path, paint..color = Colors.red);
canvas.restore();

// draw clipped text inside the wave area
_drawTextWithParagraph(canvas, TextAlign.center, Colors.white);

Conclusion

This article introduced the basic and advanced usage of Flutter's drawing APIs, including Canvas, Paint, Path, Color, image shaders, and animation controllers. By mastering these tools you can create custom graphics, loading animations, and sophisticated visual effects for your mobile applications.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

FlutteranimationGraphicsBezier CurveCustomPainter
BaiPing Technology
Written by

BaiPing Technology

Official account of the BaiPing app technology team. Dedicated to enhancing human productivity through technology. | DRINK FOR FUN!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.