以完成下面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图中显示不太明显)五个元素。所以需求五只画笔。
接下来的代码均在 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();
}
到这里已经设置结束了。 这里是完好的代码
到了这里我在想能不能设置进展条两种色彩的突变,而不是单纯的透明度。类似于这样
这时我看到了 Paint
对象的 shader
特点,
shader是一个抽象类,详细的完成有Gradient
和ImageShader
两种,shader不为null时color特点无效。
很明显我需求的是 Gradient。
Gradient :突变有三种,
- liner
- radial
- sweep
想要使用它需求先引进头文件
//需求引进头文件
import 'dart:ui' as ui;
ui.Gradient.radial
然后这样即可,
ui.Gradient.radial
liner、radial、sweep他们三个的区别,mac中按command加左键进去看API,里边介绍十分清楚。
他们都有一个一起的特点
TileMode tileMode = TileMode.clamp,
TileMode是个枚举
- clamp
- repeated
- mirror
- decal
每种枚举对应的类型效果,API里边也写的十分清楚,而且供给的有png链接能够检查样式。
例如clamp
效果:
经过检查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);
}
}
编译之后效果:
代码文件链接