阅读 178

Flutter自定义绘制基础(flutter绘制原理)

简介

通过绘制基本的线、路径、图形、曲线等。同时结合画布的旋转实现一个模拟时钟的组件。学习Flutter的基本绘制。 屏幕录制2021-11-26 18.gif

Paint和Canvas

在画布上绘制前,需要有一支画笔:

final Paint mPaint = Paint()   ..color = Colors.orange//设置画笔颜色   ..style = PaintingStyle.stroke//设置画笔类型   ..isAntiAlias = true//开启抗锯齿   ..strokeWidth = 6;//设置画笔线宽 复制代码

通过继承CustomPainter ,然后重写paint 方法获得canvas 画布对象。之后便可以在画布上进行绘制。

class Custom extends CustomPainter{   @override   void paint(Canvas canvas, Size size) {     ///这里就是绘制的地方     ///可以直接在canvas上绘制   } @override   bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } 复制代码

Paint和Canvas都提供了很多属性和方法,Canvas的方法在稍后分章节展示、这里简要讲解Paint的几个关键属性:

Paint属性数据类型简介
colorColor画笔的颜色
strokeWidthdouble画笔的线宽
stylePaintingStyle画笔的类型(fill填充和stroke线条)
strokeCapStrokeCap线头类型 (butt、round和square)

这里展示一下不同strokeCap的效果:

1637982383883.jpg

上图中依次是:round、square和butt。可以看到他们在线头处的不同效果。

###基础图形绘制

点、线和路径的绘制

可以通过drawPoints(PointMode pointMode, List<Offset> points, Paint paint)批量绘制点,该方法接受三个参数:点的模式,点位和画笔。点的模式分三种:

  1. points:点;

  2. lines:线;

  3. polygon:多边形;

分别对应如下效果:

pointslinespolygon
1637988681849.jpg1637988673707.jpg1637988687184.jpg

另外还有一种绘制点的方法:drawRawPoints(PointMode pointMode, Float32List points, Paint paint),两者的不同之处在于传递坐标的方式不同 Canvas提供两个方法drawLine(Offset p1, Offset p2, Paint paint)drawPath(Path path, Paint paint)分别用来绘制直线和路径。

其中drawLine接受三个参数:起始点位置、终点位置和画笔。 上文中strokeCap的效果展示就是通过基础的画线实现的,它的代码如下:

///划线 void line(Canvas canvas) {   canvas.drawLine(       Offset(-60, -12), Offset(60, -12), mPaint..strokeCap = StrokeCap.round);   canvas.drawLine(       Offset(-60, 0), Offset(60, 0), mPaint..strokeCap = StrokeCap.square);   canvas.drawLine(       Offset(-60, 12), Offset(60, 12), mPaint..strokeCap = StrokeCap.butt); } 复制代码

drawPath接受两个参数:路径path和画笔。这里先讲一下路径Path的三类移动策略:

