Flutter CustomPaint自定义绘画示例详解

目录
  • 正文
  • CustomPaint 介绍
    • 绘制点
  • PointMode3种模式
    • 绘制线 和路径
    • 绘制五子棋
  • 总结

正文

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)

相关推荐

  • 用Flutter做桌上弹球(绘图(Canvas&CustomPaint)API)

    本文是Flutter中Canvas和CustomPaint API的使用实例. 首先看一下我们要实现的效果: 结合动图演示,列出最终目标如下: 在程序运行后,显示一个小球: 每次程序启动后,小球的样式均发生随机性变化,体现在大小.颜色和位置三点: 小球运行的规律参考桌球或三维弹球游戏: 单击屏幕,小球变色: 双击屏幕,小球暂停/恢复运动: 长按屏幕,小球开始/停止自动变色. 运用的主要技术点:Canvas和CustomPaint API. 运行平台:Android.iOS 源码地址: Githu

  • Android Flutter利用CustomPaint绘制基本图形详解

    目录 绘制矩形 绘制圆形 绘制椭圆 绘制任意形状 绘制弧形 总结 上一篇我们介绍了 CustomPaint 的基本概念和使用,可以看到 CustomPaint 其实和 前端的 Canvas基本上是一样的,实际上前端 Canvas 支持的绘制方法 CustomPaint 都支持,毕竟 CustomPaint 其实也是基于 Canvas 实现的.本篇我们来介绍 CustomPaint 基本图形的绘制. 绘制矩形 绘制矩形比较简单,方法定义如下: void drawRect(Rect rect, Pa

  • Flutter绘制3.4边形及多边形渐变动画实现示例

    目录 正文 绘制3.4边形 整数边形的绘制 分数边形的绘制 具体代码 效果改进1 效果改进2 正文 项目被优化了,人也跟着被优化了,正好趁这一个月整理整理关于flutter的一些东西. 绘制3.4边形 先看一下效果图: 起因是上上上上上个月浏览flutter的canvas相关内容时,点进去一个网站,看到一个让我眼前一亮的动效: 作者用的代码是swift的,我没细看,不过他文章里的一句话让我醍醐灌顶: That is, we want the shape be asked to draw mult

  • Flutter绘图组件之CustomPaint使用详解

    目录 简介 CustomPaint介绍 CustomPainter示例 总结 简介 在有些场景中,我们会需要绘制一些高度定制化的组件,比如 UI 设计师给我们出了个难题 —— 弄一个奇形怪状的边框.看在 UI 设计师是一个漂亮小姐姐的份上,又不好意思说这个做不了(那样也很没面子).这个时候我们就不能直接使用 Flutter 自带的那些组件了,而是需要手动绘制组件,那就会需要用到 CuntomPaint 组件.CustomPaint 组件和前端的 Canvas差不多,允许我们在一个画布上绘制各种元

  • Flutter CustomPaint绘制widget使用示例

    目录 CustomPaint 介绍 使用 CustomPaint size 的大小. isComplex willChange foregroundPainter 动画 CustomPaint 介绍 Flutter CustomPaint 提供了一个 canvas,可以在绘制阶段在上面进行绘制内容. 需要绘制时,CustomPaint 首先要求它的 painter 在当前画布上绘画,然后它绘画它的 child,在绘画完它的 child 之后,要求他的 foregroundPainter 绘画.

  • Flutter 绘制风车实现示例详解

    目录 前言展示 1. 风车 1 的绘制 2. 风车 2 的绘制 3. 旋转动画的处理 4. 旋转动画的圈数 前言展示 最近源码看得比较多,本文来画点东西调节下心情,本绘制已收录于 FlutterUnit 的绘制集录,本文源码可参见[windmill.dart] .绘制内容非常简单,如下所示,两个样式的小风车:通过这两个小例子,可以学到: 路径的使用 画板的旋转变换 动画曲线与 Tween 的使用 风车1 风车2 1. 风车 1 的绘制 第一个风车非常简单,由四个 半圆 组成,每个部分直接的关系是

  • Flutter CustomPaint自定义绘画示例详解

    目录 正文 CustomPaint 介绍 绘制点 PointMode3种模式 绘制线 和路径 绘制五子棋 总结 正文 CustomPaint是Flutter中用于自由绘制的一个widget,它与android原生的绘制规则基本一致,以当前Canves(画布)的左上角为原点进行绘制.在有些场景中,我们会需要绘制一些高度定制化的组件,比如 UI 设计师给我们出了个难题 —— 弄一个奇形怪状的边框.这个时候我们就不能直接使用 Flutter 自带的那些组件了,而是需要手动绘制组件,那就会需要用到 Cu

  • 封装flutter状态管理工具示例详解

    目录 引言 RxBinder 代码实现 Demo 完美运行 引言 关于 Flutter 状态管理,公司项目使用的是Bloc方案.Bloc 其实本质上是 provider 的封装扩展库,整体通过 InheritedWidget .Notifier 外加 Stream中转实现状态变更通知. 关于 Bloc 实现原理,有兴趣的同学可以观看这篇文章 Bloc原理解析 RxBinder 撇开Bloc内部实现策略,小轰尝试基于数据驱动模型,自定义一套状态管理工具.构思如下: 主要成员如下: RxBinder

  • 微前端之Web组件自定义元素示例详解

    目录 我们知道的 Web组件使用 名称规范 组件传参数并可以写模板包括js和css Shadow Dom 影子节点 类中的构造函数和钩子函数 getter/setter属性和属性反射 扩展原生 HTML 我们知道的 第一:我们熟知的HTML标签有 a, p, div, section, ul, li, h2, article, head, body, strong, video, audio 等等 第二:我们知道,a标签是链接,p标签是段落,div是块级,h2是字体,strong 是粗体,vid

  • flutter text组件使用示例详解

    目录 正文 Text组件 Text组件构造器上的主要属性 正文 flutter组件的实现参考了react的设计理念,界面上所有的内容都是由组件构成,同时也有状态组件和无状态组件之分,这里简单介绍最基本的组件. 在组件代码的书写方式上,web端开发的样式主要有由css进行控制,而客户端开发根据使用的技术栈不同,写法也稍微有些不同:ReactNative的写法和web比较类似,但是ReactNative是使用StyleSheet.create()方法创建样式对象,以内联的方式进行书写. import

  • fastjson序列化时间自定义格式示例详解

    目录 Java8 的日期相关 API 首先建一个项目添加依赖 配置类中注入 Spriing 容器 写个接口做下测试 Java8 的日期相关 API Java8 的日期相关 API用起来是真香,但免不了遇到在用旧版 1.0 API 的情况.这不,跟另一个部门做对接,人家说你发过来的时间怎么带个 T,我这边没法解析...我回头就是一句xxx,情绪发泄完该做的事咱也得做不是,下面就看看怎么处理这个问题. 首先建一个项目添加依赖 <dependencies> <dependency> &l

  • Flutter Drawer抽屉菜单示例详解

    本文实例为大家分享了Flutter Drawer抽屉菜单示例代码,供大家参考,具体内容如下 一.Flutter Drawer组件简介 1.源码查看 const Drawer({     Key? key,     this.elevation = 16.0, //阴影效果大小     this.child, //内容元素     this.semanticLabel, //关闭/打开抽屉时的通知信息   })  二.抽屉菜单示例 1.菜单项,使用 ListTile 实现 Expanded(  

  • Android 自定义ListView示例详解

    本文讲实现一个自定义列表的Android程序,程序将实现一个使用自定义的适配器(Adapter)绑定 数据,通过contextView.setTag绑定数据有按钮的ListView. 系统显示列表(ListView)时,首先会实例化一个适配器,本文将实例化一个自定义的适配器.实现 自定义适配器,必须手动映射数据,这时就需要重写getView()方法,系统在绘制列表的每一行的时候 将调用此方法. ListView在开始绘制的时候,系统自动调用getCount()函数,根据函数返回值得到ListVi

  • Flutter实现自定义下拉选择框的示例详解

    在一些列表页面中,我们经常会有上方筛选项的的需求,点击出现一个下拉菜单,多选.单选.列表选等,而在Flutter中,并没有现成的这样的组件,找第三方的扩展有时候又会受到一定限制,所以最好我们可以自己做一个,这样即使扩展我们也会得心应手. 先看效果图: 关键点:弹出.收回动画.状态改变.选项联动 思路: 我们可以看到一个完整的下拉框有头部和具体的下拉选项两部分组成,头部又和下拉组进行了联动, 把头部当做1个数组,下方选项作为1个数组,两个数组数量一致之间形成一个完整的下拉选择框可以更好的控制联动效

随机推荐