以完成下面git中的效果为引导,来讲CustomPaint的使用。

首要创立画布

class RecordCustomPaint extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size){}
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

然后引用

  return CustomPaint(
    painter: RecordCustomPaint(),
    size: widget.size,
  );
}

接下来要做的便是开端创立画笔,以及设置画笔的特点。

特点名 类型 默许值 阐明
style PaintingStyle .fill .stroke 画笔类型
isAntiAlias bool true canvas上的图片和线条是否抗锯齿
color Color 0xFF000000 画笔色彩
strokeWidth double 0.0 画笔宽度
strokeCap StrokeCap StrokeCap.butt 笔头类型
strokeJoin StrokeJoin StrokeJoin.miter 详细区别看StrokeJoin枚举的介绍里边有gif解析
strokeMiterLimit dobule 4.0 斜接约束

创立画笔之前需求做需求分析。

项目有两段动画,首要是榜首段动画外圈变宽。然后是第二段动画,表里圈的进展条开端。

创立动画

late AnimationController _animationController2;
  late AnimationController _animationController3;
  int countdownSeconds = 15;
  @override
  void initState() {
// TODO: implement initState
    super.initState();
    _animationController2 =
    AnimationController(duration: Duration(milliseconds: 100), vsync: this)
      ..addListener(() {
        setState(() {});
      })
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          //按钮过渡动画完成后启动录制视频的进展条动画
          _animationController3.forward();
          widget.onStart();
        }
      });
    //第二个操控器
    _animationController3 =
    AnimationController(duration: Duration(seconds: widget.seconds), vsync: this)
      ..addListener(() {
        setState(() {});
        print("_animationController3.value -- ${_animationController3.value}");
        if (_animationController3.value == 1){
          _animationController2.reverse();
          _animationController3.value = 0;
          _animationController3.stop();
          widget.onEnd();
        }
      });
  }

逻辑便是榜首段动画结束之后敞开第二段动画。

然后把这两段动画的进展传递进RecordCustomPaint类里,并在内部进行画笔的初始化

动画中一共有内圈/内圈进展条 外圈/外圈进展条 以及表里圈之前的灰色区域(突变色,gif图中显示不太明显)五个元素。所以需求五只画笔。

Flutter CustomPaint的使用。

接下来的代码均在 RecordCustomPaint 类中完成

特点

final double firstProgress; //榜首段动画操控值,值规模[0,1]
final double secondProgress; //第二段动画操控值,值规模[0,1]
//主按钮的色彩
final Color buttonColor = Colors.red;
final Color mainColor = kColor(223,104,104, 1);
//进展条相关参数
final double progressWidth = kFit(10); //进展条 宽度
final Color progressColor = Colors.white; //kColor(223,104,104, 1); //进展条色彩
final double back90 = deg2Rad(-90.0).toDouble(); //往前推90度 从12点钟方向开端
//主按钮画笔
late Paint mainBtnPaint;
//画笔色彩
Color mainBtnColor = Colors.white;
//主按钮进展条画笔
late Paint mainBtnProgressPaint;
//主按钮和外圈之间的暗影画笔
late Paint spacerShadowPaint;
//主按钮和外圈之间的暗影突变色 
List<Color> spacerShadowColors = [Colors.red, Colors.yellow];
//外圈画笔
late Paint outerRingPaint;
//外圈画笔色彩
Color outerRingColor = Colors.white;
//进展条画笔
late Paint progressPaint;

初始化画笔

RecordCustomPaint(this.firstProgress, this.secondProgress,Size tsize) {
// 按钮圆,按钮圆初始半径刚开端时应减去 进展条的宽度,在长准时按钮圆半径变小
    final double initBtnCircleRadius = tsize.width * 0.5 - kFit(67);
    //内圈按钮的背景
    mainBtnPaint = Paint()
      //填充形式
      ..style = PaintingStyle.fill
      //画笔色彩
      ..color = mainBtnColor;
      //画笔宽度 填充形式不必设置画笔宽度
      //..strokeWidth = initBtnCircleRadius;
    final double center = tsizeidth * 0.5;
    //内圈按钮的进展条   这里没设置色彩是因为色彩需求跟着进展条突变
    mainBtnProgressPaint = Paint()
        //填充形式
        ..style = PaintingStyle.fill
        ///画笔的宽度
        ..strokeWidth = initBtnCircleRadius;
    //关于
    final double outerRingRadius = initBtnCircleRadius +kFit(7.5);
    //表里圈之间的突变色 为了使看起来明显暂时使用 红到黄突变 [Colors.red, Colors.yellow]
    spacerShadowPaint = Paint()
      //中空形式  空心圆
      ..style = PaintingStyle.stroke
      ..shader = ui.Gradient.radial(Offset(center, center),outerRingRadius, spacerShadowColors, null, TileMode.mirror ,null, null, 0)
      //画笔宽度
      ..strokeWidth = kFit(15);
      //按钮外圈
      outerRingPaint = Paint()
        ..style = PaintingStyle.stroke
        ..color = outerRingColor
        ..strokeWidth = kFit(6);
      //进展条
      progressPaint = Paint()
        ..style = PaintingStyle.stroke
        ..strokeCap = StrokeCap.round
        ..strokeWidth = progressWidth;
  }
