Flutter Loading倒影

513 阅读1分钟
效果图

iShot_2022-08-25_11.02.51.gif

发完倒影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,
      );
    }
  }
}