import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; // class FloatingSvgIcon extends StatefulWidget { // final String assetPath; // SVG 图标路径 // final double width; // 宽度 // final double height; // 高度 // final Duration duration; // 浮动周期 // final double floatOffset; // 浮动高度(单位:比例) // final VoidCallback? onTap; // 点击事件 // const FloatingSvgIcon({ // super.key, // required this.assetPath, // this.width = 60, // this.height = 60, // this.duration = const Duration(milliseconds: 800), // this.floatOffset = 0.02, // this.onTap, // }); // @override // State createState() => _FloatingSvgIconState(); // } // class _FloatingSvgIconState extends State // with SingleTickerProviderStateMixin { // late final AnimationController _controller; // late final Animation _animation; // @override // void initState() { // super.initState(); // _controller = AnimationController( // vsync: this, // duration: widget.duration, // )..repeat(reverse: true); // _animation = Tween( // begin: Offset(0, -widget.floatOffset), // end: Offset(0, widget.floatOffset), // ).animate(CurvedAnimation( // parent: _controller, // curve: Curves.easeInOut, // )); // } // @override // void dispose() { // _controller.dispose(); // super.dispose(); // } // // 计算阴影缩放因子,范围大致从 0.8 ~ 1.2 // double getShadowScale(double dy) { // return 1 - dy * 0.25; // dy 为负时,scale > 1,dy 为正时,scale < 1 // } // // 计算阴影透明度,范围大致从 0.3 ~ 0.7 // double getShadowOpacity(double dy) { // return 0.5 - dy * 0.3; // } // @override // Widget build(BuildContext context) { // return AnimatedBuilder( // animation: _controller, // builder: (context, child) { // final dy = _animation.value.dy; // final shadowScale = getShadowScale(dy).clamp(0.8, 1.2); // final shadowOpacity = getShadowOpacity(dy).clamp(0.3, 0.7); // return SlideTransition( // position: _animation, // child: GestureDetector( // onTap: widget.onTap, // child: SizedBox( // width: widget.width, // height: widget.height + widget.height * 0.3, // 多留点高度给阴影 // child: Stack( // alignment: Alignment.topCenter, // children: [ // // 阴影在底部,椭圆形,随浮动缩放和透明度变化 // Positioned( // bottom: 0, // child: Transform.scale( // scale: shadowScale, // child: Opacity( // opacity: shadowOpacity, // child: Container( // width: widget.width * 0.6, // height: widget.height * 0.08, // decoration: BoxDecoration( // color: Colors.black54, // borderRadius: // BorderRadius.circular(widget.height * 0.075), // boxShadow: [ // BoxShadow( // color: Colors.black26, // blurRadius: 8, // spreadRadius: 1, // ), // ], // ), // ), // ), // ), // ), // // SVG图标 // Positioned( // top: 0, // child: SizedBox( // width: widget.width, // height: widget.height, // child: SvgPicture.asset( // widget.assetPath, // fit: BoxFit.fill, // ), // ), // ), // ], // ), // ), // ), // ); // }, // ); // } // } import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class FloatingSvgIcon extends StatefulWidget { final String assetPath; // SVG 图标路径 final double width; // 宽度 final double height; // 高度 final Duration duration; // 浮动周期 final double floatOffset; // 浮动高度(单位:比例) final VoidCallback? onTap; // 点击事件 const FloatingSvgIcon({ super.key, required this.assetPath, this.width = 60, this.height = 60, this.duration = const Duration(milliseconds: 800), this.floatOffset = 0.02, this.onTap, }); @override State createState() => _FloatingSvgIconState(); } class _FloatingSvgIconState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation _animation; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: widget.duration, )..repeat(reverse: true); _animation = Tween( begin: Offset(0, -widget.floatOffset), end: Offset(0, widget.floatOffset), ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, )); } @override void dispose() { _controller.dispose(); super.dispose(); } // // 计算阴影缩放因子,范围大致从 0.8 ~ 1.2 // double getShadowScale(double dy) { // // dy 是当前偏移,负表示向上,正表示向下 // // 上浮时阴影变小,下落时阴影变大 // return (1 - dy * 5).clamp(0.8, 1.2); // } // // 计算阴影透明度,范围大致从 0.3 ~ 0.7 // double getShadowOpacity(double dy) { // return (0.5 - dy * 1.5).clamp(0.3, 0.7); // } double getShadowScale(double dy) { // dy负时(上浮)阴影变小,正时(下落)阴影变大 return (1 + dy * 5).clamp(0.8, 1.2); } double getShadowOpacity(double dy) { return (0.5 + dy * 1.5).clamp(0.3, 0.7); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { final dy = _animation.value.dy; final shadowScale = getShadowScale(dy); final shadowOpacity = getShadowOpacity(dy); return SizedBox( width: widget.width, height: widget.height + widget.height * 0.3, // 留出阴影空间 child: Stack( alignment: Alignment.topCenter, children: [ // 固定底部投影,大小随动画变化 Positioned( bottom: 4, child: Transform.scale( scale: shadowScale, child: Opacity( opacity: shadowOpacity, child: Container( width: widget.width * 0.5, height: widget.height * 0.12, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(widget.height * 0.1), boxShadow: const [ BoxShadow( color: Colors.black38, blurRadius: 12, spreadRadius: 0, offset: Offset(0, 2), ), ], ), ), ), ), ), // SVG图标上下浮动 SlideTransition( position: _animation, child: GestureDetector( onTap: widget.onTap, child: SizedBox( width: widget.width, height: widget.height, child: SvgPicture.asset( widget.assetPath, fit: BoxFit.fill, ), ), ), ), ], ), ); }, ); } }