最近碰到一个需求需要实现一个矩形,矩形边框为渐变色,并且要求渐变色不断滚动向前,如下图所示
主要思路参考自这篇文章 另外还有来自 chatgpt 的回答,实现效果如上图所示效果
具体实现思路
- 用
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); }
- 动画实现借助显式动画
AnimationController
和AnimationBuilder
,具体写法看下方完整代码
完整代码
- 核心绘图代码
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;
}
}