29、Flutter之CustomPaint 绘画

简介


flutter里面有着万物皆是widget这句话,CustomPaint是Flutter中用于自由绘制的一个widget。它与android原生的绘制规则基本一致,以当前Canves(画布)的左上角为原点进行绘制。

构造方法:

class CustomPaint extends SingleChildRenderObjectWidget {
  /// Creates a widget that delegates its painting.
  const CustomPaint({
    Key? key,
    this.painter,
    this.foregroundPainter,
    this.size = Size.zero,
    this.isComplex = false,
    this.willChange = false,
    Widget? child,
  }) : assert(size != null),
       assert(isComplex != null),
       assert(willChange != null),
       assert(painter != null || foregroundPainter != null || (!isComplex && !willChange)),
       super(key: key, child: child);

  /// The painter that paints before the children.
  final CustomPainter? painter;

  /// The painter that paints after the children.
  final CustomPainter? foregroundPainter;

  /// The size that this [CustomPaint] should aim for, given the layout
  /// constraints, if there is no child.
  ///
  /// Defaults to [Size.zero].
  ///
  /// If there's a child, this is ignored, and the size of the child is used
  /// instead.
  final Size size;

  /// Whether the painting is complex enough to benefit from caching.
  ///
  /// The compositor contains a raster cache that holds bitmaps of layers in
  /// order to avoid the cost of repeatedly rendering those layers on each
  /// frame. If this flag is not set, then the compositor will apply its own
  /// heuristics to decide whether the this layer is complex enough to benefit
  /// from caching.
  ///
  /// This flag can't be set to true if both [painter] and [foregroundPainter]
  /// are null because this flag will be ignored in such case.
  final bool isComplex;

  /// Whether the raster cache should be told that this painting is likely
  /// to change in the next frame.
  ///
  /// This flag can't be set to true if both [painter] and [foregroundPainter]
  /// are null because this flag will be ignored in such case.
  final bool willChange;

基本用法


 这个页面除去AppBar,下面整个都是CustomPaint的范围。
 要实现绘制,需传入painter参数,定义自己的painter继承自CustomPainter,这里面得必须重写paint和shouldRepaint。但实现绘制仅需实现paint方法
 paint的两个参数 canvas 和 size ,分别是画布和CustomPaint的子widget的宽高尺寸。
 接着仅需cavas的 draw操作就可以了。
 

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(),
      ),
    );
  }
}

class MyPainer extends CustomPainter{
  late Paint _paint;
  @override
  void paint(Canvas canvas, Size size) {
    _paint = Paint();
    _paint.color = Colors.red;
    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();
  }

}

其他重要属性


    CustomPaint的构造函数
    child: 红色区域,传入一个子widget,这个widget图层会在painter在上,在foregroundPainter之下。
    painter:蓝色区域。
    foregroundPainter:绿色区域,它与painter都是CustomPainter类型的。通过名字大概也就知道了,它会在painter的上层,也就是说在同样的位置去绘制,foregroundPainter 会覆盖painter。

 完整代码:

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();
  }

}

isComplex: 绘制是否足够复杂需要缓存,作用很明显就是为了避免在每个图层上重复渲染。
isChange:下一帧中绘制是否更改。

size:默认值是size.zero(宽高都是0),可以理解为这个就是child的宽高,但是有child后以,这个参数被child的尺寸代替。这里传入的size在pain方法里可以获取到。源码里面用这句话解释“The size that this [CustomPaint] should aim for, given the layout”,但就我测试情况看,无论如何更改size,绘制结果不受任何影响。
 

绘制点


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();
  }
  
}

 

PointMode有3种模式:

  • 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();
  }

}

 

通过CustomPaint 画出了一个蓝天跟太阳出来

class MySunPainer extends CustomPainter{
  final Paint _paint = Paint();
  @override
  void paint(Canvas canvas, Size size) {
    var rect = Offset.zero & size;
    var gradient = RadialGradient(
      center: const Alignment(0.7, -0.6),
      radius: 0.2,
      colors: [const Color(0xFFFFFF00), const Color(0xFF0099FF)],
      stops: [0.4, 1.0],
    );
    canvas.drawRect(
      rect,
      Paint()..shader = gradient.createShader(rect),
    );

  }

  SemanticsBuilderCallback get semanticsBuilder{
    return (Size size) {
      // Annotate a rectangle containing the picture of the sun
      // with the label "Sun". When text to speech feature is enabled on the
      // device, a user will be able to locate the sun on this picture by
      // touch.
      var rect = Offset.zero & size;
      var width = size.shortestSide * 0.4;
      rect = const Alignment(0.8, -0.9).inscribe(Size(width, width), rect);
      return [
        CustomPainterSemantics(
          rect: rect,
          properties: SemanticsProperties(
            label: 'Sun',
            textDirection: TextDirection.ltr,
          ),
        ),
      ];
    };
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    // throw UnimplementedError();
    return this != oldDelegate;
  }
  
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风雨「83」

你的鼓励将是我创作最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值