Flutter for OpenHarmony构建全功能视差侧滑菜单系统:从动效设计到多页面导航的完整实践

Flutter for OpenHarmony构建全功能视差侧滑菜单系统:从动效设计到多页面导航的完整实践

在现代移动应用开发中,导航体验早已超越简单的页面跳转,演变为融合动效、深度感与交互反馈 的综合艺术。本文将深入剖析一段完整的

Flutter 代码,展示如何构建一个具备视差背景、毛玻璃菜单、多级页面导航和精细交互动画 的现代化应用系统------它不仅是一个 UI

组件库,更是一套完整的用户体验范式。


完整效果



一、整体架构:模块化与路由驱动

1. 声明式路由系统

dart 复制代码
routes: {
  '/home': (context) => const HomePage(),
  '/favorites': (context) => const FavoritesPage(),
  '/history': (context) => const HistoryPage(),
  '/notifications': (context) => const NotificationsPage(),
  '/help': (context) => const HelpPage(),
}
  • 解耦设计:每个页面独立为 StatelessWidget/StatefulWidget;
  • 类型安全:通过命名路由避免硬编码字符串错误;
  • 可扩展性:新增页面只需添加路由映射。

2. 三层核心结构

  • 主壳层(HomePage):管理全局状态(菜单开合、动画控制器);
  • 子页面(FavoritesPage 等):专注业务逻辑与数据展示;
  • 共享主题:Material 3 深色主题贯穿所有页面。

💡 这种架构实现了关注点分离:动效逻辑集中在 HomePage,内容逻辑分散在各子页面。


二、高级动效系统详解

1. 双控制器协同动画

dart 复制代码
// 主动画控制器(400ms)
_animationController = AnimationController(duration: 400ms);
// 图标旋转控制器(300ms)
_rotationController = AnimationController(duration: 300ms);

// 动画组合
_slideAnimation = Tween(begin:0, end:1).animate(CurvedAnimation(
  parent: _animationController,
  curve: Curves.easeInOutCubic // 平滑加速/减速
));
  • 时间差设计:图标旋转快于菜单滑入,提供即时反馈;
  • 曲线优化easeInOutCubic 比线性动画更符合物理直觉。

2. 视差背景效果

dart 复制代码
double parallaxOffset = -_slideAnimation.value * 80;
Transform.translate(
  offset: Offset(parallaxOffset, 0),
  child: Transform.scale(scale: _scaleAnimation.value, ...)
)
  • 反向位移:菜单右滑时背景左移,模拟"景深";
  • 同步缩放:背景缩小至 90%,强化"远离"视觉暗示。

3. 智能遮罩层

dart 复制代码
if (_isMenuOpen)
  GestureDetector(
    onTap: _toggleMenu, // 点击任意位置关闭
    child: Container(color: Colors.black.withAlpha(0.4 * fadeValue))
  )
  • 条件渲染:仅菜单打开时创建,节省资源;
  • 手势穿透:确保遮罩区域可点击。

三、毛玻璃菜单与交互动效

1. Frosted Glass 实现

dart 复制代码
BackdropFilter(
  filter: ui.ImageFilter.blur(sigmaX: 20, sigmaY: 20),
  child: Container(
    decoration: BoxDecoration(
      gradient: [surface.withAlpha(0.95), surface.withAlpha(0.9)],
      boxShadow: [BoxShadow(blurRadius: 30, offset: Offset(-5, 0))]
    )
  )
)
  • 模糊强度sigma=20 提供柔和虚化而不失背景纹理;
  • 色彩叠加:半透明深色渐变确保文字可读性;
  • 动态阴影:左侧发光增强"浮出"感。

2. 菜单项微交互

dart 复制代码
InkWell(
  borderRadius: BorderRadius.circular(12),
  child: Container(
    padding: EdgeInsets.all(16),
    decoration: BoxDecoration(borderRadius: ...),
    child: Row(children: [
      // 图标容器(浅色背景突出)
      Container(decoration: primaryContainer.withAlpha(0.3), child: Icon(...)),
      Text(title),
      Icon(Icons.chevron_right) // 引导性箭头
    ])
  )
)
  • 视觉引导:右侧箭头暗示"可操作";
  • 色彩呼应 :图标容器使用 primaryContainer 色系。

3. 汉堡图标动画

dart 复制代码
IconButton(
  icon: AnimatedIcon(
    icon: AnimatedIcons.menu_close,
    progress: _animationController,
  ),
  onPressed: _toggleMenu,
)
  • 内置动画:自动处理 → × 的平滑过渡;
  • 色彩统一:白色图标与紫色渐变按钮形成高对比度。

