目标:绘制小猪佩奇
Flutter在自定义绘制上同Android类似,提供给你canvas和paint,剩下的就海阔凭鱼跃,天高任鸟飞了,给了你工具,能造出什么来就凭个人本事了。
这里为了学习与巩固flutter中的绘制相关API,于是使用其参照上图绘制一张小猪佩奇。
这里为了更有扩展性,让画布变大时绘制的内容不会变形,全图使用宽高比例绘制。
这张图相对比较简单,曲线部分全部使用贝塞尔曲线绘制,由于canvas 的API中最高支持3阶贝塞尔曲线,本图绘制正好也不需要使用更高阶的贝塞尔曲线。
先上一张最终效果器:

首先我们要自定义一个绘制佩奇的控件PeppaPaint,继承自CustomPainter,需要我们实现paint(Canvas canvas, Size size)bool shouldRepaint(CustomPainter oldDelegate)的方法:

class PeppaPaint extends CustomPainter{
}

Paint方法中即为我们真正绘制内容的区域,shouldRepaint则控制是否需要重新绘制界面,由于flutter中所有视图内容都是和数据状态绑定的,所有我们可以判断数据状态是否有改变来控制是否重绘,以减少性能损耗。
首先我们做一下准备工作,从size拿到画布的宽高,由于绘制过程中难免会有画笔粗细,圆的半径大小等相对绝对的数值,所有我们这边记一个标准,相当于我们在这个标准的尺寸下用多粗的画笔,多长的半径,在画板尺寸放大或者缩小时,对应的画笔粗细,半径尺寸也会有对应比例的变化。另外需要注意一下,下面绘制的所有基于宽高绘制的定位点都是本子自己大致估算而来大家不必纠结。这里我们以宽度为360作为标准尺寸,则有以下代码:

@override
void paint(Canvas canvas, Size size) {var width = size.width;var height = size.height;var k = width / 360; //对应标准尺寸下的比例尺
……
}

接下来开始真正会内容绘制,首先我们需要绘制一个背景:

var paint = Paint()..isAntiAlias = true..style = PaintingStyle.fill //填充..color = Color(0x77cdb175); //背景为纸黄色
canvas.drawRect(Offset.zero & size, paint);

接下来我们开始画鼻子,鼻子为一个圆孔和一个椭圆:

paint..style = PaintingStyle.stroke //线..color = Colors.pink[200]..strokeWidth = 3.0 * k;
canvas.rotate(-0.25);
//画鼻子
canvas.drawOval(Rect.fromLTRB(width * 0.7, height * 0.2, width * 0.8, height * 0.30),paint);
paint.style = PaintingStyle.fill; //填充
canvas.drawCircle(Offset(width * 0.725, height * 0.25), 5 * k, paint);
canvas.drawCircle(Offset(width * 0.775, height * 0.25), 5 * k, paint);

大家可能注意到上面有一句canvas.rotate(-0.25),这是因为鼻子的椭圆是有一定角度的,我们需要将画布旋转一定角度后作画。由于脸部轮廓和鼻子是连着的,所以我们不着急把画布旋转回来,否则旋转回来之后就找不到鼻子与脸部轮廓链接的点了。我们接着画脸部,脸部是由两个贝塞尔曲线构成,一条三阶贝塞尔连接到右下嘴角部分,一条二阶贝塞尔连接嘴角与鼻子:

//画脸部轮廓
var path = new Path();
paint.style = PaintingStyle.stroke; //
path.moveTo(width * 0.75, height * 0.2);
path.cubicTo(-width * 0.2, height * 0.05, width * 0.25, height * 0.63,width * 0.6, height * 0.35);
canvas.drawPath(path, paint);
path = new Path();
path.moveTo(width * 0.6, height * 0.35);
path.conicTo(width * 0.7, height * 0.33, width * 0.75, height * 0.3, 1);
canvas.drawPath(path, paint);
canvas.rotate(0.25);

画完脸部由于没有需要直接和鼻子与脸部的定位点连接的部位了(耳朵和身体不是和脸部的定位点连接的,脸部确是和鼻子的定位点连接的),所有可以将画布拜正了,后续都以这个角度绘制。
下面绘制耳朵,耳朵也是两条三阶贝塞尔曲线:

//画耳朵
path = new Path();
path.moveTo(width * 0.5, height * 0.1);
path.cubicTo(width * 0.42, height * 0.01, width * 0.58, height * 0.01,width * 0.55, height * 0.09);
canvas.drawPath(path, paint);
path = new Path();
path.moveTo(width * 0.41, height * 0.12);
path.cubicTo(width * 0.31, height * 0.035, width * 0.46, height * 0.035,width * 0.46, height * 0.105);
canvas.drawPath(path, paint);

