简介
Flutter中自定义view及动画一直以来自觉比较难搞,这两天研究下版本雅思设计UI发现有一个圆弧进度条效果比较炫,所以拿来先用flutter实现一把。
效果图
上代码
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Gradient Arc Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ArcPage(),
);
}
}
class ArcPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Gradient Arc Example'),
),
body: Center(
child: Transform.rotate(
angle: -(math.pi + math.pi / 4 + math.asin(22 * 0.5 / 100)),
// 0,
child: ArcView(),
),
),
);
}
}
class ArcView extends StatefulWidget {
double width = 200;
double height = 200;
@override
State<StatefulWidget> createState() {
return ArcViewState();
}
}
class ArcViewState extends State<ArcView> with SingleTickerProviderStateMixin {
late AnimationController controller;
late Curve curve;
// double progress=0;
@override
void initState() {
super.initState();
controller = AnimationController(duration:const Duration(seconds: 1),lowerBound:0,upperBound:60,vsync: this);
controller.addListener(() {
setState(() {
print(controller.value);
});
});
controller.forward();
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
controller.forward(from: 0);
},
child: CustomPaint(
painter: ArcPainter(
strokeWidth: 22,
strokeCap: StrokeCap.round,
gradientColors: const [Color(0xFF54D6A7), Color(0xFF00CDDA)],
bgColor: const Color(0xFFF3F4F6),
progress: controller.value,
roundColor: Colors.white,
width: 200,//需要与widget.width一致
),
size: Size(widget.width, widget.height), // 调整大小以适应你的需求
),
);
}
}
class ArcPainter extends CustomPainter {
double width;
///圆弧粗细
double strokeWidth;
///圆弧边缘显示样式
StrokeCap strokeCap;
///进度条渐变色
List<Color> gradientColors;
///背景颜色
Color bgColor;
///百分比进度,自己内部转换为具体度数
double progress;
///圆点颜色
Color roundColor;
ArcPainter(
{required this.width,
required this.strokeWidth,
required this.strokeCap,
required this.gradientColors,
required this.bgColor,
required this.progress,
required this.roundColor});
@override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
/// 修正后的开始角度,弧度制
final startAngle = math.asin(strokeWidth * 0.5 / (width / 2));
/// 扫过的角度,弧度制
final sweepAngle = (math.pi + math.pi / 2) * progress / 100;
var gradient = SweepGradient(
center: Alignment.center,
colors: gradientColors, // 渐变颜色列表
startAngle: 0, // 渐变起始位置
endAngle: 2 * math.pi, // 渐变结束位置
);
final paint = Paint()
..shader = gradient.createShader(rect) // 使用渐变作为画笔的颜色
..style = PaintingStyle.stroke
..strokeCap = strokeCap
..strokeWidth = strokeWidth;
final bgPaint = Paint()
..color = bgColor
..style = PaintingStyle.stroke
..strokeCap = strokeCap
..strokeWidth = strokeWidth;
canvas.drawArc(rect, startAngle, math.pi + math.pi / 2, false, bgPaint);
canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
final double drawRadius = width / 2;
final double center = width / 2;
///修正原点初始角度 90度
var deg = degToRad(90) + startAngle + sweepAngle;
final double dx = center + drawRadius * math.sin(deg);
final double dy = center - drawRadius * math.cos(deg);
Offset offsetCenter = Offset(dx, dy);
final roundPaint = Paint()
..style = PaintingStyle.fill
..color = roundColor;
// ..strokeWidth = (outerRadius - innerRadius);
canvas.drawCircle(offsetCenter, strokeWidth / 2 - 2, roundPaint);
}
/// 度数转类似于π的那种角度
double degToRad(double deg) => deg * (math.pi / 180.0);
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
技术点
画圆弧
canvas.drawArc(rect, startAngle, math.pi + math.pi / 2, false, bgPaint);
画圆弧基本上没什么难度,只要调用相关api就可以
圆弧顶点圆角设计
final paint = Paint()
..shader = gradient.createShader(rect) // 使用渐变作为画笔的颜色
..style = PaintingStyle.stroke
..strokeCap = strokeCap//注意这里设置成 StrokeCap.round
..strokeWidth = strokeWidth;
要让圆弧顶点变成圆角,只要在画圆弧的画笔设置StrokeCap.round即可,另外由于圆弧增加圆角,额外占用了圆环一部分空间,所以在设置圆弧渐变时此圆角所在会被绘制成渐变色末尾颜色。此处是坑,需留意。同样下面也给出了利用偏移角度,来解决此问题
圆弧渐变
var gradient = SweepGradient(
center: Alignment.center,
colors: gradientColors, // 渐变颜色列表
startAngle: 0, // 渐变起始位置
endAngle: 2 * math.pi, // 渐变结束位置
);
final paint = Paint()
..shader = gradient.createShader(rect) // 使用渐变作为画笔的颜色
..style = PaintingStyle.stroke
..strokeCap = strokeCap
..strokeWidth = strokeWidth;
圆弧渐变是比较难的一个东西,在不了解其他几个渐变模式下是非常难搞的。曾经一度以为圆环渐变的效果不能实现。后来经搜索得出,用此渐变效果能完美达到设计需要
此处endAngle需要进行仔细推敲,因为渐变结束位置有可能并不是2π。
画白点按钮
///修正原点初始角度 90度
var deg = degToRad(90) + startAngle + sweepAngle;
final double dx = center + drawRadius * math.sin(deg);
final double dy = center - drawRadius * math.cos(deg);
Offset offsetCenter = Offset(dx, dy);
final roundPaint = Paint()
..style = PaintingStyle.fill
..color = roundColor;
// ..strokeWidth = (outerRadius - innerRadius);
canvas.drawCircle(offsetCenter, strokeWidth / 2 - 2, roundPaint);
画白点按钮是需要一定数学功底的,它需要计算出当前小圆点的中心点坐标。才能准确画出白点位置。
注意
想要动画顺利执行,下面自定义view中的方法必须返回true或者根据实际需要返回。做这个的时候这个地方是默认false,我一度认为flutter 的setState失效。然后并不是,请主动避坑。
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}