效果图
发完倒影widget刚好看到这篇Loading倒影效果 - 掘金 (juejin.cn)刚觉效果挺好看的,用flutter做了下。由于倒影效果已经做好了,所以只需要做Loading然后用ReflectionWidget(上一篇有代码Flutter倒影小widget - 掘金 (juejin.cn))包裹一下就可以了。
Loading源码
import 'dart:ui' as ui;
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
class LoadingCircleWidget extends StatefulWidget {
const LoadingCircleWidget({Key? key}) : super(key: key);
@override
State<LoadingCircleWidget> createState() => _LoadingCircleWidgetState();
}
class _LoadingCircleWidgetState extends State<LoadingCircleWidget>
with SingleTickerProviderStateMixin {
late final animCtrl =
AnimationController(vsync: this, duration: const Duration(seconds: 3));
@override
void initState() {
super.initState();
animCtrl.repeat();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animCtrl,
builder: (BuildContext context, Widget? child) {
return Container(
width: 240,
height: 180,
decoration: const BoxDecoration(color: Color(0xFF080E1A)),
child: CustomPaint(
foregroundPainter: LoadingCirclePainter(animCtrl.value),
),
);
},
);
}
}
class LoadingCirclePainter extends CustomPainter {
final double progress;
LoadingCirclePainter(this.progress);
@override
void paint(Canvas canvas, Size size) {
_drawText(canvas, size);
_drawCircle(canvas, size);
}
void _drawText(Canvas canvas, Size size) {
TextPainter painter = TextPainter(
textDirection: TextDirection.ltr,
text: const TextSpan(
text: "Loading...",
style: TextStyle(
color: Color(0xFF1581CA),
),
),
);
painter.layout();
Size textSize = painter.size;
Offset origin = Offset(
(size.width - textSize.width) / 2, (size.height - textSize.height) / 2);
double blur = 8;
canvas.saveLayer(Offset.zero & size, Paint());
canvas.saveLayer(
Offset.zero & size,
Paint()
..blendMode = BlendMode.src
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 100)
..colorFilter = const ColorFilter.mode(Color.fromRGBO(20, 129, 202, .35), BlendMode.srcATop)
..imageFilter = ImageFilter.blur(sigmaX: blur, sigmaY: blur),
);
canvas.drawRect(origin & (textSize + const Offset(-4, -2)), Paint());
canvas.restore();
canvas.restore();
painter.paint(canvas, origin);
}
@override
bool shouldRepaint(covariant LoadingCirclePainter oldDelegate) {
return progress != oldDelegate.progress;
}
Paint paintCircle = Paint()
..color = const Color(0xFF070F1A)
..style = PaintingStyle.stroke
..strokeWidth = 10;
Paint paintPoint = Paint()
..color = const Color.fromRGBO(7, 15, 26, 1)
..style = PaintingStyle.stroke
..strokeWidth = 10
..strokeCap = StrokeCap.round;
Paint paintShadow = Paint()
..style = PaintingStyle.fill
..strokeCap = StrokeCap.round;
void _drawCircle(Canvas canvas, Size size) {
Path path = Path()
..addOval(Rect.fromCircle(
center: Offset(size.width / 2, size.height / 2),
radius: size.height / 2 - 8));
ui.PathMetrics pathMetrics = path.computeMetrics();
canvas.drawPath(path, paintCircle);
var pathMetric = pathMetrics.first;
double len = pathMetric.length;
//绘制环道的渐变
Path p = pathMetric.extractPath(len - len / 3, len);
Matrix4 matrix4 = Matrix4.identity()
..translate(size.width / 2, size.height / 2)
..multiply(Matrix4.identity()..rotateZ(math.pi * 2 * progress))
..translate(-size.width / 2, -size.height / 2);
p = p.transform(matrix4.storage);
canvas.drawPath(
p,
paintPoint
..shader = ui.Gradient.sweep(
Offset(size.width / 2, size.height / 2),
[const Color(0xFF2A7CCA), const Color(0xFF0F0A25)],
[0, .6],
TileMode.mirror,
0,
3.14,
matrix4.storage),
);
// 绘制头部
ui.Tangent? tangent = pathMetric.getTangentForOffset(len * progress);
if (tangent != null) {
canvas.drawCircle(
tangent.position,
25,
paintShadow
..shader = ui.Gradient.radial(tangent.position, 25, [
const Color.fromRGBO(40, 124, 202, 1),
const Color.fromRGBO(40, 124, 202, 0.6),
const Color.fromRGBO(40, 124, 202, 0.4),
const Color.fromRGBO(40, 124, 202, 0.18),
const Color.fromRGBO(40, 124, 202, 0),
], [
0,
.1,
0.2,
.6,
1
]),
);
canvas.drawCircle(
tangent.position,
3,
paintPoint,
);
}
}
}