首页小e动画
This commit is contained in:
273
lib/pages/mh_page/FloatingSvgIcon.dart
Normal file
273
lib/pages/mh_page/FloatingSvgIcon.dart
Normal 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 > 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user