  1. moveTo和relativeMoveTo:移动到某个位置,相当于落笔的位置

2.lineTo和relativeLineTo:画到某一位置,相当于画笔移动到的位置

含有relative的移动,是相对移动。传入的x,y是相对当前坐标的偏移量。而不含relative则是画布的绝对坐标。

///画路径 void pathLine(Canvas canvas) {   var path = Path()     ..moveTo(-100.0, -100)     ..lineTo(0.0, 0.0)     ..lineTo(100.0, -100);   canvas.drawPath(path, mPaint);   var pathRelative = Path()     ..moveTo(-100.0, 100)     ..relativeLineTo(100.0, -100.0)     ..relativeLineTo(100.0, 100.0);   canvas.drawPath(pathRelative, mPaint..color = Colors.deepPurple); } 复制代码

上面的代码执行后效果如下: 1637995989641.jpg

Path不仅可以画直线路径,还可以绘制二阶贝塞尔曲线:

///贝塞尔曲线 void bezierQuadratic(Canvas canvas, Size size) {   var first = Offset(size.width / -4, 0);   var second = Offset(0, -200);   var third = Offset(size.width / 4, 0);   Path path = Path()     ..moveTo(first.dx, first.dy)     ..quadraticBezierTo(second.dx, second.dy, third.dx, third.dy);   canvas.drawPath(path, mPaint);   canvas.drawPath(       Path()         ..moveTo(first.dx, first.dy)         ..lineTo(second.dx, second.dy)         ..lineTo(third.dx, third.dy),       mPaint..strokeWidth = 1); } 复制代码

效果如图:

image.png

三阶贝塞尔曲线:

///贝塞尔曲线 void bezierCubic(Canvas canvas, Size size) {   var first = Offset(size.width / -4, 0);   var second = Offset(size.width / -4, -200);   var third = Offset(size.width / 4, -200);   var four = Offset(size.width / 4, 0);   Path path = Path()     ..moveTo(first.dx, first.dy)     ..cubicTo(second.dx, second.dy, third.dx, third.dy, four.dx, four.dy);   canvas.drawPath(path, mPaint);   canvas.drawPath(       Path()         ..moveTo(first.dx, first.dy)         ..lineTo(second.dx, second.dy)         ..lineTo(third.dx, third.dy)         ..lineTo(four.dx, four.dy),       mPaint..strokeWidth = 1); } 复制代码

image.png

二阶和三阶贝塞尔曲线却别在于前置需要三个点确定位置,而后者需要四个点。但是两者接受的点位分别是2和3个,这是因为它们都以Path目前所处的点位为第一个点。

绘制图形

Canvas提供基本的集合图形绘制,包括圆矩形以及扇形等。

绘制圆:

///画圆形 void shapeCircle(Canvas canvas) {   canvas.drawCircle(Offset(-60, 0), 60, mPaint);   canvas.drawCircle(Offset(60, 0), 60, mPaint..style = PaintingStyle.fill); } 复制代码

方法很简单,只需要传入远点坐标和半径即可。效果如图: image.png

绘制矩形

///画矩形 void shapeRect(Canvas canvas) {  ///四条边的位置,以及圆角弧度   var rrect = RRect.fromLTRBR(-60, -60, 60, 60, Radius.circular(5));   canvas.drawRRect(rrect, mPaint); } 复制代码

效果如图:

image.png

矩形稍微负责一点,需要确定矩形方位。RRect提供了数种构造方法用来确定矩形方位,上图中通过矩形四条边的位置确定需要绘制的区域。

绘制扇形