下面绘制眼睛,嘴,和标志性的小圆脸蛋:

//画小酒窝
paint.style = PaintingStyle.fill; //填充
canvas.drawCircle(Offset(width * 0.40, height * 0.23), 18 * k, paint);
//画眼睛
paint.style = PaintingStyle.stroke; //线
canvas.drawCircle(Offset(width * 0.60, height * 0.13), 9 * k, paint);
canvas.drawCircle(Offset(width * 0.50, height * 0.15), 9 * k, paint);
//眼珠
paint.style = PaintingStyle.fill; //线
paint.color = Colors.black;
canvas.drawCircle(Offset(width * 0.59, height * 0.133), 4 * k, paint);
canvas.drawCircle(Offset(width * 0.49, height * 0.153), 4 * k, paint);
//画嘴
path = new Path();
paint.style = PaintingStyle.stroke; //线
paint.color = Colors.pink;
path.moveTo(width * 0.45, height * 0.28);
path.conicTo(width * 0.55, height * 0.33, width * 0.62, height * 0.25, 1);
canvas.drawPath(path, paint);

整个头部就算绘制完成了,接下来就是身体部分的绘制,身体部分要注意的就是底部曲线连接部分,我这边左右两边都使用了一个圆弧来连接,否则看起来会比较突兀,具体代码如下:

path = new Path();
paint.color = Colors.pink[200];
path.moveTo(width * 0.37, height * 0.327);
path.conicTo(width * 0.25, height * 0.42, width * 0.25, height * 0.6, 1);
canvas.drawPath(path, paint);
path = new Path();
paint.color = Colors.pink[200];
path.moveTo(width * 0.62, height * 0.325);
path.conicTo(width * 0.72, height * 0.42, width * 0.75, height * 0.6, 1);
canvas.drawPath(path, paint);
canvas.drawArc(Rect.fromCircle(center: Offset(width * 0.284, height * 0.6), radius: 12 * k),1.57,1.58,false,paint);canvas.drawArc(Rect.fromCircle(center: Offset(width * 0.7165, height * 0.6), radius: 12 * k),0,1.58,false,paint);
canvas.drawLine(Offset(width * 0.284, height * 0.6 + (12 * k)),Offset(width * 0.7165, height * 0.6 + (12 * k)), paint);

接下来绘制手部,左右手大同小异,主要是点位点估算与调试比较费事:

//画右手
path = new Path();
path.moveTo(width * 0.68, height * 0.4);
path.conicTo(width * 0.75, height * 0.44, width * 0.80, height * 0.48, 1);
canvas.drawPath(path, paint);
path = new Path();
path.moveTo(width * 0.78, height * 0.466);
path.conicTo(width * 0.80, height * 0.463, width * 0.81, height * 0.46, 1);
canvas.drawPath(path, paint);
path = new Path();
path.moveTo(width * 0.78, height * 0.466);
path.conicTo(width * 0.78, height * 0.47, width * 0.769, height * 0.486, 1);
canvas.drawPath(path, paint);
paint.style = PaintingStyle.fill;
canvas.drawCircle(Offset(width * 0.80, height * 0.48), 1.5 * k, paint);
canvas.drawCircle(Offset(width * 0.81, height * 0.46), 1.5 * k, paint);
canvas.drawCircle(Offset(width * 0.769, height * 0.486), 1.5 * k, paint);

接下来就是绘制小尾巴,小尾巴这里挺有意思的,由于实在没办法用一条曲线画出来,就只能两条曲线来凑了:

//画尾巴
paint.style = PaintingStyle.stroke;
path = new Path();
path.moveTo(width * 0.225, height * 0.568);
path.cubicTo(width * 0.18, height * 0.56, width * 0.21, height * 0.60,width * 0.25, height * 0.585);
canvas.drawPath(path, paint);
path = new Path();
path.moveTo(width * 0.225, height * 0.568);
path.cubicTo(width * 0.24, height * 0.57, width * 0.21, height * 0.60,width * 0.18, height * 0.585);
canvas.drawPath(path, paint);

脚的地方比较简单,两条曲线加两个椭圆:

//画脚
path = new Path();
path.moveTo(width * 0.42, height * 0.6 + (12 * k));
path.conicTo(width * 0.41, height * 0.65, width * 0.42, height * 0.69, 1);
canvas.drawPath(path, paint);
path = new Path();
path.moveTo(width * 0.58, height * 0.6 + (12 * k));
path.conicTo(width * 0.57, height * 0.65, width * 0.58, height * 0.69, 1);
canvas.drawPath(path, paint);
paint.color = Colors.teal[800];
paint.style = PaintingStyle.fill;
canvas.drawOval(Rect.fromLTRB(width * 0.41, height * 0.685, width * 0.45, height * 0.70),paint);
canvas.drawOval(Rect.fromLTRB(width * 0.57, height * 0.685, width * 0.61, height * 0.70),paint);

