Flutter 仿抖音直播头像闪烁动画

先看效果


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();
    }
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容