首页小e动画

This commit is contained in:
czz
2025-06-07 17:49:19 +08:00
parent 77152e5d81
commit 48b3b06f17
3 changed files with 331 additions and 60 deletions

View File

@@ -0,0 +1,273 @@
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,
),
),
),
),
],
),
);
},
);
}
}