274 lines
8.3 KiB
Dart
274 lines
8.3 KiB
Dart
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 > 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<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,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|