先看效果
PixPin_2025-02-28_13-52-43.gif
使用方式,可以自己把颜色也放到参数,animate是控制是否开启动画
AvatarBorderAnim(
padding: 3.0, // 可选,如果你想要不同的内边距大小
child: CircleAvatar(
radius: 40,
backgroundImage: NetworkImage("https://photo.tuchong.com/424887/f/10370407.jpg"),
),
animate: animate,
)
动画部件 AvatarBorderAnim
class AvatarBorderAnim extends StatefulWidget {
final Widget child;
final double padding;
final bool animate;
const AvatarBorderAnim({
Key? key,
required this.child,
this.padding = 10.0,
this.animate = false, // 默认内边距为10
}) : super(key: key);
@override
_AvatarBorderAnimState createState() => _AvatarBorderAnimState();
}
class _AvatarBorderAnimState extends State<AvatarBorderAnim> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
bool isForward=true;
late GlobalKey _globalKey;
double? _childWidth;
// var ratio=100/_childSize!.width;
double ratio=0.89285714286; // 100/112 外层图片半径50,直径100,这里却得到112,需要计算出比例
double? get w=> _childWidth!=null? _childWidth!+20 : null;
double? get h=> _childWidth!=null? _childWidth!+20 : null;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
)..repeat(reverse: true);
updateAnimate();
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
// 判断单向动画完成
_controller.addStatusListener((AnimationStatus status){
setState(() {
isForward=status.name=='forward';
});
});
_globalKey = GlobalKey();
WidgetsBinding.instance!.addPostFrameCallback((_) {
_getChildSize();
});
}
void _getChildSize() {
final RenderBox? renderBox = _globalKey.currentContext?.findRenderObject() as RenderBox?;
if (renderBox != null) {
_childWidth =double.parse((renderBox.size.width*ratio).toStringAsFixed(2)) ;
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (_, child) {
return Stack(
alignment: Alignment.center,
children: [
Transform.scale(
scale: scaleNum(),
child: borderBg(),
),
Container(
key: _globalKey,
padding: EdgeInsets.all(widget.padding), // 应用内边距
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.red, // 动画透明度效果
width: 3,
),
),
child:Transform.scale(
scale: 1-_animation.value/1*(1-0.96),
child: child,
),
)
],
);
},
child:widget.child,
);
}
borderBg(){
return Container(
padding: EdgeInsets.all(widget.padding),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.red.withOpacity(isForward? 1-_animation.value : 0),// 扩散动画只做单向,然后立即消失
width: 3,
),
),
child: Container(width:w,height:h),
);
}
scaleNum(){
// 0.83= 100/120
// var res=0.83+_animation.value/1*((120-100)/120);
// print("res:$res");
// return res;
if(w==null) return 0.0;
var d= w!-_childWidth!;
var o=_childWidth!/w!;
var oFormatted = double.parse(o.toStringAsFixed(2));
var res=oFormatted+_animation.value/1*(d/w!);
// print("o:$oFormatted ,d:${d},d/w:${d/w!},w!:${w!},$res");
return res;
}
updateAnimate() {
if (widget.animate) {
_controller.repeat(reverse: true);
} else {
_controller.stop();
_controller.value = 0; // 停止动画并设置透明度为0
}
}
@override
void didUpdateWidget(AvatarBorderAnim oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.animate != oldWidget.animate) {
updateAnimate();
}
}
}