头图

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

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,具体写法看下方完整代码

    完整代码

  3. 核心绘图代码

    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;
      }
    }
    

大桔子
588 声望51 粉丝

下一步该干什么呢...