四、五大子页面深度解析

1. 收藏页(FavoritesPage)

  • 卡片式布局:每项带双色渐变图标;
  • 数据驱动 :静态列表 _favoriteItems 包含标题/副标题/颜色;
  • 视觉层次:图标 > 标题 > 副标题 > 收藏按钮。

2. 历史页(HistoryPage)

  • 时间分组:按"今天/昨天/更早"分类;
  • 日期标题:灰色小字区分时间段;
  • 操作集成:右上角"清空历史"弹出确认对话框。

3. 通知页(NotificationsPage)

  • 状态管理_NotificationItem 类包含 isRead 字段;
  • 筛选系统:顶部 FilterChip 切换"全部/未读";
  • 未读标记:红色圆点 + 加粗标题双重提示;
  • 交互反馈:点击通知自动标记为已读。

4. 帮助页(HelpPage)

  • 搜索集成:顶部 TextField 支持问题搜索;
  • FAQ 展开:ExpansionTile 实现问答折叠;
  • 联系渠道:电话/邮件/在线客服三重支持;
  • 关于信息:版本号 + 应用描述卡片。

五、交互细节打磨

1. 即时反馈系统

  • SnackBar 提示:操作后底部弹出轻量提示;
  • Dialog 确认:危险操作(如清空历史)需二次确认;
  • 页面返回:所有子页面 AppBar 含返回按钮。

2. 响应式安全区

dart 复制代码
Positioned(
  top: MediaQuery.of(context).padding.top + 16, // 避开状态栏
  left: 16,
  child: ...
)
  • 刘海屏兼容:动态获取系统状态栏高度;
  • 边缘留白 :左右 16px 内边距符合 Material Design 规范。

3. 主题一致性

  • 种子色系统Colors.deepPurple 自动派生完整色板;
  • 深色模式原生支持:所有组件自动适配深色背景;
  • 文本层级onSurface / onSurfaceVariant 确保可读性。

六、性能与可维护性

1. 动画资源管理

dart 复制代码
@override
void dispose() {
  _animationController.dispose();
  _rotationController.dispose();
  super.dispose();
}
  • 内存安全:及时释放动画控制器,避免内存泄漏。

2. 组件化设计

  • _buildMenuItem():复用菜单项模板;
  • _buildSettingOption():标准化设置选项;
  • 单一职责:每个方法只负责特定 UI 片段。

3. 高效重绘

  • AnimatedBuilder 仅重建动画相关子树;
  • 条件渲染 (if (_isMenuOpen)) 避免无用 widget 创建;
  • 子页面使用 StatelessWidget 减少状态管理开销。

七、扩展可能性

  1. 手势滑动支持

    添加 Draggable 区域,支持从左侧边缘滑出菜单。

  2. 动态数据加载

    将菜单项和子页面数据改为 API 驱动,支持远程配置。

  3. 主题切换

    在设置面板中添加浅色/深色模式切换开关。

  4. 国际化支持

    集成 flutter_localizations 实现多语言。

  5. 状态持久化

    使用 shared_preferences 保存通知阅读状态。


🌐 加入社区

欢迎加入 开源鸿蒙跨平台开发者社区 ,获取最新资源与技术支持:

👉 开源鸿蒙跨平台开发者社区
完整代码

bash 复制代码
import 'dart:ui' as ui;
import 'package:flutter/material.dart';

void main() {
  runApp(const ParallaxApp());
}

