flutter_liquid_glass_nav_bar 1.0.1
flutter_liquid_glass_nav_bar: ^1.0.1 copied to clipboard
A VisionOS-grade liquid glass and spring physics bottom navigation bar for Flutter with zero external dependencies.
import 'package:flutter/material.dart';
import 'package:flutter_liquid_glass_nav_bar/flutter_liquid_glass_nav_bar.dart';
void main() {
runApp(const LiquidGlassNavBarExampleApp());
}
class LiquidGlassNavBarExampleApp extends StatefulWidget {
const LiquidGlassNavBarExampleApp({super.key});
@override
State<LiquidGlassNavBarExampleApp> createState() =>
_LiquidGlassNavBarExampleAppState();
}
class _LiquidGlassNavBarExampleAppState
extends State<LiquidGlassNavBarExampleApp> {
ThemeMode _themeMode = ThemeMode.system;
void _toggleTheme() {
setState(() {
_themeMode = _themeMode == ThemeMode.dark
? ThemeMode.light
: _themeMode == ThemeMode.light
? ThemeMode.system
: ThemeMode.dark;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Liquid Glass NavBar Demo',
debugShowCheckedModeBanner: false,
themeMode: _themeMode,
theme: ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6366F1),
brightness: Brightness.light,
),
extensions: const [
LiquidGlassNavBarTheme(
height: 58.0,
floatingOffset: 14.0,
enableShimmer: true,
glassBlurSigma: 15.0,
glassOpacity: 0.30,
),
],
),
darkTheme: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF818CF8),
brightness: Brightness.dark,
),
extensions: const [
LiquidGlassNavBarTheme(
height: 58.0,
floatingOffset: 14.0,
enableShimmer: true,
glassBlurSigma: 16.0,
glassOpacity: 0.25,
),
],
),
home: ExampleHomePage(
themeMode: _themeMode,
onToggleTheme: _toggleTheme,
),
);
}
}
class ExampleHomePage extends StatefulWidget {
final ThemeMode themeMode;
final VoidCallback onToggleTheme;
const ExampleHomePage({
super.key,
required this.themeMode,
required this.onToggleTheme,
});
@override
State<ExampleHomePage> createState() => _ExampleHomePageState();
}
class _ExampleHomePageState extends State<ExampleHomePage> {
int _currentIndex = 0;
int _unreadCount = 3;
// 🎛️ 实时调参实验室参数
double _glassBlurSigma = 15.0;
double _glassOpacity = 0.30;
double _pillBlurSigma = 10.0;
double _pillOpacity = 0.50;
double _height = 58.0;
double _floatingOffset = 14.0;
double _borderRadius = 29.0;
bool _enableShimmer = true;
bool _enableSpatialDepth = true;
bool _enableIconGlow = true;
bool _enableHaptics = true;
void _applyPreset(String name) {
setState(() {
switch (name) {
case 'vision_os':
_glassBlurSigma = 20.0;
_glassOpacity = 0.22;
_pillBlurSigma = 12.0;
_pillOpacity = 0.60;
_borderRadius = 32.0;
_enableShimmer = true;
_enableSpatialDepth = true;
break;
case 'frosted_deep':
_glassBlurSigma = 28.0;
_glassOpacity = 0.45;
_pillBlurSigma = 16.0;
_pillOpacity = 0.70;
_borderRadius = 20.0;
_enableShimmer = false;
_enableSpatialDepth = true;
break;
case 'minimal_clean':
_glassBlurSigma = 8.0;
_glassOpacity = 0.15;
_pillBlurSigma = 6.0;
_pillOpacity = 0.35;
_borderRadius = 28.0;
_enableShimmer = false;
_enableSpatialDepth = false;
break;
case 'default':
default:
_glassBlurSigma = 15.0;
_glassOpacity = 0.30;
_pillBlurSigma = 10.0;
_pillOpacity = 0.50;
_height = 58.0;
_floatingOffset = 14.0;
_borderRadius = 29.0;
_enableShimmer = true;
_enableSpatialDepth = true;
break;
}
});
}
void _openPlaygroundSheet() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return StatefulBuilder(
builder: (context, setModalState) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
height: MediaQuery.of(context).size.height * 0.72,
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(28)),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 20,
offset: Offset(0, -4),
),
],
),
child: Column(
children: [
const SizedBox(height: 12),
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(2),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 14),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'🎛️ 光学与物理调参实验室',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
TextButton(
onPressed: () {
_applyPreset('default');
setModalState(() {});
},
child: const Text('重置默认'),
),
],
),
),
const Divider(height: 1),
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 12),
children: [
const Text(
'🌟 风格预设 (Presets)',
style: TextStyle(
fontWeight: FontWeight.w600, fontSize: 13),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: [
ActionChip(
label: const Text('VisionOS 水晶'),
onPressed: () {
_applyPreset('vision_os');
setModalState(() {});
},
),
ActionChip(
label: const Text('深邃磨砂'),
onPressed: () {
_applyPreset('frosted_deep');
setModalState(() {});
},
),
ActionChip(
label: const Text('极简透光'),
onPressed: () {
_applyPreset('minimal_clean');
setModalState(() {});
},
),
],
),
const SizedBox(height: 16),
_buildSlider(
label: '底壳模糊度 (Glass Blur)',
value: _glassBlurSigma,
min: 0,
max: 30,
unit: 'px',
onChanged: (v) {
setState(() => _glassBlurSigma = v);
setModalState(() {});
},
),
_buildSlider(
label: '底壳透光度 (Glass Opacity)',
value: _glassOpacity,
min: 0.05,
max: 0.80,
unit: '',
onChanged: (v) {
setState(() => _glassOpacity = v);
setModalState(() {});
},
),
_buildSlider(
label: '胶囊滑块模糊度 (Pill Blur)',
value: _pillBlurSigma,
min: 0,
max: 25,
unit: 'px',
onChanged: (v) {
setState(() => _pillBlurSigma = v);
setModalState(() {});
},
),
_buildSlider(
label: '胶囊滑块透明度 (Pill Opacity)',
value: _pillOpacity,
min: 0.1,
max: 0.9,
unit: '',
onChanged: (v) {
setState(() => _pillOpacity = v);
setModalState(() {});
},
),
_buildSlider(
label: '导航栏高度 (Height)',
value: _height,
min: 48,
max: 72,
unit: 'px',
onChanged: (v) {
setState(() => _height = v);
setModalState(() {});
},
),
_buildSlider(
label: '底部悬浮间距 (Floating Offset)',
value: _floatingOffset,
min: 0,
max: 30,
unit: 'px',
onChanged: (v) {
setState(() => _floatingOffset = v);
setModalState(() {});
},
),
_buildSlider(
label: '底壳圆角 (Border Radius)',
value: _borderRadius,
min: 12,
max: 36,
unit: 'px',
onChanged: (v) {
setState(() => _borderRadius = v);
setModalState(() {});
},
),
const SizedBox(height: 8),
SwitchListTile(
title: const Text('45° 镜面反光流光扫描 (Shimmer)'),
value: _enableShimmer,
onChanged: (v) {
setState(() => _enableShimmer = v);
setModalState(() {});
},
),
SwitchListTile(
title: const Text('3D 空间纵深图标上浮 (Spatial Depth)'),
value: _enableSpatialDepth,
onChanged: (v) {
setState(() => _enableSpatialDepth = v);
setModalState(() {});
},
),
SwitchListTile(
title: const Text('选中图标主题色悬浮柔光 (Icon Glow)'),
value: _enableIconGlow,
onChanged: (v) {
setState(() => _enableIconGlow = v);
setModalState(() {});
},
),
SwitchListTile(
title: const Text('物理触觉振动反馈 (Haptics)'),
value: _enableHaptics,
onChanged: (v) {
setState(() => _enableHaptics = v);
setModalState(() {});
},
),
const SizedBox(height: 24),
],
),
),
],
),
);
},
);
},
);
}
Widget _buildSlider({
required String label,
required double value,
required double min,
required double max,
required String unit,
required ValueChanged<double> onChanged,
}) {
final display = unit.isEmpty
? value.toStringAsFixed(2)
: '${value.toStringAsFixed(1)}$unit';
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontSize: 13)),
Text(
display,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
color: Color(0xFF6366F1),
),
),
],
),
Slider(
value: value,
min: min,
max: max,
activeColor: const Color(0xFF6366F1),
onChanged: onChanged,
),
],
),
);
}
@override
Widget build(BuildContext context) {
final navItems = [
const LiquidGlassNavBarItem(
icon: Icons.home_outlined,
activeIcon: Icons.home_rounded,
label: '精选',
),
LiquidGlassNavBarItem(
// 多态图标演示:自定义 Widget 渐变微图标
icon: Container(
width: 22,
height: 22,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: Colors.grey.withValues(alpha: 0.6), width: 1.5),
),
child: const Icon(Icons.explore_outlined, size: 14),
),
activeIcon: Container(
width: 22,
height: 22,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
gradient: const LinearGradient(
colors: [Color(0xFF6366F1), Color(0xFFA855F7)],
),
),
child:
const Icon(Icons.explore_rounded, size: 14, color: Colors.white),
),
label: '探索',
),
LiquidGlassNavBarItem(
icon: Icons.chat_bubble_outline_rounded,
activeIcon: Icons.chat_bubble_rounded,
label: '消息',
badgeCount: _unreadCount,
),
LiquidGlassNavBarItem(
icon: Icons.person_outline_rounded,
builder: (context, isSelected) => AnimatedScale(
scale: isSelected ? 1.15 : 1.0,
duration: const Duration(milliseconds: 200),
child: Icon(
isSelected ? Icons.person_rounded : Icons.person_outline_rounded,
),
),
label: '我的',
showBadgeDot: true,
),
];
return Scaffold(
extendBody: true, // 核心:毛玻璃悬浮于页面内容上方
appBar: AppBar(
title: const Text(
'Liquid Glass NavBar',
style: TextStyle(fontWeight: FontWeight.w700),
),
actions: [
IconButton(
tooltip: '调参实验室',
icon: const Icon(Icons.tune_rounded),
onPressed: _openPlaygroundSheet,
),
IconButton(
tooltip: '切换主题模式',
icon: Icon(
widget.themeMode == ThemeMode.dark
? Icons.dark_mode_rounded
: widget.themeMode == ThemeMode.light
? Icons.light_mode_rounded
: Icons.brightness_auto_rounded,
),
onPressed: widget.onToggleTheme,
),
],
),
body: IndexedStack(
index: _currentIndex,
children: [
_buildFeedTab(),
_buildExploreTab(),
_buildMessagesTab(),
_buildProfileTab(),
],
),
bottomNavigationBar: LiquidGlassNavBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
glassBlurSigma: _glassBlurSigma,
glassOpacity: _glassOpacity,
pillBlurSigma: _pillBlurSigma,
pillOpacity: _pillOpacity,
height: _height,
floatingOffset: _floatingOffset,
borderRadius: _borderRadius,
enableShimmer: _enableShimmer,
enableSpatialDepth: _enableSpatialDepth,
enableIconGlow: _enableIconGlow,
enableHaptics: _enableHaptics,
items: navItems,
),
);
}
/// Tab 0: 沉浸式内容滚动流 (直观体验穿透毛玻璃的折射质感)
Widget _buildFeedTab() {
final isDark = Theme.of(context).brightness == Brightness.dark;
final cardGradients = [
[const Color(0xFF6366F1), const Color(0xFF8B5CF6)],
[const Color(0xFFEC4899), const Color(0xFFF43F5E)],
[const Color(0xFF10B981), const Color(0xFF059669)],
[const Color(0xFFF59E0B), const Color(0xFFD97706)],
[const Color(0xFF3B82F6), const Color(0xFF1D4ED8)],
[const Color(0xFF8B5CF6), const Color(0xFF6D28D9)],
];
return ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
itemCount: cardGradients.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: (isDark ? Colors.white : Colors.black)
.withValues(alpha: 0.08),
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFF6366F1).withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.auto_awesome_rounded,
color: Color(0xFF6366F1),
size: 20,
),
),
const SizedBox(width: 12),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'向下滑动体验毛玻璃穿透折射',
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 15),
),
Text(
'extendBody: true 使内容在底栏后方若隐若现',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
],
),
),
],
),
],
),
);
}
final gradient = cardGradients[index - 1];
return Container(
height: 140,
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: gradient,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(22),
boxShadow: [
BoxShadow(
color: gradient.first.withValues(alpha: 0.35),
blurRadius: 16,
offset: const Offset(0, 6),
),
],
),
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'VisionOS 卡片 #$index',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.25),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'光学折射层',
style: TextStyle(color: Colors.white, fontSize: 11),
),
),
],
),
const Text(
'当此卡片滑至屏幕最底部时,观察毛玻璃透镜内部的光线折射与 120Hz 弹簧形变',
style: TextStyle(color: Colors.white70, fontSize: 12),
),
],
),
);
},
);
}
/// Tab 1: 探索网格流
Widget _buildExploreTab() {
return GridView.builder(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 14,
mainAxisSpacing: 14,
childAspectRatio: 0.85,
),
itemCount: 8,
itemBuilder: (context, index) {
final colors = [
[const Color(0xFF38BDF8), const Color(0xFF0284C7)],
[const Color(0xFFF472B6), const Color(0xFFDB2777)],
[const Color(0xFF4ADE80), const Color(0xFF16A34A)],
[const Color(0xFFA78BFA), const Color(0xFF7C3AED)],
[const Color(0xFFFBBF24), const Color(0xFFD97706)],
[const Color(0xFF2DD4BF), const Color(0xFF0D9488)],
[const Color(0xFFFB7185), const Color(0xFFE11D48)],
[const Color(0xFF818CF8), const Color(0xFF4F46E5)],
];
final gradient = colors[index % colors.length];
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: gradient),
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CircleAvatar(
backgroundColor: Colors.white.withValues(alpha: 0.25),
child: const Icon(
Icons.explore_rounded,
color: Colors.white,
size: 20,
),
),
Text(
'探索项目 #${index + 1}',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
);
},
);
}
/// Tab 2: 消息与徽标互动
Widget _buildMessagesTab() {
final isDark = Theme.of(context).brightness == Brightness.dark;
return ListView(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
const Text(
'🎈 液态徽标 (Liquid Badge) 互动',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FilledButton.tonalIcon(
icon: const Icon(Icons.remove_rounded),
label: const Text('减少未读'),
onPressed: () {
setState(() {
if (_unreadCount > 0) _unreadCount--;
});
},
),
const SizedBox(width: 14),
FilledButton.icon(
icon: const Icon(Icons.add_rounded),
label: const Text('增加未读'),
onPressed: () {
setState(() {
_unreadCount++;
});
},
),
],
),
],
),
),
const SizedBox(height: 16),
...List.generate(
6,
(i) => ListTile(
leading: CircleAvatar(
backgroundColor: const Color(0xFF6366F1).withValues(alpha: 0.2),
child: Text('${i + 1}'),
),
title: Text('消息通知 #${i + 1}'),
subtitle: const Text('滑动查看底部导航栏高斯模糊穿透效果'),
trailing: const Icon(Icons.chevron_right_rounded),
),
),
],
);
}
/// Tab 3: 个人中心
Widget _buildProfileTab() {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
return ListView(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
children: [
Center(
child: Column(
children: [
const SizedBox(height: 16),
CircleAvatar(
radius: 46,
backgroundColor:
const Color(0xFF6366F1).withValues(alpha: 0.18),
child: const Icon(Icons.person_rounded,
size: 48, color: Color(0xFF6366F1)),
),
const SizedBox(height: 14),
const Text(
'Flutter Developer',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const Text(
'VisionOS Liquid Glass UI Toolkit',
style: TextStyle(color: Colors.grey),
),
],
),
),
const SizedBox(height: 28),
Container(
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
ListTile(
leading:
const Icon(Icons.tune_rounded, color: Color(0xFF6366F1)),
title: const Text('打开参数调参实验室'),
trailing: const Icon(Icons.chevron_right_rounded),
onTap: _openPlaygroundSheet,
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.palette_outlined,
color: Color(0xFFA855F7)),
title: const Text('切换深色/浅色模式'),
trailing: const Icon(Icons.chevron_right_rounded),
onTap: widget.onToggleTheme,
),
],
),
),
],
);
}
}