一统天下 flutter - 图形: 绘制

源码 https://github.com/webabcd/flutter_demo
作者 webabcd

一统天下 flutter - 图形: 绘制

示例如下:

lib\shape\paint.dart

/*
 * 绘制
 *
 * CustomPaint - 用于自定义绘制的 Widget(其内通过 CustomPainter 实现自定义的绘制逻辑)
 */

import 'dart:math';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_demo/helper.dart';

class PaintDemo extends StatefulWidget {
  const PaintDemo({Key? key}) : super(key: key);

  @override
  _PaintDemoState createState() => _PaintDemoState();
}

class _PaintDemoState extends State<PaintDemo> {

  /// 这个用于演示如何通过 CustomPainter 绘制指定的图像(注:这个 Image 是 dart:ui 库中的 Image)
  ui.Image? _image;

  /// 获取图像数据
  Future<ui.Image> loadImage(String asset) async {
    ByteData data = await rootBundle.load(asset);
    ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    ui.FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }

  @override
  void initState() {
    super.initState();

    /// 注:在 pubspec.yaml 中配置好需要打包的文件后,这里才能正确加载
    loadImage("assets/son.jpg").then((i) {
      setState(() {
        _image = i;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.orange,
      /// CustomPaint - 用于自定义绘制的 Widget
      ///   先绘制 painter,然后在 painter 的前面渲染 child, 然后在 child 的前面绘制 foregroundPainter
      child: CustomPaint(
        painter: MyCustomPainter(_image),
        child: const Center(
          child: MyText("我是 CustomPaint 的 child"),
        ),
        foregroundPainter: null,
        /// 如果不指定 child 则 CustomPaint 使用 size 作为自己的尺寸
        /// 如果指定了 child 则 CustomPaint 使用 child 的尺寸作为自己的尺寸
        size: Size.zero,
      ),
    );
  }
}

/// CustomPainter - 自定义绘制逻辑
/// 原点在左上角,右为 x 轴正方向,下为 y 轴正方向,顺时针为旋转正方向
/// 弧度是弧的长度与弧的半径的比值,所以弧度 π 就是 180 度
class MyCustomPainter extends CustomPainter {
  ui.Image? image;

  MyCustomPainter(this.image);

  /// Paint - 画笔
  final Paint _paint = Paint()
    /// 画笔颜色
    ..color = Colors.blue
    /// 画笔宽度
    ..strokeWidth = 5
    /// 是否抗锯齿
    ..isAntiAlias = true
    /// 绘制风格
    /// fill - 比如画个圆,中间会被自动填充
    /// stroke - 比如画个圆,中间不会被填充
    ..style = PaintingStyle.fill
    /// 模糊效果
    // ..maskFilter = const MaskFilter.blur(BlurStyle.inner, 3.0)
    /// 渲染质量
    ..filterQuality = FilterQuality.high
    /// 画笔两端的处理方式
    /// round - 有一个圆形的延伸
    /// square - 有一个矩形的延伸
    /// butt - 无特殊处理
    ..strokeCap = StrokeCap.round
    /// 画笔拐角处的处理方式(比如三角形的三个顶点)
    /// round - 圆角处理
    /// miter - 尖角处理
    /// bevel - 斜切处理
    ..strokeJoin = StrokeJoin.bevel
  ;

  /// 自定义绘制
  ///   canvas - 画布,所有 canvas 的操作都是调用的 native 方法
  ///   size - 绘制区域(即 CustomPaint 的尺寸,参见上面 CustomPaint 的 size 的说明)
  @override
  void paint(Canvas canvas, Size size) {
    log("paint size: $size");

    var points1 = [
      Offset(40, 40),
      Offset(140, 40),
      Offset(240, 40),
    ];
    _paint.strokeWidth = 20;
    /// 画点
    canvas.drawPoints(ui.PointMode.points, points1, _paint);

    var points2 = [
      Offset(40, 60),
      Offset(140, 60)
    ];
    _paint.strokeWidth = 5;
    /// 画直线
    canvas.drawPoints(ui.PointMode.lines, points2, _paint);

    var points3 = [
      Offset(240, 60),
      Offset(300, 100),
      Offset(240, 100),
    ];
    /// 画折线
    canvas.drawPoints(ui.PointMode.polygon, points3, _paint);

    /// 画圆(需要指定圆心和半径)
    canvas.drawCircle(const Offset(50, 120), 30, _paint);

    /// 画椭圆(在指定的矩形内绘制椭圆)
    canvas.drawOval(const Rect.fromLTRB(150, 120, 300, 180), _paint);

    _paint.style = PaintingStyle.stroke;
    /// 画圆弧
    ///   第 1 个参数:在指定的矩形内绘制椭圆
    ///   第 2 个参数:从椭圆上截取的圆弧的起始点的弧度
    ///   第 3 个参数:从椭圆上截取的圆弧的起始点到结束点的弧度
    ///   第 4 个参数:圆弧的两个端点是否要与椭圆的圆心用直线相连
    canvas.drawArc(const Rect.fromLTRB(40, 150, 200, 200), pi / 2, pi / 2, false, _paint);

    /// 画矩形
    canvas.drawRect(const Rect.fromLTRB(40, 240, 160, 300), _paint);

    /// 画圆角矩形
    canvas.drawRRect(RRect.fromLTRBAndCorners(200, 240, 300, 300, topLeft: const Radius.circular(20),), _paint);

    /// 画路径
    _paint.style = PaintingStyle.stroke;
    var path = Path()
      ..moveTo(40, 320)
      ..lineTo(80, 400)
      ..lineTo(40, 400)
      ..close(); /// 调用 close() 则自动连接起点和终点
    canvas.drawPath(path, _paint);

    /// 通过 path.arcTo() 画二次贝塞尔曲线
    /// 通过 path.cubicTo() 画三次贝塞尔曲线

    if (image != null) {
      /// 画 Image
      ///   第 1 个参数:需要画的 Image(注:这个 Image 是 dart:ui 库中的 Image)
      ///   第 2 个参数:从原图中截取指定矩形范围内的图像数据
      ///   第 3 个参数:在指定的矩形范围内拉伸并显示截取的图像数据
      canvas.drawImageRect(
          image!,
          Rect.fromLTRB(0, 0, image!.width.toDouble() - 20, image!.height.toDouble() - 20),
          Rect.fromLTRB(100, 320, 300, 400),
          _paint
      );
    }
  }

  /// 如果之后 widget 重新 build 了,就会执行到这里
  /// 一般通过判断 MyCustomPainter 新旧实例的与 UI 相关的参数是否发生变化来决定是否需要重绘
  @override
  bool shouldRepaint(MyCustomPainter oldDelegate) {
    return this != oldDelegate;
  }
}

源码 https://github.com/webabcd/flutter_demo
作者 webabcd

posted @ 2023-03-23 16:02  webabcd  阅读(114)  评论(0编辑  收藏  举报