深色模式、首页设置页面和功能优化
This commit is contained in:
159
lib/widgets/main_navigation.dart
Normal file
159
lib/widgets/main_navigation.dart
Normal file
@@ -0,0 +1,159 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../constants/app_constants.dart';
|
||||
import '../views/home/home_page.dart';
|
||||
import '../views/discover_page.dart';
|
||||
import '../views/favorites_page.dart';
|
||||
import '../views/profile/profile_page.dart';
|
||||
import '../services/get/main_navigation_controller.dart';
|
||||
import '../services/get/profile_controller.dart';
|
||||
import '../services/get/theme_controller.dart';
|
||||
import '../services/get/tap_liquid_glass_controller.dart';
|
||||
import 'tap-liquid-glass.dart';
|
||||
|
||||
/// 时间: 2025-03-21
|
||||
/// 功能: 主导航页面,包含底部导航栏和4个主要页面
|
||||
/// 介绍: 这是应用的主要导航容器,提供底部导航栏来切换主页、发现、收藏和个人页面
|
||||
/// 最新变化: 使用 Stack 布局实现 Tap 沉浸光感液态玻璃导航栏悬浮效果
|
||||
|
||||
class MainNavigation extends StatelessWidget {
|
||||
const MainNavigation({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 初始化导航控制器(只初始化一次)
|
||||
Get.lazyPut(() => MainNavigationController());
|
||||
Get.lazyPut(() => TapLiquidGlassController());
|
||||
final controller = Get.find<MainNavigationController>();
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final glassController = Get.find<TapLiquidGlassController>();
|
||||
|
||||
final List<Widget> pages = [
|
||||
HomePage(),
|
||||
const DiscoverPage(),
|
||||
const FavoritesPage(),
|
||||
const ProfilePage(),
|
||||
];
|
||||
|
||||
final List<BottomNavigationBarItem> bottomNavItems = [
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home),
|
||||
activeIcon: Icon(Icons.home, color: AppConstants.primaryColor),
|
||||
label: '主页',
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.explore),
|
||||
activeIcon: Icon(Icons.explore, color: AppConstants.primaryColor),
|
||||
label: '发现',
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.favorite_border),
|
||||
activeIcon: Icon(Icons.favorite, color: AppConstants.primaryColor),
|
||||
label: '收藏',
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person_outline),
|
||||
activeIcon: Icon(Icons.person, color: AppConstants.primaryColor),
|
||||
label: '个人',
|
||||
),
|
||||
];
|
||||
|
||||
return Obx(() {
|
||||
final isDark = themeController.isDarkMode;
|
||||
final useGlass = glassController.isEnabled;
|
||||
|
||||
return Scaffold(
|
||||
body: useGlass
|
||||
? _buildGlassBody(controller, pages)
|
||||
: _buildClassicBody(controller, pages, isDark, bottomNavItems),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// 构建液态玻璃导航栏主体
|
||||
/// 使用 Stack 布局,让导航栏悬浮在页面内容上方
|
||||
Widget _buildGlassBody(
|
||||
MainNavigationController controller,
|
||||
List<Widget> pages,
|
||||
) {
|
||||
return Stack(
|
||||
children: [
|
||||
// 页面内容 - 延伸到屏幕底部
|
||||
IndexedStack(
|
||||
index: controller.currentIndex.value,
|
||||
children: pages,
|
||||
),
|
||||
// 悬浮的液态玻璃导航栏
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: TapLiquidGlassNavigation(
|
||||
currentIndex: controller.currentIndex.value,
|
||||
onTap: (index) {
|
||||
controller.switchPage(index);
|
||||
if (index == 3) {
|
||||
final profileController = Get.find<ProfileController>();
|
||||
profileController.onInit();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建经典导航栏主体
|
||||
Widget _buildClassicBody(
|
||||
MainNavigationController controller,
|
||||
List<Widget> pages,
|
||||
bool isDark,
|
||||
List<BottomNavigationBarItem> items,
|
||||
) {
|
||||
return Column(
|
||||
children: [
|
||||
// 页面内容
|
||||
Expanded(
|
||||
child: IndexedStack(
|
||||
index: controller.currentIndex.value,
|
||||
children: pages,
|
||||
),
|
||||
),
|
||||
// 经典底部导航栏
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? const Color(0xFF2A2A2A) : Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: isDark ? 0.3 : 0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: BottomNavigationBar(
|
||||
currentIndex: controller.currentIndex.value,
|
||||
onTap: (index) {
|
||||
controller.switchPage(index);
|
||||
if (index == 3) {
|
||||
final profileController = Get.find<ProfileController>();
|
||||
profileController.onInit();
|
||||
}
|
||||
},
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: AppConstants.primaryColor,
|
||||
unselectedItemColor: isDark ? Colors.grey[400] : Colors.grey[600],
|
||||
backgroundColor: isDark ? const Color(0xFF2A2A2A) : Colors.white,
|
||||
elevation: 0,
|
||||
items: items,
|
||||
selectedFontSize: 12,
|
||||
unselectedFontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import '../constants/app_constants.dart';
|
||||
/// 时间: 2026-03-22
|
||||
/// 功能: 主导航内「标题 + Tab」共用 AppBar 构造
|
||||
/// 介绍: 压缩工具栏与 Tab 行高度,关闭 M3 卷动 surface tint,统一收藏页与发现页顶部观感
|
||||
/// 最新变化: 初始提取,减少两页相同结构的顶部留白
|
||||
/// 最新变化: 2026-04-02 支持深色模式
|
||||
|
||||
/// 主导航子页(IndexedStack 内)带 [TabBar] 的页面共用 [AppBar] 配置
|
||||
class TabbedNavAppBar {
|
||||
@@ -19,19 +19,26 @@ class TabbedNavAppBar {
|
||||
bool tabBarScrollable = false,
|
||||
EdgeInsetsGeometry? tabPadding,
|
||||
EdgeInsetsGeometry? tabLabelPadding,
|
||||
Color? backgroundColor,
|
||||
Color? foregroundColor,
|
||||
}) {
|
||||
final isDark = backgroundColor != null && backgroundColor != Colors.white;
|
||||
|
||||
return AppBar(
|
||||
title: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
color: Colors.black87,
|
||||
color: foregroundColor ?? (isDark ? Colors.white : Colors.black87),
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black87,
|
||||
iconTheme: const IconThemeData(color: Colors.black87),
|
||||
backgroundColor: backgroundColor ?? Colors.white,
|
||||
foregroundColor:
|
||||
foregroundColor ?? (isDark ? Colors.white : Colors.black87),
|
||||
iconTheme: IconThemeData(
|
||||
color: foregroundColor ?? (isDark ? Colors.white : Colors.black87),
|
||||
),
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
@@ -49,7 +56,7 @@ class TabbedNavAppBar {
|
||||
dividerColor: Colors.transparent,
|
||||
tabs: tabLabels.map((String e) => Tab(text: e)).toList(),
|
||||
labelColor: AppConstants.primaryColor,
|
||||
unselectedLabelColor: Colors.grey[600],
|
||||
unselectedLabelColor: isDark ? Colors.grey[400] : Colors.grey[600],
|
||||
indicator: UnderlineTabIndicator(
|
||||
borderSide: BorderSide(color: AppConstants.primaryColor, width: 3),
|
||||
),
|
||||
|
||||
212
lib/widgets/tap-liquid-glass.dart
Normal file
212
lib/widgets/tap-liquid-glass.dart
Normal file
@@ -0,0 +1,212 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../constants/app_constants.dart';
|
||||
import '../config/app_config.dart';
|
||||
import '../services/get/theme_controller.dart';
|
||||
import '../services/get/tap_liquid_glass_controller.dart';
|
||||
|
||||
class TapLiquidGlassNavigation extends StatelessWidget {
|
||||
final int currentIndex;
|
||||
final Function(int) onTap;
|
||||
|
||||
const TapLiquidGlassNavigation({
|
||||
super.key,
|
||||
required this.currentIndex,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final glassController = Get.find<TapLiquidGlassController>();
|
||||
|
||||
return Obx(() {
|
||||
final isDark = themeController.isDarkMode;
|
||||
final enableBlur = themeController.enableBlurEffect;
|
||||
final transparencyValues = glassController.transparencyValues;
|
||||
return _buildGlassBar(context, isDark, enableBlur, transparencyValues);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildGlassBar(
|
||||
BuildContext context,
|
||||
bool isDark,
|
||||
bool enableBlur,
|
||||
Map<String, double> transparencyValues,
|
||||
) {
|
||||
final backgroundOpacity = transparencyValues['backgroundOpacity']!;
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
left: AppConfig.liquidGlassHorizontalMargin,
|
||||
right: AppConfig.liquidGlassHorizontalMargin,
|
||||
bottom: AppConfig.liquidGlassBottomMargin,
|
||||
top: 8,
|
||||
),
|
||||
child: Container(
|
||||
height: AppConfig.liquidGlassHeight,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.liquidGlassCornerRadius,
|
||||
),
|
||||
boxShadow: backgroundOpacity > 0.2
|
||||
? [
|
||||
BoxShadow(
|
||||
color: isDark
|
||||
? Colors.black.withValues(alpha: 0.6)
|
||||
: Colors.black.withValues(alpha: 0.15),
|
||||
blurRadius: 35,
|
||||
spreadRadius: -10,
|
||||
offset: const Offset(0, 12),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.liquidGlassCornerRadius,
|
||||
),
|
||||
child: enableBlur && backgroundOpacity >= 0.01
|
||||
? BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: AppConfig.liquidGlassBlur,
|
||||
sigmaY: AppConfig.liquidGlassBlur,
|
||||
),
|
||||
child: _buildGlassContent(isDark, backgroundOpacity),
|
||||
)
|
||||
: _buildGlassContent(isDark, backgroundOpacity),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGlassContent(bool isDark, double backgroundOpacity) {
|
||||
return Stack(
|
||||
children: [
|
||||
if (backgroundOpacity >= 0.1)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? Colors.black.withValues(alpha: backgroundOpacity * 0.4)
|
||||
: Colors.white.withValues(alpha: backgroundOpacity),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.liquidGlassCornerRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (backgroundOpacity >= 0.15)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.liquidGlassCornerRadius,
|
||||
),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white.withValues(alpha: backgroundOpacity * 0.2)
|
||||
: Colors.black.withValues(alpha: backgroundOpacity * 0.1),
|
||||
width: 0.6,
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildNavItems(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNavItems() {
|
||||
final items = [
|
||||
_NavItem(Icons.home_rounded, '🏠', '主页'),
|
||||
_NavItem(Icons.explore_rounded, '🔍', '发现'),
|
||||
_NavItem(Icons.favorite_border_rounded, '❤️', '收藏'),
|
||||
_NavItem(Icons.person_outline_rounded, '👤', '个人'),
|
||||
];
|
||||
|
||||
return Row(
|
||||
children: List.generate(items.length, (index) {
|
||||
return Expanded(
|
||||
child: _buildNavItem(
|
||||
items[index],
|
||||
index == currentIndex,
|
||||
() => onTap(index),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNavItem(_NavItem item, bool isSelected, VoidCallback onTap) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
|
||||
return Obx(() {
|
||||
final isDark = themeController.isDarkMode;
|
||||
final enableAnimation = themeController.enableAnimation;
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.liquidGlassCornerRadius,
|
||||
),
|
||||
child: AnimatedContainer(
|
||||
duration: enableAnimation
|
||||
? const Duration(milliseconds: 250)
|
||||
: Duration.zero,
|
||||
curve: Curves.easeOutCubic,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
enableAnimation
|
||||
? AnimatedScale(
|
||||
scale: isSelected ? 1.1 : 1.0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: _buildIcon(item, isSelected, isDark),
|
||||
)
|
||||
: _buildIcon(item, isSelected, isDark),
|
||||
const SizedBox(height: 3),
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: enableAnimation
|
||||
? const Duration(milliseconds: 200)
|
||||
: Duration.zero,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
|
||||
color: isSelected
|
||||
? AppConstants.primaryColor
|
||||
: (isDark ? Colors.grey[400] : Colors.grey[600]),
|
||||
letterSpacing: 0.15,
|
||||
),
|
||||
child: Text(item.label),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildIcon(_NavItem item, bool isSelected, bool isDark) {
|
||||
return Icon(
|
||||
item.icon,
|
||||
size: 24,
|
||||
color: isSelected
|
||||
? AppConstants.primaryColor
|
||||
: (isDark ? Colors.grey[400] : Colors.grey[600]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NavItem {
|
||||
final IconData icon;
|
||||
final String emoji;
|
||||
final String label;
|
||||
|
||||
_NavItem(this.icon, this.emoji, this.label);
|
||||
}
|
||||
Reference in New Issue
Block a user