class ParallaxApp extends StatelessWidget {
  const ParallaxApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '视差侧滑菜单',
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.deepPurple,
          brightness: Brightness.dark,
        ),
      ),
      routes: {
        '/home': (context) => const HomePage(),
        '/favorites': (context) => const FavoritesPage(),
        '/history': (context) => const HistoryPage(),
        '/notifications': (context) => const NotificationsPage(),
        '/help': (context) => const HelpPage(),
      },
      home: const HomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
  bool _isMenuOpen = false;
  final double _menuWidth = 280.0;

  // 控制器
  late AnimationController _animationController;
  late AnimationController _rotationController;
  late Animation<double> _slideAnimation;
  late Animation<double> _scaleAnimation;
  late Animation<double> _fadeAnimation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 400),
    );

    _rotationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );

    _slideAnimation = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(
        parent: _animationController,
        curve: Curves.easeInOutCubic,
      ),
    );

    _scaleAnimation = Tween<double>(begin: 1, end: 0.9).animate(
      CurvedAnimation(
        parent: _animationController,
        curve: Curves.easeInOut,
      ),
    );

    _fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(parent: _animationController, curve: Curves.easeIn),
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    _rotationController.dispose();
    super.dispose();
  }

  void _toggleMenu() {
    if (_isMenuOpen) {
      _animationController.reverse();
      _rotationController.reverse();
    } else {
      _animationController.forward();
      _rotationController.forward();
    }
    _isMenuOpen = !_isMenuOpen;
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Scaffold(
      body: Stack(
        children: [
          // 背景图片 (带有视差效果)
          AnimatedBuilder(
            animation: _slideAnimation,
            builder: (context, child) {
              // 视差系数:菜单开得越大,背景移动越多
              double parallaxOffset = -_slideAnimation.value * 80;

              return Transform.translate(
                offset: Offset(parallaxOffset, 0),
                child: Transform.scale(
                  scale: _scaleAnimation.value,
                  child: Container(
                    decoration: BoxDecoration(
                      gradient: LinearGradient(
                        begin: Alignment.topLeft,
                        end: Alignment.bottomRight,
                        colors: [
                          theme.colorScheme.surface,
                          theme.colorScheme.surface.withValues(alpha: 0.8),
                          theme.colorScheme.primaryContainer
                              .withValues(alpha: 0.3),
                        ],
                      ),
                    ),
                    child: Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Icon(
                            Icons.menu_open_rounded,
                            size: 80,
                            color: theme.colorScheme.primary
                                .withValues(alpha: 0.5),
                          ),
                          const SizedBox(height: 24),
                          Text(
                            '视差侧滑菜单',
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              fontSize: 32,
                              fontWeight: FontWeight.bold,
                              color: theme.colorScheme.primary
                                  .withValues(alpha: 0.8),
                            ),
                          ),
                          const SizedBox(height: 12),
                          Text(
                            '点击左上角按钮打开菜单',
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              fontSize: 16,
                              color: theme.colorScheme.onSurface
                                  .withValues(alpha: 0.6),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              );
            },
          ),

          // 侧边栏菜单
          AnimatedBuilder(
            animation: _slideAnimation,
            builder: (context, child) {
              // 计算滑入时的偏移和缩放
              final slideOffset = (1 - _slideAnimation.value) * 100;

              return Transform.translate(
                offset: Offset(slideOffset, 0),
                child: ClipRRect(
                  borderRadius: const BorderRadius.only(
                    bottomRight: Radius.circular(20),
                    topRight: Radius.circular(20),
                  ),
                  child: BackdropFilter(
                    filter: ui.ImageFilter.blur(sigmaX: 20, sigmaY: 20),
                    child: Container(
                      width: _menuWidth,
                      decoration: BoxDecoration(
                        gradient: LinearGradient(
                          begin: Alignment.topLeft,
                          end: Alignment.bottomRight,
                          colors: [
                            theme.colorScheme.surface.withValues(alpha: 0.95),
                            theme.colorScheme.surface.withValues(alpha: 0.9),
                          ],
                        ),
                        boxShadow: [
                          BoxShadow(
                            color: Colors.black.withValues(alpha: 0.3),
                            blurRadius: 30,
                            offset: const Offset(-5, 0),
                          ),
                        ],
                      ),
                      child: ListView(
                        padding: const EdgeInsets.symmetric(vertical: 20),
                        children: [
                          // 用户头像区域
                          Padding(
                            padding: const EdgeInsets.symmetric(
                                horizontal: 20, vertical: 10),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                Container(
                                  decoration: BoxDecoration(
                                    shape: BoxShape.circle,
                                    gradient: LinearGradient(
                                      colors: [
                                        theme.colorScheme.primary,
                                        theme.colorScheme.secondary,
                                      ],
                                    ),
                                    boxShadow: [
                                      BoxShadow(
                                        color: theme.colorScheme.primary
                                            .withValues(alpha: 0.5),
                                        blurRadius: 20,
                                        offset: const Offset(0, 5),
                                      ),
                                    ],
                                  ),
                                  child: const CircleAvatar(
                                    radius: 45,
                                    backgroundColor: Colors.transparent,
                                    child: Icon(
                                      Icons.person_rounded,
                                      size: 60,
                                      color: Colors.white,
                                    ),
                                  ),
                                ),
                                const SizedBox(height: 16),
                                const Text(
                                  '欢迎回来',
                                  style: TextStyle(
                                    fontSize: 14,
                                    color: Colors.grey,
                                  ),
                                ),
                                const SizedBox(height: 4),
                                Text(
                                  'Flutter 开发者',
                                  style: TextStyle(
                                    fontSize: 20,
                                    fontWeight: FontWeight.bold,
                                    color: theme.colorScheme.onSurface,
                                  ),
                                ),
                                const SizedBox(height: 8),
                                Container(
                                  padding: const EdgeInsets.symmetric(
                                    horizontal: 12,
                                    vertical: 4,
                                  ),
                                  decoration: BoxDecoration(
                                    color: Colors.green.withValues(alpha: 0.2),
                                    borderRadius: BorderRadius.circular(12),
                                    border: Border.all(
                                      color:
                                          Colors.green.withValues(alpha: 0.5),
                                      width: 1,
                                    ),
                                  ),
                                  child: Row(
                                    mainAxisSize: MainAxisSize.min,
                                    children: [
                                      Container(
                                        width: 8,
                                        height: 8,
                                        decoration: const BoxDecoration(
                                          color: Colors.green,
                                          shape: BoxShape.circle,
                                        ),
                                      ),
                                      const SizedBox(width: 6),
                                      const Text(
                                        '在线',
                                        style: TextStyle(
                                          fontSize: 12,
                                          color: Colors.green,
                                          fontWeight: FontWeight.w500,
                                        ),
                                      ),
                                    ],
                                  ),
                                ),
                              ],
                            ),
                          ),
                          const Divider(height: 30),
                          // 菜单项
                          ..._buildMenuItems(theme),
                          const Divider(height: 30),
                          // 设置按钮
                          _buildMenuItem(
                            icon: Icons.settings_rounded,
                            title: '设置',
                            theme: theme,
                            onTap: () {
                              _showSettingsBottomSheet(context);
                            },
                          ),
                          const SizedBox(height: 20),
                        ],
                      ),
                    ),
                  ),
                ),
              );
            },
          ),

          // 主内容区域的遮罩层 (当菜单打开时)
          if (_isMenuOpen)
            AnimatedBuilder(
              animation: _fadeAnimation,
              builder: (context, child) {
                return GestureDetector(
                  onTap: _toggleMenu,
                  child: Container(
                    color: Colors.black
                        .withValues(alpha: 0.4 * _fadeAnimation.value),
                  ),
                );
              },
            ),

          // 浮动操作按钮 (用于打开菜单)
          Positioned(
            top: MediaQuery.of(context).padding.top + 16,
            left: 16,
            child: Material(
              elevation: 8,
              borderRadius: BorderRadius.circular(16),
              child: Container(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [
                      theme.colorScheme.primary,
                      theme.colorScheme.secondary,
                    ],
                  ),
                  borderRadius: BorderRadius.circular(16),
                  boxShadow: [
                    BoxShadow(
                      color: theme.colorScheme.primary.withValues(alpha: 0.5),
                      blurRadius: 20,
                      offset: const Offset(0, 8),
                    ),
                  ],
                ),
                child: IconButton(
                  icon: AnimatedIcon(
                    icon: AnimatedIcons.menu_close,
                    progress: _animationController,
                    color: Colors.white,
                  ),
                  onPressed: _toggleMenu,
                  iconSize: 28,
                  padding: const EdgeInsets.all(12),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  List<Widget> _buildMenuItems(ThemeData theme) {
    final menuItems = [
      {'icon': Icons.home_rounded, 'title': '首页', 'route': '/home'},
      {'icon': Icons.favorite_rounded, 'title': '收藏', 'route': '/favorites'},
      {'icon': Icons.history_rounded, 'title': '历史', 'route': '/history'},
      {
        'icon': Icons.notifications_rounded,
        'title': '通知',
        'route': '/notifications'
      },
      {'icon': Icons.help_rounded, 'title': '帮助', 'route': '/help'},
    ];

    return menuItems.map((item) {
      return _buildMenuItem(
        icon: item['icon'] as IconData,
        title: item['title'] as String,
        theme: theme,
        onTap: () {
          Navigator.pushNamed(context, item['route'] as String);
          _toggleMenu();
        },
      );
    }).toList();
  }

  Widget _buildMenuItem({
    required IconData icon,
    required String title,
    required ThemeData theme,
    required VoidCallback onTap,
  }) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          onTap: onTap,
          borderRadius: BorderRadius.circular(12),
          child: Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(12),
            ),
            child: Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: theme.colorScheme.primaryContainer
                        .withValues(alpha: 0.3),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    icon,
                    size: 22,
                    color: theme.colorScheme.primary,
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Text(
                    title,
                    style: TextStyle(
                      fontSize: 15,
                      fontWeight: FontWeight.w500,
                      color: theme.colorScheme.onSurface,
                    ),
                  ),
                ),
                Icon(
                  Icons.chevron_right_rounded,
                  size: 20,
                  color: theme.colorScheme.onSurface.withValues(alpha: 0.4),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  void _showSettingsBottomSheet(BuildContext context) {
    showModalBottomSheet(
      context: context,
      backgroundColor: Colors.transparent,
      isScrollControlled: true,
      builder: (context) => Container(
        decoration: BoxDecoration(
          color: Theme.of(context).colorScheme.surface,
          borderRadius: const BorderRadius.only(
            topLeft: Radius.circular(24),
            topRight: Radius.circular(24),
          ),
        ),
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              width: 40,
              height: 4,
              decoration: BoxDecoration(
                color: Colors.grey.withValues(alpha: 0.3),
                borderRadius: BorderRadius.circular(2),
              ),
            ),
            const SizedBox(height: 20),
            const Text(
              '设置',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 20),
            _buildSettingOption(
              icon: Icons.notifications_active_rounded,
              title: '通知',
              subtitle: '启用推送通知',
              theme: Theme.of(context),
            ),
            _buildSettingOption(
              icon: Icons.dark_mode_rounded,
              title: '深色模式',
              subtitle: '使用深色主题',
              theme: Theme.of(context),
            ),
            _buildSettingOption(
              icon: Icons.language_rounded,
              title: '语言',
              subtitle: '简体中文',
              theme: Theme.of(context),
            ),
            const SizedBox(height: 20),
          ],
        ),
      ),
    );
  }

  Widget _buildSettingOption({
    required IconData icon,
    required String title,
    required String subtitle,
    required ThemeData theme,
  }) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16),
      child: Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: theme.colorScheme.surfaceContainerHighest,
          borderRadius: BorderRadius.circular(16),
        ),
        child: Row(
          children: [
            Container(
              padding: const EdgeInsets.all(10),
              decoration: BoxDecoration(
                color: theme.colorScheme.primary.withValues(alpha: 0.1),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Icon(
                icon,
                color: theme.colorScheme.primary,
              ),
            ),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    title,
                    style: const TextStyle(
                      fontWeight: FontWeight.w600,
                      fontSize: 15,
                    ),
                  ),
                  Text(
                    subtitle,
                    style: TextStyle(
                      color: Colors.grey,
                      fontSize: 13,
                    ),
                  ),
                ],
              ),
            ),
            Switch(
              value: true,
              onChanged: (value) {},
            ),
          ],
        ),
      ),
    );
  }
}

