Flutter 绘制 Tiled Lines

927 阅读3分钟

前言

前几天发现了一个使用 <canvas> 绘制图形的教程 generative artistry 感觉很有意思,尝试用 Flutter 实现。本文实现第一篇教程的图形 Tiled Lines 效果如下。

创建画布

首先使用一个 Container 控件创建一个 320*320 大小的绘制区域,添加 CustomPaint 画布和一个继承 CustomPainter 的画笔 TiledLinesPainter。关于 CustomPaintCustomPainter 的知识可以查阅这篇文章 使用 Flutter 绘制图表(一)柱状图📊

import 'package:flutter/material.dart';

class TiledLines extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          width: 320.0,
          height: 320.0,
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.black,
              width: 1.0,
            ),
          ),
          child: CustomPaint(
            painter: TiledLinesPainter(),
          ),
        ),
      ),
    );
  }
}

class TiledLinesPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {}

  bool shouldRepaint(TiledLinesPainter oldDelegate) => false;
}

绘制线条

创建好画布后在 TiledLinesPainterpaint 方法里进行绘制。添加一个 _drawLine 方法用来绘制线条,绘制线条需要起始点和终止点,通过参数将数值传入。

class TiledLinesPainter extends CustomPainter {
  void _drawLine(
    Canvas canvas,
    double x,
    double y,
    double width,
    double height,
  ) {
    final Paint paint = Paint()
      ..strokeCap = StrokeCap.square
      ..strokeWidth = 2;

    Offset p1 = Offset(x, y);
    Offset p2 = Offset(x + width, y + height);

    canvas.drawLine(p1, p2, paint);
  }

  @override
  void paint(Canvas canvas, Size size) {
    _drawLine(canvas, 0, 0, size.width, size.height);
  }

  bool shouldRepaint(TiledLinesPainter oldDelegate) => false;
}

加入随机性

使用 Random().nextBool() 方法创建一个随机的布尔值,在绘制线条之前改变起始点和终止点的坐标,这样 _drawLine 方法就有了绘制不同方向的线条的能力。

void _drawLine(
    Canvas canvas,
    double x,
    double y,
    double width,
    double height,
  ) {
  final bool isLeftToRight = Random().nextBool();

  final Paint paint = Paint()
    ..strokeCap = StrokeCap.square
    ..strokeWidth = 2;

  Offset p1;
  Offset p2;

  if (isLeftToRight) {
    p1 = Offset(x, y);
    p2 = Offset(x + width, y + height);
  } else {
    p1 = Offset(x + width, y);
    p2 = Offset(x, y + height);
  }

  canvas.drawLine(p1, p2, paint);
}

绘制更多的线条

可以绘制更多的线条喽!给 TiledLinesPainter 添加一个 step 属性,表示在画布上每隔多长距离绘制一条线。使用 step 将画布分割为多个小的方格,在每个小的方格里面绘制线条。

class TiledLinesPainter extends CustomPainter {
  final double step;

  TiledLinesPainter(this.step);

  void _drawLine(
    Canvas canvas,
    double x,
    double y,
    double width,
    double height,
  ) {
    final bool isLeftToRight = Random().nextBool();

    final Paint paint = Paint()
      ..strokeCap = StrokeCap.square
      ..strokeWidth = 2;

    Offset p1;
    Offset p2;

    if (isLeftToRight) {
      p1 = Offset(x, y);
      p2 = Offset(x + width, y + height);
    } else {
      p1 = Offset(x + width, y);
      p2 = Offset(x, y + height);
    }

    canvas.drawLine(p1, p2, paint);
  }

  @override
  void paint(Canvas canvas, Size size) {
    for (double x = 0; x < size.width; x += step) {
      for (double y = 0; y < size.height; y += step) {
        _drawLine(canvas, x, y, step, step);
      }
    }
  }

  bool shouldRepaint(TiledLinesPainter oldDelegate) => false;
}

//...

TiledLinesPainter(20)

绘制边框和阴影

最后给画布添加边框和阴影效果,大功告成!👏 感谢阅读。

import 'dart:math';
import 'package:flutter/material.dart';

class TiledLines extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    List<BoxShadow> shadows = [];
    double opacity = 0.1;

    // 添加画布阴影
    for (double i = 1; i <= 16; i++) {
      opacity -= 0.01;
      opacity = opacity > 0.01 ? opacity : 0.01;

      shadows.add(
        BoxShadow(
          offset: Offset(-i, i),
          color: Color.fromRGBO(0, 0, 0, opacity),
          blurRadius: 2,
          spreadRadius: 1,
        ),
      );
    }

    return Scaffold(
      body: Center(
        child: Container(
          width: 320.0,
          height: 320.0,
          decoration: BoxDecoration(
            // 添加画布边框
            border: Border.all(
              color: Colors.black,
              width: 20.0,
            ),
            boxShadow: shadows,
          ),
          child: Container(
            color: Colors.white,
            padding: const EdgeInsets.all(20.0),
            child: CustomPaint(
              painter: TiledLinesPainter(20),
            ),
          ),
        ),
      ),
    );
  }
}

class TiledLinesPainter extends CustomPainter {
  final double step;

  TiledLinesPainter(this.step);

  void _drawLine(
    Canvas canvas,
    double x,
    double y,
    double width,
    double height,
  ) {
    // 创建随机性
    final bool isLeftToRight = Random().nextBool();

    final Paint paint = Paint()
      ..strokeCap = StrokeCap.square
      ..strokeWidth = 2;

    Offset p1;
    Offset p2;

    // 设置线条的起始点和终止点
    if (isLeftToRight) {
      p1 = Offset(x, y);
      p2 = Offset(x + width, y + height);
    } else {
      p1 = Offset(x + width, y);
      p2 = Offset(x, y + height);
    }

    canvas.drawLine(p1, p2, paint);
  }

  @override
  void paint(Canvas canvas, Size size) {
    // 使用 step 分割画布,创建小的绘制方格
    for (double x = 0; x < size.width; x += step) {
      for (double y = 0; y < size.height; y += step) {
        _drawLine(canvas, x, y, step, step);
      }
    }
  }

  bool shouldRepaint(TiledLinesPainter oldDelegate) => false;
}

参考

Tiled Lines

本文使用 mdnice 排版