From 48b3b06f1746d7386f21752e6c55616dff3c6e15 Mon Sep 17 00:00:00 2001 From: czz <862977248@qq.com> Date: Sat, 7 Jun 2025 17:49:19 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E5=B0=8Fe=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/img/icon/xiaoe.svg | 2 +- lib/pages/mh_page/FloatingSvgIcon.dart | 273 +++++++++++++++++++++++++ lib/pages/mh_page/new_Home_page.dart | 116 ++++++----- 3 files changed, 331 insertions(+), 60 deletions(-) create mode 100644 lib/pages/mh_page/FloatingSvgIcon.dart diff --git a/assets/img/icon/xiaoe.svg b/assets/img/icon/xiaoe.svg index f996a3d..49bf9f9 100644 --- a/assets/img/icon/xiaoe.svg +++ b/assets/img/icon/xiaoe.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/lib/pages/mh_page/FloatingSvgIcon.dart b/lib/pages/mh_page/FloatingSvgIcon.dart new file mode 100644 index 0000000..1cacf4f --- /dev/null +++ b/lib/pages/mh_page/FloatingSvgIcon.dart @@ -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 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, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/pages/mh_page/new_Home_page.dart b/lib/pages/mh_page/new_Home_page.dart index 046ac2d..4ac2df3 100644 --- a/lib/pages/mh_page/new_Home_page.dart +++ b/lib/pages/mh_page/new_Home_page.dart @@ -9,7 +9,7 @@ import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/controller/device/body_device_controller.dart'; import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; import 'package:vbvs_app/controller/mh_controller/muser_info_controller.dart'; - +import 'package:vbvs_app/pages/mh_page/FloatingSvgIcon.dart'; class NewHomePage extends StatefulWidget { const NewHomePage({super.key}); @@ -96,64 +96,64 @@ class _NewHomePageState extends State { child: Scaffold( backgroundColor: Colors.transparent, appBar: AppBar( - iconTheme: IconThemeData(color: themeController.currentColor.sc3), - backgroundColor: Colors.transparent, - automaticallyImplyLeading: false, - titleSpacing: 0, - title: Row( - children: [ - // 左侧头像 - SizedBox(width: 40.rpx), - CircleAvatar( - radius: 27.rpx, // 可根据需求调整 - backgroundImage: login == 1 - ? (userInfoController.model.user!.avatar == null || - userInfoController.model.user!.avatar!.isEmpty - ? const AssetImage( + iconTheme: + IconThemeData(color: themeController.currentColor.sc3), + backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + titleSpacing: 0, + title: Container( + height: 180.rpx, + child: Row( + children: [ + // 左侧头像 + SizedBox(width: 40.rpx), + CircleAvatar( + radius: 27.rpx, // 可根据需求调整 + backgroundImage: login == 1 + ? (userInfoController.model.user!.avatar == null || + userInfoController + .model.user!.avatar!.isEmpty + ? const AssetImage( + "assets/images/people_avatar.png", + ) + : NetworkImage( + userInfoController.model.user!.avatar!, + )) + : const AssetImage( "assets/images/people_avatar.png", - ) - : NetworkImage( - userInfoController.model.user!.avatar!, - )) - : const AssetImage( - "assets/images/people_avatar.png", - ), - ), - SizedBox(width: 23.rpx), // 左侧头像和文本之间的间距 - Text( - 'Eason Chan', - style: TextStyle(fontSize: 30.rpx, color: Colors.white), - ), + ), + ), + SizedBox(width: 23.rpx), // 左侧头像和文本之间的间距 + Text( + 'Eason Chan', + style: TextStyle(fontSize: 30.rpx, color: Colors.white), + ), - const Spacer(), // 左右分隔 - // Container( - // width: 61.rpx, - // height: 78.rpx, - // alignment: const Alignment(0, 0), - // child: Image.asset( - // "assets/images/xiaoe.png", - // fit: BoxFit.cover, - // ), - // ), - // SizedBox(width: 46.rpx), // icon 之间的间距 - ClickableContainer( - backgroundColor: Colors.transparent, - highlightColor: Colors.transparent, - padding: EdgeInsets.only(right: 0), - onTap: () {}, - child: Container( - height: 60.rpx, - width: 75.rpx, - child: SvgPicture.asset( - 'assets/img/icon/xiaoe.svg', - // color: Colors.white, - ))), + const Spacer(), // 左右分隔 + // Container( + // width: 61.rpx, + // height: 78.rpx, + // alignment: const Alignment(0, 0), + // child: Image.asset( + // "assets/images/xiaoe.png", + // fit: BoxFit.cover, + // ), + // ), + // SizedBox(width: 46.rpx), // icon 之间的间距 - // SizedBox(width: 40.rpx), - ], - ), - centerTitle: false, - ), + FloatingSvgIcon( + assetPath: 'assets/img/icon/xiaoe.svg', + width: 60.rpx, + height: 60.rpx, + onTap: () { + print("点击了小鹅图标"); + }, + ), + + SizedBox(width: 40.rpx), + ], + ), + )), body: SafeArea( child: Container( width: MediaQuery.sizeOf(context).width, @@ -165,9 +165,7 @@ class _NewHomePageState extends State { // fit: BoxFit.cover, // ), // ), - decoration: BoxDecoration( - - ), + decoration: BoxDecoration(), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.max,