当前位置: 代码网 > it编程>App开发>苹果IOS > Flutter CustomPaint自定义绘画示例详解

Flutter CustomPaint自定义绘画示例详解

2024年05月18日 苹果IOS 我要评论
正文custompaint是flutter中用于自由绘制的一个widget,它与android原生的绘制规则基本一致,以当前canves(画布)的左上角为原点进行绘制。在有些场景中,我们会需要绘制一些

正文

custompaint是flutter中用于自由绘制的一个widget,它与android原生的绘制规则基本一致,以当前canves(画布)的左上角为原点进行绘制。在有些场景中,我们会需要绘制一些高度定制化的组件,比如 ui 设计师给我们出了个难题 —— 弄一个奇形怪状的边框。这个时候我们就不能直接使用 flutter 自带的那些组件了,而是需要手动绘制组件,那就会需要用到 cuntompaint 组件。custompaint 组件和前端的 canvas差不多,允许我们在一个画布上绘制各种元素,包括点、线、矩形、圆弧、文字、图片等等。

custompaint 介绍

custompaint是一个 widget,其中有三个重要的参数:

custompaint(
  child: childwidget(),
  foregroundpainter: foregroundpainter(),
  painter: backgroundpainter(),
)

childcustompaint的子组件;

painterforegroundpainter:都是 custompainter 类,用于定义 canvas 绘制的内容。区别在于,首先是执行 painter 的绘制指令。然后是在背景上渲染 child 子组件。最后,foregroundpainter 的内容会绘制在 child 上一层。

案例展示:

import 'package:demo202112/utils/common_appbar.dart';
import "package:flutter/material.dart";
class mypaint extends statelesswidget {
  const mypaint({key? key}) : super(key: key);
  @override
  widget build(buildcontext context) {
    return scaffold(
      appbar: getappbar('custompaint'),
      body: custompaint(
        painter: mypainer(),
        child: container(height: 80,width: 80,child: text('child测试'),color: colors.red,),
        foregroundpainter: myforegroundpainer(),
      ),
    );
  }
}
class mypainer extends custompainter{
  late paint _paint;
  @override
  void paint(canvas canvas, size size) {
    _paint = paint();
    _paint.color = colors.blue;
    canvas.drawcircle(offset(100, 100), 100, _paint);
    canvas.drawline(offset(300, 300), offset(400, 400), _paint);
    // todo: implement paint
  }
  @override
  bool shouldrepaint(covariant custompainter olddelegate) {
    // todo: implement shouldrepaint
    throw unimplementederror();
  }
}
class myforegroundpainer extends custompainter{
  late paint _paint;
  @override
  void paint(canvas canvas, size size) {
    _paint = paint();
    _paint.color = colors.green;
    canvas.drawcircle(offset(100, 100), 70, _paint);
    // canvas.drawline(offset(300, 300), offset(400, 400), _paint);
    // todo: implement paint
  }
  @override
  bool shouldrepaint(covariant custompainter olddelegate) {
    // todo: implement shouldrepaint
    throw unimplementederror();
  }
}

运行效果:

child: 红色区域,传入一个子widget,这个widget图层会在painter在上,在foregroundpainter之下。

painter:蓝色区域。

foregroundpainter:绿色区域,它与painter都是custompainter类型的。通过名字大概也就知道了,它会在painter的上层,也就是说在同样的位置去绘制,foregroundpainter 会覆盖painter。

custompainter提供了一个paint绘图方法供我们绘制图形,该方法携带canvassize两个参数,其中 canvas 是画布,size 是画布大小。canvas 提供了很多绘制图形的方法,比如绘制路径、矩形、圆形和线条等等。

//画圆
drawcircle(offset c, double radius, paint paint) → void
//画图片
drawimage(image image, offset p, paint paint) → void
//画九宫图
drawimagenine(image image, rect center, rect dst, paint paint) → void
//画线
drawline(offset p1, offset p2, paint paint) → void
//画椭圆
drawoval(rect rect, paint paint) → void
//画文字
drawparagraph(paragraph paragraph, offset offset) → void
//画rect区域
drawrect(rect rect, paint paint) → void
//画阴影
drawshadow(path path, color color, double elevation, bool transparentoccluder) → void

绘制点

class mypoints extends custompainter{
  paint _paint = paint()
  ..color = colors.red
  ..strokewidth = 15;
  @override
  void paint(canvas canvas, size size) {
    // todo: implement paint
    var points =[
      offset(0, 0),
      offset(size.width/2, size.height/2),
      offset(size.width, size.height),
    ];
    canvas.drawpoints(pointmode.points, points, _paint);
  }
  @override
  bool shouldrepaint(covariant custompainter olddelegate) {
    // todo: implement shouldrepaint
    throw unimplementederror();
  }
}

运行效果:

pointmode3种模式

  • points:点
  • lines:将2个点绘制为线段,如果点的个数为奇数,最后一个点将会被忽略
  • polygon:将整个点绘制为一条线

绘制线 和路径