至此,咱们的佩奇已经画完了,和参考的图片对比一下,发现还是有几分相似的,哈哈。
最后我们要给佩奇画上签名文字了,这里我找了一下,发现flutter绘制文字还是比较麻烦的,不像Android里面直接drawtext()就搞定了。这里也没有导入字体,使用的默认的字体,具体代码如下:

//画文字ParagraphBuilder pb = ParagraphBuilder(ParagraphStyle(textAlign: TextAlign.center,fontWeight: FontWeight.w800,fontStyle: FontStyle.italic,fontSize: 35.0 * k,));pb.pushStyle(ui.TextStyle(color: Colors.green));pb.addText('peppapig');ParagraphConstraints pc = ParagraphConstraints(width: width);
//这里需要先layout, 后面才能获取到文字高度Paragraph paragraph = pb.build()..layout(pc);
//文字居中显示Offset offset = Offset(width / 2 - paragraph.width / 2, height * 0.71 + paragraph.height / 2);canvas.drawParagraph(paragraph, offset);

大功告成!接下来我们要检验我们绘制的图的兼容性了,在放大和缩小的情况下,看看咱们的图是否会变形,各个定位点是否会错位。在放大和缩小的时候我们需要注意一下,需要按照宽高比等比例的缩放,否则一定会导致画的图变形。所以还需要自己设置一个标准的宽高比,按照对应的宽高比设置缩放后的尺寸:

var width = 50.0;
var height;
var lastScale = 1.0;
@override
void initState() {super.initState();width = 50.0;height = getHeight(width);
}double getHeight(double width) {return 558.0 * width / 360;
}

在展示部分,我们引入一个手势缩放控件,来缩放咱们画的图,看看是否会变形:

Scaffold(appBar: AppBar(title: Text(widget.title),),body: Center(child: GestureDetector(onDoubleTap: () {setState(() {width = 1.2 * width;height = getHeight(width);});},onScaleStart: (s) {lastScale = 1.0;},onScaleUpdate: (scale) {setState(() {width = (scale.scale / lastScale) * width;height = getHeight(width);lastScale = scale.scale;});},child: CustomPaint(size: Size(width, height),painter: PeppaPaint(),),),),
);

需要注意的是,在绘制控件中,paint()方法中的size为实际的画布尺寸,与CustomPaint设置的尺寸不一定相同,因为可能屏幕没有那么长或者那么宽,所以为了让绘制的宽高比保持正常,需要在paint方法中,重新设置宽高,当宽高比与标准宽高比大时,表示图片过宽,应该以高为基准,按照标准宽高比重新计算宽度,然后按照重新计算的宽高进行绘制。反之亦然。这里为了方便,统一以宽度为基准,重新计算高度,得到的绘制结果不会变形,但也可能会超出屏幕。

void paint(Canvas canvas, Size size) {var width = size.width;var height = getHeight(size.width);
……
}

本来本文到此就结束了,但是……总觉得画的文字的这个字体看着不太协调,顺便识别了一下原图的字体。

死活下不来这个字体,某某网站该字体需要VIP才能下载…….

只能上Google字体库找找类似的字体了。
https://fonts.google.com/
下载字体后需在pubspec.yaml配置文件中配置我们的字体:

fonts:- family: Berkshire Swashfonts:#https://fonts.googleapis.com/css?family=Berkshire+Swash- asset: fonts/BerkshireSwash-Regular.ttf

需要把字体文件放到配置的对应文件夹下。
对应的使用就非常简单了,只需要指明需要使用的字体库:、

加载该字体后效果图:

最后奉上源码地址:https://github.com/1126174422/flutter-peppapig