// ============ 首页 ============
class FavoritesPage extends StatelessWidget {
  const FavoritesPage({super.key});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_rounded),
          onPressed: () => Navigator.pop(context),
        ),
        title: const Text('我的收藏'),
        centerTitle: true,
      ),
      body: ListView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: _favoriteItems.length,
        itemBuilder: (context, index) {
          final item = _favoriteItems[index];
          return Card(
            margin: const EdgeInsets.only(bottom: 12),
            elevation: 2,
            child: ListTile(
              leading: Container(
                width: 56,
                height: 56,
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [
                      item['color1'] as Color,
                      item['color2'] as Color,
                    ],
                  ),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Icon(
                  item['icon'] as IconData,
                  color: Colors.white,
                ),
              ),
              title: Text(
                item['title'] as String,
                style: const TextStyle(fontWeight: FontWeight.bold),
              ),
              subtitle: Text(item['subtitle'] as String),
              trailing: IconButton(
                icon: const Icon(Icons.bookmark_rounded),
                color: theme.colorScheme.primary,
                onPressed: () {},
              ),
            ),
          );
        },
      ),
    );
  }

  static final List<Map<String, dynamic>> _favoriteItems = [
    {
      'icon': Icons.code_rounded,
      'title': 'Flutter 开发指南',
      'subtitle': '深入学习 Flutter 框架',
      'color1': Colors.blue,
      'color2': Colors.purple,
    },
    {
      'icon': Icons.api_rounded,
      'title': 'API 接口文档',
      'subtitle': 'RESTful API 设计规范',
      'color1': Colors.orange,
      'color2': Colors.red,
    },
    {
      'icon': Icons.design_services_rounded,
      'title': 'UI/UX 设计原则',
      'subtitle': 'Material Design 3.0',
      'color1': Colors.green,
      'color2': Colors.teal,
    },
    {
      'icon': Icons.storage_rounded,
      'title': '数据库优化',
      'subtitle': 'SQL 和 NoSQL 最佳实践',
      'color1': Colors.pink,
      'color2': Colors.purple,
    },
    {
      'icon': Icons.cloud_upload_rounded,
      'title': '云端部署',
      'subtitle': 'CI/CD 流程自动化',
      'color1': Colors.cyan,
      'color2': Colors.blue,
    },
  ];
}