style特点:
PaintingStyle.fill 实心圆 不必设置画笔宽度
PaintingStyle.stroke 空心圆

关于空心圆半径设置

在设置空心圆的时候,关于设置圆半径需求留意一下,例如你设置圆的半径是 10像素,画笔宽度是 4像素,那么画出来的空心圆直径为 8像素. 也便是说画笔的笔尖在 10像素 上,然后笔宽4像素,表里测各占用2个像素。上面就用到了这个逻辑.

看上面代码,内圈宽度是 initBtnCircleRadius ,然后表里圈中心的暗影画笔宽度是 15,那么暗影画笔的半径应该应该设置多少才能刚好跟内圈拼接上?如下:

final double outerRingRadius = initBtnCircleRadius +kFit(7.5);

上方代码中的突变色,

//需求引进头文件
import 'dart:ui' as ui;
ui.Gradient.radial

使用方法,能够点击进去看类的介绍,比较详细。

接下来就便是要用画笔在画布上开端制作图形。

@override
void paint(Canvas canvas, Size size) {
  outerRingPaint.strokeWidth = kFit(6) + kFit(10) * firstProgress;
  //进展条突变色给一个默许透明度否则开端看起来色彩比较淡
  double color_opacity = secondProgress + 0.2;
  if (color_opacity > 1){
    color_opacity = 1;
  }
  // 半径,
  final double center = size.width * 0.5;
  //圆心
  final Offset circleCenter = Offset(center, center);
  // 按钮圆,按钮圆初始半径刚开端时应减去 表里圈中心的暗影宽度和外圈进展条的宽度。
  final double initBtnCircleRadius = size.width * 0.5 - kFit(72);
  //逆时针90旋转画布,因为画笔是从90开端绘画的
  canvas.translate(0.0, size.width);
  canvas.rotate(back90);
  //15是表里圈的间隔,6是变粗之前的外圈宽度  kFit(5) * firstProgress是外圈进展条需求变粗的宽度
  final double outerRingRadius = initBtnCircleRadius + kFit(15) + kFit(6) + kFit(5) * firstProgress;
  //视点转化为弧度
  final double outerRingSweepAngle = deg2Rad(360.0).toDouble();
  ///画主按钮
  _drawMainBtn(){
    //视点转化为弧度
    final double sweepAngle = deg2Rad(360.0).toDouble();
    final Rect btnArcRect =
    Rect.fromCircle(center: circleCenter, radius: initBtnCircleRadius);
    canvas.drawArc(btnArcRect, 0, sweepAngle, true, mainBtnPaint);
  }
  //主按钮进展条
  _drawMainBtnProgress(){
    //内圈圆突变色 随时动画的时刻 色彩透明度渐突变为 1
    mainBtnProgressPaint.color = kColor(238, 172, 3, color_opacity);
    if (secondProgress > 0) {
      //secondProgress 值转化为度数
      final double angle = 360.0 * secondProgress;
      //视点转化为弧度
      final double sweepAngle = deg2Rad(angle).toDouble();
      final Rect btnArcRect =
      Rect.fromCircle(center: circleCenter, radius: initBtnCircleRadius);
      canvas.drawArc(btnArcRect, 0, sweepAngle, true, mainBtnProgressPaint);
    }
  }
  //主按钮和外圈之间的暗影
  _drwaSpacerShadow(){
  //这里为什么+7.5 因为画笔的宽度是15 为什么这么做 上面的 “关于空心圆半径设置” 有解释
    final Rect arcRect =
    Rect.fromCircle(center: circleCenter, radius:initBtnCircleRadius + kFit(7.5));
    canvas.drawArc(arcRect, 0, outerRingSweepAngle, false, spacerShadowPaint);
  }
  //外圈
  _drawOuterRing(){
    final Rect arcRect =
    Rect.fromCircle(center: circleCenter, radius: outerRingRadius);
    canvas.drawArc(arcRect, 0, outerRingSweepAngle, false, outerRingPaint);
  }
  //制作外圈进展条
  _drawOuterRingProgress(){
    if (secondProgress > 0) {
      progressPaint.color = kColor(238, 172, 3, color_opacity);
      //画笔设置的 ..strokeCap = StrokeCap.round 所以起始点会超出12点钟半个笔头的宽度,把他转换为视点并旋转,这样起始点就会在正12点钟方向。
      var offset = asin(progressWidth * 0.5 / outerRingRadius);
      final double angle = 360.0 * secondProgress;
      final double progressCircleRadius = outerRingRadius;
      final double sweepAngle = deg2Rad(angle).toDouble();
      final Rect arcRect =
      Rect.fromCircle(center: circleCenter, radius: progressCircleRadius);
      canvas.drawArc(arcRect, offset, sweepAngle, false, progressPaint);
    }
  }
  //制作主按钮
  _drawMainBtn();
  //制作主按钮突变色
  _drawMainBtnProgress();
  //暗影制作
  _drwaSpacerShadow();
  //制作外圈
  _drawOuterRing();
  //制作外圈进展条
  _drawOuterRingProgress();
}

