Files
tuiche/lib/pages/mh_page/FloatingSvgIcon.dart
2025-06-07 17:49:19 +08:00

274 lines
8.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<FloatingSvgIcon> createState() => _FloatingSvgIconState();
// }
// class _FloatingSvgIconState extends State<FloatingSvgIcon>
// with SingleTickerProviderStateMixin {
// late final AnimationController _controller;
// late final Animation<Offset> _animation;
// @override
// void initState() {
// super.initState();
// _controller = AnimationController(
// vsync: this,
// duration: widget.duration,
// )..repeat(reverse: true);
// _animation = Tween<Offset>(
// 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 > 1dy 为正时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<FloatingSvgIcon> createState() => _FloatingSvgIconState();
}
class _FloatingSvgIconState extends State<FloatingSvgIcon>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<Offset> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: widget.duration,
)..repeat(reverse: true);
_animation = Tween<Offset>(
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,
),
),
),
),
],
),
);
},
);
}
}