// ============ 历史页面 ============
class HistoryPage extends StatelessWidget {
  const HistoryPage({super.key});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_rounded),
          onPressed: () => Navigator.pop(context),
        ),
        title: const Text('浏览历史'),
        actions: [
          IconButton(
            icon: const Icon(Icons.delete_outline_rounded),
            onPressed: () {
              showDialog(
                context: context,
                builder: (context) => AlertDialog(
                  title: const Text('清空历史'),
                  content: const Text('确定要清空所有浏览历史吗?'),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('取消'),
                    ),
                    FilledButton(
                      onPressed: () {
                        Navigator.pop(context);
                        ScaffoldMessenger.of(context).showSnackBar(
                          const SnackBar(content: Text('历史记录已清空')),
                        );
                      },
                      child: const Text('确定'),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
        centerTitle: true,
      ),
      body: ListView(
        children: [
          _buildDateHeader('今天'),
          ..._historyItems.where((item) => item['day'] == 'today').map((item) {
            return _buildHistoryItem(item, theme);
          }),
          _buildDateHeader('昨天'),
          ..._historyItems
              .where((item) => item['day'] == 'yesterday')
              .map((item) {
            return _buildHistoryItem(item, theme);
          }),
          _buildDateHeader('更早'),
          ..._historyItems
              .where((item) => item['day'] == 'earlier')
              .map((item) {
            return _buildHistoryItem(item, theme);
          }),
        ],
      ),
    );
  }

  Widget _buildDateHeader(String date) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 24, 16, 8),
      child: Text(
        date,
        style: const TextStyle(
          fontSize: 14,
          fontWeight: FontWeight.bold,
          color: Colors.grey,
        ),
      ),
    );
  }

  Widget _buildHistoryItem(Map<String, dynamic> item, ThemeData theme) {
    return ListTile(
      leading: Container(
        width: 48,
        height: 48,
        decoration: BoxDecoration(
          color: (item['color'] as Color).withValues(alpha: 0.2),
          borderRadius: BorderRadius.circular(10),
        ),
        child: Icon(
          item['icon'] as IconData,
          color: item['color'] as Color,
        ),
      ),
      title: Text(item['title'] as String),
      subtitle: Text(item['time'] as String),
      trailing: Icon(
        Icons.chevron_right_rounded,
        color: theme.colorScheme.onSurface.withValues(alpha: 0.4),
      ),
      onTap: () {
        // 导航到对应页面
      },
    );
  }

  static final List<Map<String, dynamic>> _historyItems = [
    {
      'day': 'today',
      'icon': Icons.article_rounded,
      'title': '项目文档',
      'time': '10:30',
      'color': Colors.blue
    },
    {
      'day': 'today',
      'icon': Icons.chat_rounded,
      'title': '团队会议',
      'time': '09:15',
      'color': Colors.green
    },
    {
      'day': 'today',
      'icon': Icons.code_rounded,
      'title': '代码审查',
      'time': '08:45',
      'color': Colors.orange
    },
    {
      'day': 'yesterday',
      'icon': Icons.mail_rounded,
      'title': '邮件通知',
      'time': '16:20',
      'color': Colors.purple
    },
    {
      'day': 'yesterday',
      'icon': Icons.folder_rounded,
      'title': '资源库',
      'time': '14:30',
      'color': Colors.red
    },
    {
      'day': 'yesterday',
      'icon': Icons.image_rounded,
      'title': '设计稿',
      'time': '11:00',
      'color': Colors.cyan
    },
    {
      'day': 'earlier',
      'icon': Icons.video_library_rounded,
      'title': '培训视频',
      'time': '3天前',
      'color': Colors.pink
    },
    {
      'day': 'earlier',
      'icon': Icons.download_rounded,
      'title': '文件下载',
      'time': '5天前',
      'color': Colors.teal
    },
  ];
}

