flutter - 实现渐变动画矩形边框

452 阅读2分钟

最近碰到一个需求需要实现一个矩形,矩形边框为渐变色,并且要求渐变色不断滚动向前,如下图所示

image.png

主要思路参考自这篇文章 另外还有来自 chatgpt 的回答,实现效果如上图所示效果

具体实现思路

  1. CustomPainter 实现一个渐变矩形边框
      void paint(Canvas canvas, Size size) {
        // 创建一个矩形区域
        final rect = Rect.fromLTWH(0, 0, 96, 38);
        final paint = Paint()
          ..shader = LinearGradient(
            // 渐变色值
            colors: const [Color.yellow, Colors.green],
            // 此处是实现动画的关键 => 动态传入角度来实现动画
            transform: GradientRotation(animation * 2 * pi),
            // 创建一个线性渐变着色器对象并将其应用于矩形形状的填充
          ).createShader(rect)
          ..style = PaintingStyle.stroke
          // 边框宽度为 2 注意边框宽度是在矩形外边,所以这个矩形的宽高就变为 100*42
          ..strokeWidth = 2; 
        
        // 实现圆角矩形
        final rRect = RRect.fromRectAndRadius(rect, const Radius.circular(8));
        canvas.drawRRect(rRect, paint);
        
        // 另外也可实现直角矩形/圆形
        // 画直角矩形 
        canvas.drawRect(rect, paint);
        // 画圆形 参数:圆心坐标, 半径
        canvas.drawCircle(Offset(48, 20), 50, paint);
      }
    
  2. 动画实现借助显式动画 AnimationControllerAnimationBuilder,具体写法看下方完整代码

完整代码

  • 核心绘图代码
import 'dart:math';

import 'package:flutter/material.dart';

class GradientBound extends StatefulWidget {
  // 矩形 长、宽、边框宽度,其中长、宽已包含边框宽度
  final double width;
  final double height;
  final double border;
  final Widget child;
  const GradientBound({
    super.key,
    required this.width,
    required this.height,
    required this.border,
    required this.child,
  });

  @override
  State<GradientBound> createState() => _GradientBoundState();
}

class _GradientBoundState extends State<GradientBound>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );
    animation = Tween<double>(begin: 0, end: 1.0).animate(CurvedAnimation(
      parent: controller,
      curve: Curves.linear,
    ));

    controller.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: animation,
        builder: (BuildContext context, Widget? child) {
          return CustomPaint(
            // 创建 painter
            painter: GradientBoundPainter(
              colors: const [Color.yellow, Colors.green],
              animation: animation.value,
              width: widget.width,
              height: widget.height,
              border: widget.border,
            ),
            // child 内容铺满容器并居中
            child: Container(
              alignment: Alignment.center,
              width: widget.width,
              height: widget.height,
              color: Colors.transparent,
              child: widget.child,
            ),
          );
        });
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

// 渐变边框核心绘图逻辑
class GradientBoundPainter extends CustomPainter {
  final List<Color> colors;
  final double animation;
  final double width;
  final double height;
  final double border;
  const GradientBoundPainter({
    Key? key,
    required this.colors,
    required this.animation,
    required this.width,
    required this.height,
    required this.border,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final rect = Rect.fromLTWH(0, 0, width, height);
    final paint = Paint()
      ..shader = LinearGradient(
        colors: colors,
        transform: GradientRotation(animation * 2 * pi),
      ).createShader(rect)
      ..style = PaintingStyle.stroke
      ..strokeWidth = border;

    final rRect = RRect.fromRectAndRadius(rect, const Radius.circular(8));
    canvas.drawRRect(rRect, paint);
  }

  @override
  bool shouldRepaint(covariant GradientBoundPainter oldDelegate) {
    return oldDelegate.colors != colors || oldDelegate.animation != animation;
  }
}