 ///画圆弧 void pathArc(Canvas canvas) {   var rect = Rect.fromPoints(Offset(-120, -120), Offset(120, 120));   canvas.drawArc(rect, 0, pi, false, mPaint);   canvas.drawArc(       rect, pi / -3, pi / -2, true, mPaint..color = Colors.deepPurple); } 复制代码

效果如图:

image.png

扇形的绘制也需要先确定方位,然后需要确定起始角度和结束角度。

绘制阴影

///绘制阴影 void shadow(Canvas canvas) {   var pathRelative = Path()     ..moveTo(-100.0, 100)     ..relativeLineTo(100.0, -100.0)     ..relativeLineTo(100.0, 100.0);   canvas.drawShadow(pathRelative, Colors.orange, 3, false); } 复制代码

效果如图:

image.png

画布的变换

画布的变换包含平移、旋转和缩放三种。这里主要讲一下常用的平移的旋转。 translate:将画布移动到指定位置 rotate:将画布旋转一定的角度 注意。在变换之后,画布的坐标和角度都会变化。如果想要回复,则需要在变换前调用save方法,之后使用restore复原。 我们直接使用时钟的绘制来展示旋转的效果:

///绘制时钟 void canvasRotateTranslate(Canvas canvas) {   ///画表圈   canvas.drawCircle(Offset(0, 0), 122, mPaint..style = PaintingStyle.stroke);   ///绘制刻度   ///将表盘六十等分   for (var i = 1; i <= 60; i++) {     ///旋转画布     canvas.rotate(pi / 30);     ///每五个小刻度绘制一个大刻度     if (i % 5 == 0) {       canvas.drawLine(Offset(0, -120), Offset(0, i % 3 == 0 ? -105 : -108),           mPaint..strokeWidth = i % 3 == 0 ? 6 : 4);     } else {       canvas.drawLine(           Offset(0, -120), Offset(0, -115), mPaint..strokeWidth = 3);     }   }   ///绘制时刻   TextPainter textPainter = new TextPainter(       textAlign: TextAlign.left, textDirection: TextDirection.ltr);   for (var i = 0; i < 12; i++) {     canvas.save();     ///移动画布,使得时刻刚好处在刻度旁边     canvas.translate(0, -95);     ///画布旋转一定角度,使得时刻数组竖直显示     canvas.rotate(-pi / 6 * i);     ///绘制文字     textPainter.text = TextSpan(         style: new TextStyle(color: Colors.deepOrange, fontSize: 22),         text: "${i == 0 ? 12 : i}");     textPainter.layout();     textPainter.paint(         canvas, Offset(-textPainter.width / 2, -textPainter.height / 2));     canvas.restore();     ///旋转角度,使时刻处在正确的位置     canvas.rotate(pi / 6);   }   var hours = DateTime.now().hour % 12;   var minutes = DateTime.now().minute;   var seconds = DateTime.now().second;   ///绘制时分秒针   canvas.save();   ///计算时针位置需要旋转的角度   canvas.rotate(       (hours * pi / 6) + (pi / 6) * minutes / 60 + (pi / 6) * seconds / 3600);   canvas.drawLine(Offset.zero, Offset(0, -40), mPaint..strokeWidth = 3);   canvas.restore();   canvas.save();   ///计算分针位置需要旋转的角度   canvas.rotate(((minutes * pi / 30) + seconds / 60 * pi / 30));   canvas.drawLine(Offset.zero, Offset(0, -60), mPaint..strokeWidth = 3);   canvas.restore();   canvas.save();   ///计算秒针位置需要旋转的角度   canvas.rotate((seconds * pi / 30));   canvas.drawLine(       Offset.zero,       Offset(0, -80),       mPaint         ..strokeWidth = 2         ..color = Colors.red);   canvas.restore();   canvas.drawCircle(Offset(0, 0), 5, mPaint..style = PaintingStyle.fill); } 复制代码

效果如图:

屏幕录制2021-11-26 18.gif

最后

贝塞尔曲线实现波浪线效果:

      void wave(Canvas canvas, Size size) {   canvas.save();   var waveWidth = size.width / 8;   canvas.translate(-size.width / 2 * 3, 0);   Path path = Path()..moveTo(0, 0);   ///确定曲线路径   for (var i = 1, j = -1; i < 16; i += 2) {     path.quadraticBezierTo(size.width * value + waveWidth * i, j * waveHeight,         size.width * value + waveWidth * (i + 1), 0);     j = -j;   }   path..lineTo(waveWidth * 16, 60)..lineTo(0, 60)..lineTo(0, 0);   canvas.drawPath(       path,       mPaint         ..style = PaintingStyle.fill         ..color = Colors.orange);   canvas.restore(); } 复制代码

 class MyApp extends StatelessWidget {   // This widget is the root of your application.   @override   Widget build(BuildContext context) {     return MaterialApp(       title: 'Flutter Draw Demo',       theme: ThemeData(         primarySwatch: Colors.blue,       ),       home: MyHomePage(),     );   } } class MyHomePage extends StatefulWidget {   MyHomePage({Key key}) : super(key: key);   @override   _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {   Timer timer;   int value = 0;   @override   void initState() {     super.initState();     WidgetsBinding.instance.addPostFrameCallback((timeStamp) {      timer = Timer.periodic(Duration(milliseconds: 100), (timer) {        setState(() {          value++;          if(value >=10){            value = 0;          }        });      });    });   }   @override   void dispose() {     timer.cancel();     super.dispose();   }   @override   Widget build(BuildContext context) {     return Scaffold(       appBar: new AppBar(         title: new Text('$hours:$minutes:$seconds:${DateTime.now().millisecond}'),       ),       body: CustomPaint(         painter: KarlPainter(value*0.1),         size: MediaQuery.of(context).size,       ),     );   } } 复制代码

效果如图:

屏幕录制2021-11-27 15.gif


作者:白瑞德
链接:https://juejin.cn/post/7035156816879157284

 伪原创工具 SEO网站优化  https://www.237it.com/ 


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