// ============ 通知页面 ============
class NotificationsPage extends StatefulWidget {
  const NotificationsPage({super.key});

  @override
  State<NotificationsPage> createState() => _NotificationsPageState();
}

class _NotificationsPageState extends State<NotificationsPage> {
  bool _filterAll = true;
  bool _filterUnread = false;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_rounded),
          onPressed: () => Navigator.pop(context),
        ),
        title: const Text('消息通知'),
        centerTitle: true,
        actions: [
          TextButton.icon(
            onPressed: () {
              setState(() {
                _notifications.clear();
                _notifications.addAll([
                  _NotificationItem(
                    title: '欢迎使用',
                    message: '感谢您使用本应用',
                    time: '刚刚',
                    icon: Icons.wb_sunny_rounded,
                    color: Colors.orange,
                    isRead: false,
                  ),
                ]);
              });
            },
            icon: const Icon(Icons.mark_email_read_rounded),
            label: const Text('全部已读'),
          ),
        ],
      ),
      body: Column(
        children: [
          // 筛选按钮
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              children: [
                Expanded(
                  child: FilterChip(
                    label: const Text('全部'),
                    selected: _filterAll,
                    onSelected: (value) {
                      setState(() {
                        _filterAll = true;
                        _filterUnread = false;
                      });
                    },
                    backgroundColor: theme.colorScheme.surfaceContainerHighest,
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: FilterChip(
                    label: const Text('未读'),
                    selected: _filterUnread,
                    onSelected: (value) {
                      setState(() {
                        _filterUnread = true;
                        _filterAll = false;
                      });
                    },
                    backgroundColor: theme.colorScheme.surfaceContainerHighest,
                  ),
                ),
              ],
            ),
          ),
          // 通知列表
          Expanded(
            child: ListView.separated(
              padding: const EdgeInsets.symmetric(horizontal: 16),
              itemCount: _filterUnread
                  ? _notifications.where((n) => !n.isRead).length
                  : _notifications.length,
              separatorBuilder: (_, __) => const Divider(),
              itemBuilder: (context, index) {
                final notifications = _filterUnread
                    ? _notifications.where((n) => !n.isRead).toList()
                    : _notifications;
                final notification = notifications[index];
                return _buildNotificationTile(notification, theme);
              },
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildNotificationTile(
      _NotificationItem notification, ThemeData theme) {
    return ListTile(
      contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
      leading: Stack(
        children: [
          Container(
            width: 50,
            height: 50,
            decoration: BoxDecoration(
              color: notification.color.withValues(alpha: 0.15),
              shape: BoxShape.circle,
            ),
            child: Icon(notification.icon, color: notification.color),
          ),
          if (!notification.isRead)
            Positioned(
              right: 0,
              top: 0,
              child: Container(
                width: 12,
                height: 12,
                decoration: const BoxDecoration(
                  color: Colors.red,
                  shape: BoxShape.circle,
                ),
              ),
            ),
        ],
      ),
      title: Text(
        notification.title,
        style: TextStyle(
          fontWeight: notification.isRead ? FontWeight.normal : FontWeight.bold,
        ),
      ),
      subtitle: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(notification.message),
          const SizedBox(height: 4),
          Text(
            notification.time,
            style: TextStyle(
              fontSize: 12,
              color: Colors.grey[400],
            ),
          ),
        ],
      ),
      onTap: () {
        setState(() {
          notification.isRead = true;
        });
      },
    );
  }

  final List<_NotificationItem> _notifications = [
    _NotificationItem(
      title: '新功能上线',
      message: '我们推出了全新的侧滑菜单功能',
      time: '刚刚',
      icon: Icons.auto_awesome_rounded,
      color: Colors.purple,
      isRead: false,
    ),
    _NotificationItem(
      title: '系统更新',
      message: '版本 2.0.0 已发布',
      time: '1小时前',
      icon: Icons.system_update_alt_rounded,
      color: Colors.blue,
      isRead: false,
    ),
    _NotificationItem(
      title: '安全提醒',
      message: '请在设置中修改您的密码',
      time: '3小时前',
      icon: Icons.security_rounded,
      color: Colors.red,
      isRead: false,
    ),
    _NotificationItem(
      title: '活动邀请',
      message: '参加 Flutter 开发者大会',
      time: '昨天',
      icon: Icons.event_rounded,
      color: Colors.green,
      isRead: true,
    ),
    _NotificationItem(
      title: '完成任务',
      message: '您的代码审查已完成',
      time: '昨天',
      icon: Icons.task_alt_rounded,
      color: Colors.orange,
      isRead: true,
    ),
  ];
}