class mygraph extends custompainter{
  final paint _paint = paint()
    ..color = colors.red
    ..strokewidth = 15;
  final paint _paintpath = paint()
    ..color = colors.blue
    ..strokewidth = 5
  ..style = paintingstyle.fill;
  @override
  void paint(canvas canvas, size size) {
    // todo: implement paint
    //绘制线
    canvas.drawline(offset(0, 30),offset(size.width-30, size.height), _paint);
    //绘制路径
    var _path = path()
    ..moveto(0, 0)
    ..lineto(size.width, 0)
    ..lineto(size.width, size.height)
    ..close();
    canvas.drawpath(_path, _paintpath);
    //这里注意paint.style,还可以设置为paintingstyle.fill,
    //绘制圆形
    canvas.drawcircle(offset(size.width/2+50, size.height/2+50), 20, _paint);
    //绘制椭圆
    canvas.drawoval(rect.fromltrb(0, 0, size.width, size.height/2), _paint);
    //绘制弧
    canvas.drawarc(rect.fromltrb(0, 0, size.width, size.height), 0, pi/2, true, _paint);
    //绘制圆角矩形
    canvas.drawrrect(rrect.fromltrbr(0, 0, size.width, size.height, radius.circular(10)), _paint);
  }
  @override
  bool shouldrepaint(covariant custompainter olddelegate) {
    // todo: implement shouldrepaint
    throw unimplementederror();
  }
}

运行效果:

绘制五子棋

首先绘制背景,淡黄色,再绘制棋盘网格线,随后绘制黑白子,具体代码:

class custompaintroute extends statelesswidget {
  @override
  widget build(buildcontext context) {
    return center(
      child: custompaint(
        size: size(300, 300), //指定画布大小
        painter: mypainter(),
      ),
    );
  }
}
class mypainter extends custompainter {
  @override
  void paint(canvas canvas, size size) {
    double ewidth = size.width / 15;
    double eheight = size.height / 15;
    //画棋盘背景
    var paint = paint()
      ..isantialias = true
      ..style = paintingstyle.fill //填充
      ..color = color(0x77cdb175); //背景为纸黄色
    canvas.drawrect(offset.zero & size, paint);
    //画棋盘网格
    paint
      ..style = paintingstyle.stroke //线
      ..color = colors.black87
      ..strokewidth = 1.0;
    for (int i = 0; i <= 15; ++i) {
      double dy = eheight * i;
      canvas.drawline(offset(0, dy), offset(size.width, dy), paint);
    }
    for (int i = 0; i <= 15; ++i) {
      double dx = ewidth * i;
      canvas.drawline(offset(dx, 0), offset(dx, size.height), paint);
    }
    //画一个黑子
    paint
      ..style = paintingstyle.fill
      ..color = colors.black;
    canvas.drawcircle(
      offset(size.width / 2 - ewidth / 2, size.height / 2 - eheight / 2),
      min(ewidth / 2, eheight / 2) - 2,
      paint,
    );
    //画一个白子
    paint.color = colors.white;
    canvas.drawcircle(
      offset(size.width / 2 + ewidth / 2, size.height / 2 - eheight / 2),
      min(ewidth / 2, eheight / 2) - 2,
      paint,
    );
  }
  //在实际场景中正确利用此回调可以避免重绘开销,本示例我们简单的返回true
  @override
  bool shouldrepaint(custompainter olddelegate) => true;
}

运行效果:

绘制是比较昂贵的操作,所以我们在实现自绘控件时应该考虑到性能开销,下面是两条关于性能优化的建议:

  • 尽可能的利用好shouldrepaint返回值;在ui树重新build时,控件在绘制前都会先调用该方法以确定是否有必要重绘;假如我们绘制的ui不依赖外部状态,那么就应该始终返回false,因为外部状态改变导致重新build时不会影响我们的ui外观;如果绘制依赖外部状态,那么我们就应该在shouldrepaint中判断依赖的状态是否改变,如果已改变则应返回true来重绘,反之则应返回false不需要重绘。
  • 绘制尽可能多的分层;在上面五子棋的示例中,我们将棋盘和棋子的绘制放在了一起,这样会有一个问题:由于棋盘始终是不变的,用户每次落子时变的只是棋子,但是如果按照上面的代码来实现,每次绘制棋子时都要重新绘制一次棋盘,这是没必要的。优化的方法就是将棋盘单独抽为一个widget,并设置其shouldrepaint回调值为false,然后将棋盘widget作为背景。然后将棋子的绘制放到另一个widget中,这样落子时只需要绘制棋子。

总结

custompaint class提供了让用户自定义widget的能力,它暴露了一个canvas,可以通过这个canvas来绘制widget,custompaint会先调用painter绘制背景,然后再绘制child,最后调用foregroundpainter来绘制前景。

canvas--画布,真正的绘制是由canvas跟paint来完成的,画布提供了各种绘制的接口来绘制图形,除此以外画布还提供了平移、缩放、旋转等矩阵变换接口,画布都有固定大小跟形状,还可以使用画布提供的裁剪接口来裁剪画布的大小形状等等

paint---笔画,是用来设置在画布上面绘制图形时的一些笔画属性,如:颜色、线宽、绘制模式、抗锯齿等等.

自绘控件非常强大,理论上可以实现任何2d图像外观,想更深入的了解,可以找到其对应的renderobject对象,如text widget最终会通过renderparagraph对象来通过canvas实现文本绘制逻辑。了解了更底层的绘制逻辑,才能更好的在实际项目中灵活应用。

以上就是flutter custompaint自定义绘画示例详解的详细内容,更多关于flutter custompaint 绘画的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com