到这里已经设置结束了。 这里是完好的代码

到了这里我在想能不能设置进展条两种色彩的突变,而不是单纯的透明度。类似于这样

Flutter CustomPaint的使用。

这时我看到了 Paint 对象的 shader特点,

shader是一个抽象类,详细的完成有GradientImageShader两种,shader不为null时color特点无效。

很明显我需求的是 Gradient。

Gradient :突变有三种,

  • liner
  • radial
  • sweep

想要使用它需求先引进头文件

//需求引进头文件

import 'dart:ui' as ui;
ui.Gradient.radial

然后这样即可,

ui.Gradient.radial

Flutter CustomPaint的使用。

liner、radial、sweep他们三个的区别,mac中按command加左键进去看API,里边介绍十分清楚。

他们都有一个一起的特点

TileMode tileMode = TileMode.clamp,

TileMode是个枚举

  • clamp
  • repeated
  • mirror
  • decal

每种枚举对应的类型效果,API里边也写的十分清楚,而且供给的有png链接能够检查样式。

例如clamp效果:

Flutter CustomPaint的使用。
Flutter CustomPaint的使用。
Flutter CustomPaint的使用。

经过检查API了解到,我需求的应该是

ui.Gradient.sweep
tileMode:TileMode.clamp

修正以下代码,经过 红变黄 来演示。

1.初始化画笔代码修正

//内圈进展条
mainBtnProgressPaint = Paint()
    ..style = PaintingStyle.fill
    ..shader = ui.Gradient.sweep(Offset(center, center), [Colors.red, Colors.yellow], null, TileMode.clamp ,deg2Rad(-90.0).toDouble(), math.pi * 2, null)
    ..strokeWidth = initBtnCircleRadius;
//彻底进展条
progressPaint = Paint()
  ..style = PaintingStyle.stroke
  ..shader = ui.Gradient.sweep(Offset(center, center), [Colors.red, Colors.yellow], null, TileMode.clamp ,deg2Rad(-90.0).toDouble(), math.pi * 2, null)
  ..strokeCap = StrokeCap.round
  ..strokeWidth = progressWidth;

2.制作代码修正

//主按钮突变
_drawMainBtnProgress(){
  //内圈圆突变色
  if (secondProgress > 0) {
    //secondProgress 值转化为度数
    final double angle = 360.0 * secondProgress;
    //视点转化为弧度
    final double sweepAngle = deg2Rad(angle).toDouble();
    final Rect btnArcRect =
    Rect.fromCircle(center: circleCenter, radius: initBtnCircleRadius);
    canvas.drawArc(btnArcRect, 0, sweepAngle, true, mainBtnProgressPaint);
  }
}
//制作外圈进展条
_drawOuterRingProgress(){
  if (secondProgress > 0) {
    var offset = asin(progressWidth * 0.5 / outerRingRadius);
    final double angle = 360.0 * secondProgress;
    final double progressCircleRadius = outerRingRadius;
    final double sweepAngle = deg2Rad(angle).toDouble();
    final Rect arcRect =
    Rect.fromCircle(center: circleCenter, radius: progressCircleRadius);
    canvas.drawArc(arcRect, offset, sweepAngle, false, progressPaint);
  }
}

编译之后效果:

Flutter CustomPaint的使用。

代码文件链接