class _NotificationItem {
  String title;
  String message;
  String time;
  IconData icon;
  Color color;
  bool isRead;

  _NotificationItem({
    required this.title,
    required this.message,
    required this.time,
    required this.icon,
    required this.color,
    required this.isRead,
  });
}

// ============ 帮助页面 ============
class HelpPage extends StatelessWidget {
  const HelpPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_rounded),
          onPressed: () => Navigator.pop(context),
        ),
        title: const Text('帮助中心'),
        centerTitle: true,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // 搜索框
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            decoration: BoxDecoration(
              color: Theme.of(context).colorScheme.surfaceContainerHighest,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Row(
              children: [
                const Icon(Icons.search_rounded, color: Colors.grey),
                const SizedBox(width: 12),
                Expanded(
                  child: TextField(
                    decoration: const InputDecoration(
                      hintText: '搜索问题...',
                      border: InputBorder.none,
                    ),
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 24),
          // 常见问题
          _buildSectionTitle('常见问题'),
          ...List.generate(_faqItems.length, (index) {
            return _buildExpansionTile(
              icon: _faqItems[index]['icon'] as IconData,
              title: _faqItems[index]['question'] as String,
              answer: _faqItems[index]['answer'] as String,
            );
          }),
          const SizedBox(height: 24),
          // 联系我们
          _buildSectionTitle('联系我们'),
          Card(
            child: Column(
              children: [
                _buildContactItem(
                  icon: Icons.phone_rounded,
                  title: '客服热线',
                  subtitle: '400-888-8888',
                ),
                const Divider(),
                _buildContactItem(
                  icon: Icons.email_rounded,
                  title: '电子邮件',
                  subtitle: 'support@example.com',
                ),
                const Divider(),
                _buildContactItem(
                  icon: Icons.chat_rounded,
                  title: '在线客服',
                  subtitle: '工作时间 9:00-18:00',
                ),
              ],
            ),
          ),
          const SizedBox(height: 24),
          // 关于
          _buildSectionTitle('关于'),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                children: [
                  const Icon(
                    Icons.info_rounded,
                    size: 48,
                    color: Colors.blue,
                  ),
                  const SizedBox(height: 12),
                  const Text(
                    '视差侧滑菜单',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    '版本 1.0.0',
                    style: TextStyle(
                      color: Colors.grey[400],
                    ),
                  ),
                  const SizedBox(height: 16),
                  const Text(
                    '这是一个使用 Flutter 开发的现代化应用示例,展示了流畅的侧滑菜单动画和优雅的 UI 设计。',
                    textAlign: TextAlign.center,
                    style: TextStyle(
                      color: Colors.grey,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildSectionTitle(String title) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Text(
        title,
        style: const TextStyle(
          fontSize: 16,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }

  Widget _buildExpansionTile({
    required IconData icon,
    required String title,
    required String answer,
  }) {
    return Card(
      margin: const EdgeInsets.only(bottom: 8),
      child: ExpansionTile(
        leading: Icon(icon),
        title: Text(title),
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text(
              answer,
              style: const TextStyle(
                color: Colors.grey,
                height: 1.5,
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildContactItem({
    required IconData icon,
    required String title,
    required String subtitle,
  }) {
    return ListTile(
      leading: Icon(icon),
      title: Text(title),
      subtitle: Text(subtitle),
      trailing: const Icon(Icons.chevron_right_rounded),
    );
  }

  static final List<Map<String, dynamic>> _faqItems = [
    {
      'icon': Icons.menu_open_rounded,
      'question': '如何打开侧滑菜单?',
      'answer': '点击左上角的菜单按钮即可打开侧滑菜单。您也可以从屏幕左边缘向右滑动来打开菜单。',
    },
    {
      'icon': Icons.favorite_rounded,
      'question': '如何收藏内容?',
      'answer': '在内容详情页点击收藏按钮即可将内容添加到收藏列表。收藏后可以在"收藏"页面查看所有收藏的内容。',
    },
    {
      'icon': Icons.notifications_rounded,
      'question': '如何设置通知?',
      'answer': '在设置页面中找到"通知"选项,您可以开启或关闭推送通知。支持分类通知和免打扰模式。',
    },
    {
      'icon': Icons.sync_rounded,
      'question': '如何同步数据?',
      'answer': '应用支持云端同步功能。在设置中登录您的账号,即可将数据同步到云端,在不同设备间保持一致性。',
    },
  ];
}
相关推荐
黎子越2 小时前
python相关练习
java·前端·python
d_b_2 小时前
UCIE 笔记(一)
笔记·学习·芯片
悠哉悠哉愿意2 小时前
【强化学习学习笔记】强化学习简介
笔记·学习·强化学习
北极糊的狐2 小时前
若依项目vue前端启动键入npm run dev 报错:不是内部或外部命令,也不是可运行的程序或批处理文件。
前端·javascript·vue.js
星火开发设计2 小时前
C++ 输入输出流:cin 与 cout 的基础用法
java·开发语言·c++·学习·算法·编程·知识
XRJ040618xrj2 小时前
Nginx下构建PC站点
服务器·前端·nginx
恋猫de小郭2 小时前
Flutter 在 Android 出现随机字体裁剪?其实是图层合并时的边界计算问题
android·flutter·ios
AI_56782 小时前
用Everything+Total Commander管理电脑文件
人工智能·学习
秦奈2 小时前
Unity复习学习随笔(11):二进制存储
学习