Flutter-绘制小猪佩奇相关推荐

  1. python turtle画熊-Python使用turtle库绘制小猪佩奇(实例代码)

    turtle(海龟)是Python重要的标准库之一,它能够进行基本的图形绘制.turtle图形绘制的概念诞生于1969年,成功应用于LOGO编程语言. turtle库绘制图形有一个基本框架:一个小海龟 ...

  2. 前端绘制小猪佩奇(CSS)

    绘制小猪佩奇的方法有很多.从前端的角度,可以在 canvas 中绘制,或者直接使用 css3 绘制.这里就练习最基本的CSS绘制小猪佩奇. <!DOCTYPE html> <html ...

  3. 绘制小猪佩奇的python代码

    一个很有意思的代码,可以绘制出小猪佩奇,代码来源 github.com """ 绘制小猪佩奇 """ from turtle import ...

  4. python绘制小猪佩奇程序设计大作业_代码绘制一只小猪佩奇---python篇

    今天教大家用python的pillow包来绘制小猪佩奇,python的安装就不用多说了,直接上代码吧 0.首先当然是安装pillow包啦. 关于pillow库的安装有几种方式 最常使用的是pip安装 ...

  5. python turtle 绘图小猪佩奇,Python使用turtle库绘制小猪佩奇(实例代码)

    这篇文章主要介绍了Python使用turtle库绘制小猪佩奇,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下 turtle(海龟)是Python重要的标准库之一,它 ...

  6. 趣味python | 一步一步绘制小猪佩奇

    微信公众号:Python 集中营 简单的事情重复做,重复的事情坚持做,坚持的事情用心做; 你的肯定是我坚持的动力,如果这篇文章对你有帮助,点个关注吧! 定义全局属性 1# 导入turtle绘图库23i ...

  7. python设置笔大小的函数_小朋友们,你试过用Python语言绘制小猪佩奇吗?来完成你的第一个创作吧!...

    在上一章中,我们用海龟绘图绘制了机器猫的卡通图像.在本章中,我们介绍如何用海龟绘图来绘制小朋友们喜欢的另一个卡通形象--小猪佩奇. 1 程序分析 我们先来看一下小猪佩奇的样子,如图1所示. 图1 观察 ...

  8. 用python画小猪佩奇的编码_如何用python绘制小猪佩奇-python绘图教程图文讲解

    原标题:如何用python绘制小猪佩奇-python绘图教程图文讲解 如何运用python来绘制小猪佩奇呢?通过几道简单的python代码即可让你绘制出小猪佩奇,话不多说,直接上代码. 用python ...

  9. 小朋友们,你试过用Python绘制小猪佩奇吗?来开始创作吧

    在上一章中,我们用海龟绘图绘制了机器猫的卡通图像.在本章中,我们介绍如何用海龟绘图来绘制小朋友们喜欢的另一个卡通形象--小猪佩奇. 1 程序分析 我们先来看一下小猪佩奇的样子,如图1所示. 图1 观察 ...

  10. Python|Python简介|安装Python解释器|运行|开发工具|Python之禅|turtle绘制五星红旗|绘制方块|绘制小猪佩奇|语言100课:学习(1)

    文章目录 源项目地址 初识Python Python简介 Python的历史 Python的优缺点 Python的应用领域 安装Python解释器 运行Python程序 确认Python的版本 编写P ...

最新文章

  1. Python 封装MySQL类
  2. Javascript 调用XML制作连动下拉框
  3. 【机器学习实战】Machine Learning in Action 代码 视频 项目案例
  4. oxyen eclipse 启动 报错 se启动提示javaw.exe in your current PATH、No java virtual machine
  5. 深圳大学计算机课程表2018,深圳大学国际交流学院2017—2018学年第1学期本科生课程表...
  6. 漫画:如何给女朋友解释什么是系统可用性?| 技术头条
  7. PHP分类输出代码,PHP无限分类代码,支持数组格式化、直接输出菜单两种方式_php技巧...
  8. C语言小案例_OA大典故障案例摘录【第1390篇】夏普mxm2608n 黑白复印机漏粉
  9. 实现透明背景但背景上元素不透明
  10. 【cuda】——npp/cuda图像预处理resize+norm对比
  11. 【问题记录】git报错:[remote rejected] (pre-receive hook declined)
  12. k折交叉验证优缺点_为什么要用交叉验证
  13. unable to translate bytes at index from specified code page to unicode
  14. 10.2国庆作业(PWM实验)
  15. JavaScript实现H5游戏断线自动重连的技术
  16. erdas裁剪影像_erdas切割图像步骤
  17. Android开发MVP模式(解决了View和Model的耦合)
  18. CREO2——解决CREO生成的二维图drw转换成CAD的dwg格式尺寸失真问题
  19. 数据要素的性质、定价及配置
  20. win10无限重启服务器,win10 event viewer 报错10016 系统会重启

热门文章

  1. phone开发基础教程
  2. 时间复杂度、空间复杂度的分析--王争数据结构与算法学习笔记
  3. zencart购物车修改调用显示购物车图片,修改边栏购物车模版
  4. 鸿蒙3.0又多了一种开发方式Ets
  5. 《使用HTML语言和CSS开发商业站点》学习笔记
  6. 牛客15499 jxc的军训(快速幂)
  7. 【转】Jabber即时通信系统服务整体框架概述
  8. win7 64bit下硬盘内容显示该文件夹为空,但是显示有7G的占用,解释
  9. 听音识故障,人工智能“诊断”机器新形式
  10. java 特殊字符_Java正则表达式特殊字符及其处理以及正则表达式详解