Flutter非遗文化查询:传承中华文化瑰宝

项目简介

非遗文化查询是一款专为非物质文化遗产保护和传播打造的Flutter应用,提供非遗项目查询、传承人信息展示和地域分布统计功能。通过智能搜索和分类筛选,让用户轻松了解和学习中华优秀传统文化。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

核心功能

  • 非遗项目查询:50个非遗项目,涵盖10大分类
  • 传承人信息:30位传承人详细资料
  • 智能搜索:支持项目名称、传承人、地区搜索
  • 多维筛选:按分类、等级、省份筛选
  • 详细信息:项目简介、历史渊源、特色展示
  • 地域分布:可视化展示各省份非遗项目分布
  • 等级标识:国家级、省级、市级、县级
  • 传承状态:传承良好、濒危状态标识
  • 收藏分享:收藏项目、分享功能
  • 传承人档案:个人简介、成就、技艺、荣誉

技术特点

  • Material Design 3设计风格
  • NavigationBar底部导航
  • 三页面架构(非遗项目、传承人、地域分布)
  • 搜索和筛选功能
  • 详情页展示
  • 响应式卡片布局
  • 数据可视化统计
  • 无需额外依赖包

核心代码实现

1. 非遗项目数据模型

class HeritageProject {
  final String id;              // 项目ID
  final String name;            // 项目名称
  final String category;        // 分类
  final String level;           // 保护等级
  final String province;        // 省份
  final String city;            // 城市
  final String description;     // 项目简介
  final String history;         // 历史渊源
  final String features;        // 项目特色
  final String status;          // 传承状态
  final List<String> tags;      // 标签
  final DateTime approvalDate;  // 批准时间
  final int batchNumber;        // 批次

  HeritageProject({
    required this.id,
    required this.name,
    required this.category,
    required this.level,
    required this.province,
    required this.city,
    required this.description,
    required this.history,
    required this.features,
    required this.status,
    required this.tags,
    required this.approvalDate,
    required this.batchNumber,
  });

  // 分类颜色
  Color get categoryColor {
    switch (category) {
      case '民间文学': return Colors.purple;
      case '传统音乐': return Colors.blue;
      case '传统舞蹈': return Colors.pink;
      case '传统戏剧': return Colors.red;
      case '曲艺': return Colors.orange;
      case '传统体育': return Colors.green;
      case '传统美术': return Colors.indigo;
      case '传统技艺': return Colors.teal;
      case '传统医药': return Colors.cyan;
      case '民俗': return Colors.amber;
      default: return Colors.grey;
    }
  }

  // 分类图标
  IconData get categoryIcon {
    switch (category) {
      case '民间文学': return Icons.menu_book;
      case '传统音乐': return Icons.music_note;
      case '传统舞蹈': return Icons.theater_comedy;
      case '传统戏剧': return Icons.masks;
      case '曲艺': return Icons.mic;
      case '传统体育': return Icons.sports_martial_arts;
      case '传统美术': return Icons.palette;
      case '传统技艺': return Icons.handyman;
      case '传统医药': return Icons.medical_services;
      case '民俗': return Icons.festival;
      default: return Icons.category;
    }
  }

  // 等级颜色
  Color get levelColor {
    switch (level) {
      case '国家级': return Colors.red;
      case '省级': return Colors.orange;
      case '市级': return Colors.blue;
      case '县级': return Colors.green;
      default: return Colors.grey;
    }
  }
}

模型字段说明

字段类型说明
idString唯一标识符
nameString项目名称
categoryString分类(10大类)
levelString保护等级
provinceString所属省份
cityString所属城市
descriptionString项目简介
historyString历史渊源
featuresString项目特色
statusString传承状态
tagsList标签列表
approvalDateDateTime批准时间
batchNumberint批次编号

计算属性

  • categoryColor:根据分类返回对应颜色
  • categoryIcon:根据分类返回对应图标
  • levelColor:根据等级返回对应颜色

非遗分类与颜色映射

分类颜色图标说明
民间文学紫色menu_book神话、传说、故事等
传统音乐蓝色music_note民歌、器乐等
传统舞蹈粉色theater_comedy民间舞蹈
传统戏剧红色masks戏曲、曲艺等
曲艺橙色mic说唱艺术
传统体育绿色sports_martial_arts武术、竞技等
传统美术靛蓝palette绘画、雕刻等
传统技艺青色handyman手工技艺
传统医药青色medical_services中医药等
民俗琥珀色festival节日、礼仪等

保护等级

等级颜色说明
国家级红色国家级非遗项目
省级橙色省级非遗项目
市级蓝色市级非遗项目
县级绿色县级非遗项目

2. 传承人数据模型

class Inheritor {
  final String id;              // 传承人ID
  final String name;            // 姓名
  final String gender;          // 性别
  final int age;                // 年龄
  final String projectName;     // 传承项目
  final String level;           // 传承等级
  final String province;        // 省份
  final String city;            // 城市
  final String introduction;    // 个人简介
  final String achievements;    // 主要成就
  final String skills;          // 技艺特长
  final List<String> awards;    // 获得荣誉
  final DateTime recognitionDate; // 认定时间

  Inheritor({
    required this.id,
    required this.name,
    required this.gender,
    required this.age,
    required this.projectName,
    required this.level,
    required this.province,
    required this.city,
    required this.introduction,
    required this.achievements,
    required this.skills,
    required this.awards,
    required this.recognitionDate,
  });

  // 等级颜色
  Color get levelColor {
    switch (level) {
      case '国家级': return Colors.red;
      case '省级': return Colors.orange;
      case '市级': return Colors.blue;
      case '县级': return Colors.green;
      default: return Colors.grey;
    }
  }
}

模型字段说明

字段类型说明
idString唯一标识符
nameString传承人姓名
genderString性别
ageint年龄
projectNameString传承项目名称
levelString传承等级
provinceString所在省份
cityString所在城市
introductionString个人简介
achievementsString主要成就
skillsString技艺特长
awardsList荣誉列表
recognitionDateDateTime认定时间

计算属性

  • levelColor:根据传承等级返回对应颜色

3. 数据生成逻辑

void _generateData() {
  final random = Random();
  
  // 非遗项目名称库
  final projectNames = [
    '昆曲', '古琴艺术', '木版水印', '景泰蓝制作技艺', '京剧',
    '越剧', '黄梅戏', '评剧', '豫剧', '川剧', '秦腔', '粤剧',
    '湘绣', '苏绣', '蜀绣', '剪纸', '皮影戏', '木偶戏',
    '龙舟竞渡', '太极拳', '少林功夫', '武当武术', '中医针灸',
    '藏医药', '蒙医药', '景德镇瓷器', '宜兴紫砂', '龙泉青瓷',
    '德化白瓷', '钧瓷', '汝瓷', '定瓷', '哥窑', '官窑', '建盏',
    '漆器髹饰', '金银细工', '玉雕', '木雕', '石雕', '竹编',
    '藤编', '草编', '柳编', '棕编', '扎染', '蜡染', '蓝印花布',
  ];

  // 生成50个非遗项目
  for (int i = 0; i < 50; i++) {
    final category = _categories[random.nextInt(_categories.length - 1) + 1];
    final level = _levels[random.nextInt(_levels.length - 1) + 1];
    final province = _provinces[random.nextInt(_provinces.length - 1) + 1];

    _allProjects.add(HeritageProject(
      id: 'project_$i',
      name: projectNames[i % projectNames.length],
      category: category,
      level: level,
      province: province,
      city: '${province.replaceAll('省', '').replaceAll('市', '').replaceAll('壮族自治区', '')}市',
      description: '这是一项具有深厚历史底蕴和独特艺术价值的非物质文化遗产项目,代表了中华民族优秀传统文化的精髓。',
      history: '该项目起源于${1000 + random.nextInt(1000)}年前,历经数代传承,形成了独特的艺术风格和技艺体系。',
      features: '具有鲜明的地域特色、精湛的技艺水平、深厚的文化内涵和广泛的群众基础。',
      status: random.nextDouble() > 0.3 ? '传承良好' : '濒危',
      tags: ['传统文化', '非遗保护', '文化传承'],
      approvalDate: DateTime.now().subtract(Duration(days: random.nextInt(3650))),
      batchNumber: random.nextInt(5) + 1,
    ));
  }

  // 传承人名称库
  final inheritorNames = [
    '张大师', '李传人', '王艺人', '赵师傅', '刘老师', '陈大师',
    '杨传人', '周艺人', '吴师傅', '郑老师', '孙大师', '朱传人',
    '胡艺人', '林师傅', '何老师', '高大师', '梁传人', '郭艺人',
    '马师傅', '罗老师', '宋大师', '唐传人', '韩艺人', '冯师傅',
    '于老师', '董大师', '萧传人', '程艺人', '曹师傅', '袁老师',
  ];

  // 生成30位传承人
  for (int i = 0; i < 30; i++) {
    final project = _allProjects[random.nextInt(_allProjects.length)];
    final level = _levels[random.nextInt(_levels.length - 1) + 1];

    _allInheritors.add(Inheritor(
      id: 'inheritor_$i',
      name: inheritorNames[i % inheritorNames.length],
      gender: random.nextDouble() > 0.3 ? '男' : '女',
      age: 40 + random.nextInt(40),
      projectName: project.name,
      level: level,
      province: project.province,
      city: project.city,
      introduction: '从事${project.name}技艺传承${20 + random.nextInt(30)}年,是该项目的代表性传承人。',
      achievements: '多次参加国内外文化交流活动,作品获得多项荣誉,培养学徒${10 + random.nextInt(50)}余人。',
      skills: '精通${project.name}的各项技艺,在传统技法基础上有所创新和发展。',
      awards: ['非遗传承人', '工艺美术大师', '文化传承贡献奖'],
      recognitionDate: DateTime.now().subtract(Duration(days: random.nextInt(1825))),
    ));
  }

  _applyFilters();
}

数据生成特点

  1. 生成50个非遗项目
  2. 生成30位传承人
  3. 随机分配分类、等级、省份
  4. 传承人与项目关联
  5. 年龄范围:40-80岁
  6. 传承年限:20-50年
  7. 培养学徒:10-60人
  8. 70%传承良好,30%濒危

4. 搜索和筛选功能

void _applyFilters() {
  setState(() {
    // 筛选非遗项目
    _filteredProjects = _allProjects.where((project) {
      // 搜索关键词筛选
      if (_searchQuery.isNotEmpty) {
        final query = _searchQuery.toLowerCase();
        if (!project.name.toLowerCase().contains(query) &&
            !project.province.toLowerCase().contains(query) &&
            !project.city.toLowerCase().contains(query)) {
          return false;
        }
      }
      
      // 分类筛选
      if (_selectedCategory != '全部' && project.category != _selectedCategory) {
        return false;
      }
      
      // 等级筛选
      if (_selectedLevel != '全部' && project.level != _selectedLevel) {
        return false;
      }
      
      // 省份筛选
      if (_selectedProvince != '全部' && project.province != _selectedProvince) {
        return false;
      }
      
      return true;
    }).toList();

    // 筛选传承人
    _filteredInheritors = _allInheritors.where((inheritor) {
      // 搜索关键词筛选
      if (_searchQuery.isNotEmpty) {
        final query = _searchQuery.toLowerCase();
        if (!inheritor.name.toLowerCase().contains(query) &&
            !inheritor.projectName.toLowerCase().contains(query) &&
            !inheritor.province.toLowerCase().contains(query)) {
          return false;
        }
      }
      
      // 等级筛选
      if (_selectedLevel != '全部' && inheritor.level != _selectedLevel) {
        return false;
      }
      
      // 省份筛选
      if (_selectedProvince != '全部' && inheritor.province != _selectedProvince) {
        return false;
      }
      
      return true;
    }).toList();
  });
}

筛选条件

筛选项适用对象说明
搜索关键词项目、传承人匹配名称、地区
分类项目10大分类筛选
等级项目、传承人4个等级筛选
省份项目、传承人20个省份筛选

筛选流程

  1. 检查搜索关键词匹配
  2. 检查分类匹配(仅项目)
  3. 检查等级匹配
  4. 检查省份匹配
  5. 更新筛选结果列表

5. 搜索栏组件

Widget _buildSearchBar() {
  return Container(
    padding: const EdgeInsets.all(16),
    child: TextField(
      decoration: InputDecoration(
        hintText: '搜索非遗项目、传承人或地区',
        prefixIcon: const Icon(Icons.search),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(12),
        ),
        filled: true,
      ),
      onChanged: (value) {
        setState(() {
          _searchQuery = value;
          _applyFilters();
        });
      },
    ),
  );
}

搜索功能特点

  • 实时搜索
  • 支持项目名称搜索
  • 支持传承人姓名搜索
  • 支持地区搜索
  • 不区分大小写
  • 自动触发筛选

6. 筛选对话框

Widget _buildFilterSheet() {
  return Container(
    padding: const EdgeInsets.all(16),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          '筛选条件',
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 16),
        // 分类筛选
        DropdownButtonFormField<String>(
          value: _selectedCategory,
          decoration: const InputDecoration(
            labelText: '分类',
            border: OutlineInputBorder(),
          ),
          items: _categories.map((category) {
            return DropdownMenuItem(
              value: category,
              child: Text(category),
            );
          }).toList(),
          onChanged: (value) {
            setState(() {
              _selectedCategory = value!;
              _applyFilters();
            });
            Navigator.pop(context);
          },
        ),
        const SizedBox(height: 16),
        // 等级筛选
        DropdownButtonFormField<String>(
          value: _selectedLevel,
          decoration: const InputDecoration(
            labelText: '等级',
            border: OutlineInputBorder(),
          ),
          items: _levels.map((level) {
            return DropdownMenuItem(
              value: level,
              child: Text(level),
            );
          }).toList(),
          onChanged: (value) {
            setState(() {
              _selectedLevel = value!;
              _applyFilters();
            });
            Navigator.pop(context);
          },
        ),
        const SizedBox(height: 16),
        // 省份筛选
        DropdownButtonFormField<String>(
          value: _selectedProvince,
          decoration: const InputDecoration(
            labelText: '省份',
            border: OutlineInputBorder(),
          ),
          items: _provinces.map((province) {
            return DropdownMenuItem(
              value: province,
              child: Text(province),
            );
          }).toList(),
          onChanged: (value) {
            setState(() {
              _selectedProvince = value!;
              _applyFilters();
            });
            Navigator.pop(context);
          },
        ),
      ],
    ),
  );
}

筛选对话框设计

  • 使用ModalBottomSheet展示
  • 三个下拉选择框
  • 选择后自动应用筛选
  • 自动关闭对话框

7. 非遗项目卡片

Widget _buildProjectCard(HeritageProject project) {
  return Card(
    margin: const EdgeInsets.only(bottom: 16),
    child: InkWell(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => ProjectDetailPage(project: project),
          ),
        );
      },
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 顶部:图标、名称、等级、分类
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(12),
                  decoration: BoxDecoration(
                    color: project.categoryColor.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Icon(
                    project.categoryIcon,
                    color: project.categoryColor,
                    size: 32,
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        project.name,
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Row(
                        children: [
                          // 等级标签
                          Container(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 8,
                              vertical: 2,
                            ),
                            decoration: BoxDecoration(
                              color: project.levelColor,
                              borderRadius: BorderRadius.circular(12),
                            ),
                            child: Text(
                              project.level,
                              style: const TextStyle(
                                fontSize: 12,
                                color: Colors.white,
                              ),
                            ),
                          ),
                          const SizedBox(width: 8),
                          // 分类标签
                          Container(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 8,
                              vertical: 2,
                            ),
                            decoration: BoxDecoration(
                              color: project.categoryColor.withValues(alpha: 0.2),
                              borderRadius: BorderRadius.circular(12),
                            ),
                            child: Text(
                              project.category,
                              style: TextStyle(
                                fontSize: 12,
                                color: project.categoryColor,
                              ),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            // 项目简介
            Text(
              project.description,
              style: const TextStyle(
                fontSize: 14,
                color: Colors.grey,
                height: 1.5,
              ),
              maxLines: 2,
              overflow: TextOverflow.ellipsis,
            ),
            const SizedBox(height: 12),
            // 底部:地区、传承状态
            Row(
              children: [
                const Icon(Icons.location_on, size: 16, color: Colors.grey),
                const SizedBox(width: 4),
                Text(
                  '${project.province} ${project.city}',
                  style: const TextStyle(fontSize: 12, color: Colors.grey),
                ),
                const Spacer(),
                Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 8,
                    vertical: 2,
                  ),
                  decoration: BoxDecoration(
                    color: project.status == '传承良好'
                        ? Colors.green.withValues(alpha: 0.1)
                        : Colors.red.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Text(
                    project.status,
                    style: TextStyle(
                      fontSize: 12,
                      color: project.status == '传承良好'
                          ? Colors.green
                          : Colors.red,
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

卡片布局结构

  1. 顶部:分类图标、项目名称、等级标签、分类标签
  2. 中部:项目简介(最多2行)
  3. 底部:地区信息、传承状态标签

设计要点

  • 使用Card提供阴影效果
  • InkWell提供点击反馈
  • 图标使用分类颜色背景
  • 标签使用圆角设计
  • 传承状态用颜色区分

8. 传承人卡片

Widget _buildInheritorCard(Inheritor inheritor) {
  return Card(
    margin: const EdgeInsets.only(bottom: 16),
    child: InkWell(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => InheritorDetailPage(inheritor: inheritor),
          ),
        );
      },
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            // 头像
            CircleAvatar(
              radius: 32,
              backgroundColor: Colors.deepOrange.withValues(alpha: 0.1),
              child: Text(
                inheritor.name.substring(0, 1),
                style: const TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  color: Colors.deepOrange,
                ),
              ),
            ),
            const SizedBox(width: 16),
            // 信息
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Text(
                        inheritor.name,
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(width: 8),
                      Text(
                        '${inheritor.gender} · ${inheritor.age}岁',
                        style: const TextStyle(
                          fontSize: 14,
                          color: Colors.grey,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 4),
                  Container(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 8,
                      vertical: 2,
                    ),
                    decoration: BoxDecoration(
                      color: inheritor.levelColor,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text(
                      '${inheritor.level}传承人',
                      style: const TextStyle(
                        fontSize: 12,
                        color: Colors.white,
                      ),
                    ),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    inheritor.projectName,
                    style: const TextStyle(
                      fontSize: 14,
                      color: Colors.deepOrange,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Row(
                    children: [
                      const Icon(Icons.location_on, size: 14, color: Colors.grey),
                      const SizedBox(width: 4),
                      Text(
                        '${inheritor.province} ${inheritor.city}',
                        style: const TextStyle(fontSize: 12, color: Colors.grey),
                      ),
                    ],
                  ),
                ],
              ),
            ),
            const Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey),
          ],
        ),
      ),
    ),
  );
}

卡片布局结构

  • 左侧:圆形头像(显示姓氏首字)
  • 中间:姓名、性别年龄、等级标签、传承项目、地区
  • 右侧:箭头图标

设计要点

  • CircleAvatar显示头像
  • 等级标签使用对应颜色
  • 传承项目使用主题色
  • 地区信息带图标

9. 地域分布统计页

Widget _buildMapPage() {
  // 统计各省份的非遗项目数量
  final provinceCount = <String, int>{};
  for (var project in _allProjects) {
    provinceCount[project.province] = (provinceCount[project.province] ?? 0) + 1;
  }

  // 按数量降序排序
  final sortedProvinces = provinceCount.entries.toList()
    ..sort((a, b) => b.value.compareTo(a.value));

  return ListView(
    padding: const EdgeInsets.all(16),
    children: [
      // 统计概览卡片
      Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Row(
                children: [
                  Icon(Icons.bar_chart, color: Colors.deepOrange),
                  SizedBox(width: 8),
                  Text(
                    '地域分布统计',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 16),
              Text(
                '共收录 ${_allProjects.length} 个非遗项目',
                style: const TextStyle(fontSize: 14, color: Colors.grey),
              ),
              Text(
                '涉及 ${provinceCount.length} 个省份',
                style: const TextStyle(fontSize: 14, color: Colors.grey),
              ),
            ],
          ),
        ),
      ),
      const SizedBox(height: 16),
      // 各省份统计卡片
      ...sortedProvinces.map((entry) {
        final percentage = (entry.value / _allProjects.length * 100);
        return Card(
          margin: const EdgeInsets.only(bottom: 12),
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      entry.key,
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Text(
                      '${entry.value} 项',
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                        color: Colors.deepOrange,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                LinearProgressIndicator(
                  value: percentage / 100,
                  backgroundColor: Colors.grey[200],
                  color: Colors.deepOrange,
                ),
                const SizedBox(height: 4),
                Text(
                  '占比 ${percentage.toStringAsFixed(1)}%',
                  style: const TextStyle(fontSize: 12, color: Colors.grey),
                ),
              ],
            ),
          ),
        );
      }),
    ],
  );
}

统计功能

  1. 统计各省份非遗项目数量
  2. 按数量降序排序
  3. 计算占比百分比
  4. 使用进度条可视化展示

页面结构

  • 顶部:统计概览卡片
  • 列表:各省份统计卡片

可视化设计

  • LinearProgressIndicator显示占比
  • 进度条颜色使用主题色
  • 显示具体数量和百分比

10. 非遗项目详情页

class ProjectDetailPage extends StatelessWidget {
  final HeritageProject project;

  const ProjectDetailPage({super.key, required this.project});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(project.name),
        actions: [
          IconButton(
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('已添加到收藏')),
              );
            },
            icon: const Icon(Icons.favorite_border),
            tooltip: '收藏',
          ),
          IconButton(
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('分享功能')),
              );
            },
            icon: const Icon(Icons.share),
            tooltip: '分享',
          ),
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildHeader(),
            _buildBasicInfo(),
            _buildDescription(),
            _buildHistory(),
            _buildFeatures(),
            _buildTags(),
          ],
        ),
      ),
    );
  }

  // 头部展示
  Widget _buildHeader() {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [
            project.categoryColor.withValues(alpha: 0.3),
            project.categoryColor.withValues(alpha: 0.1),
          ],
        ),
      ),
      child: Column(
        children: [
          Container(
            padding: const EdgeInsets.all(24),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(16),
            ),
            child: Icon(
              project.categoryIcon,
              size: 64,
              color: project.categoryColor,
            ),
          ),
          const SizedBox(height: 16),
          Text(
            project.name,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 8),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 4,
                ),
                decoration: BoxDecoration(
                  color: project.levelColor,
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Text(
                  project.level,
                  style: const TextStyle(color: Colors.white),
                ),
              ),
              const SizedBox(width: 8),
              Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 4,
                ),
                decoration: BoxDecoration(
                  color: project.categoryColor.withValues(alpha: 0.2),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Text(
                  project.category,
                  style: TextStyle(color: project.categoryColor),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  // 基本信息
  Widget _buildBasicInfo() {
    return Card(
      margin: const EdgeInsets.all(16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Row(
              children: [
                Icon(Icons.info_outline, color: Colors.deepOrange),
                SizedBox(width: 8),
                Text(
                  '基本信息',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            _buildInfoRow('所属地区', '${project.province} ${project.city}'),
            _buildInfoRow('保护等级', project.level),
            _buildInfoRow('项目类别', project.category),
            _buildInfoRow(
              '批准时间',
              '${project.approvalDate.year}${project.approvalDate.month}月',
            ),
            _buildInfoRow('批次', '第${project.batchNumber}批'),
            _buildInfoRow('传承状态', project.status),
          ],
        ),
      ),
    );
  }

  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 80,
            child: Text(
              label,
              style: const TextStyle(
                fontSize: 14,
                color: Colors.grey,
              ),
            ),
          ),
          Expanded(
            child: Text(
              value,
              style: const TextStyle(
                fontSize: 14,
                fontWeight: FontWeight.w500,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

详情页结构

  1. 头部:渐变背景、大图标、项目名称、等级分类标签
  2. 基本信息卡片:地区、等级、类别、批准时间、批次、传承状态
  3. 项目简介卡片:详细描述
  4. 历史渊源卡片:历史背景
  5. 项目特色卡片:特色介绍
  6. 相关标签卡片:标签展示

设计要点

  • 使用渐变背景突出头部
  • 卡片式布局清晰分区
  • 图标配合文字说明
  • 信息行左右对齐

11. 传承人详情页

class InheritorDetailPage extends StatelessWidget {
  final Inheritor inheritor;

  const InheritorDetailPage({super.key, required this.inheritor});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(inheritor.name),
        actions: [
          IconButton(
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('已添加到关注')),
              );
            },
            icon: const Icon(Icons.person_add),
            tooltip: '关注',
          ),
          IconButton(
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('分享功能')),
              );
            },
            icon: const Icon(Icons.share),
            tooltip: '分享',
          ),
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildHeader(),
            _buildBasicInfo(),
            _buildIntroduction(),
            _buildAchievements(),
            _buildSkills(),
            _buildAwards(),
          ],
        ),
      ),
    );
  }

  // 头部展示
  Widget _buildHeader() {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [
            Colors.deepOrange.withValues(alpha: 0.3),
            Colors.deepOrange.withValues(alpha: 0.1),
          ],
        ),
      ),
      child: Column(
        children: [
          CircleAvatar(
            radius: 48,
            backgroundColor: Colors.white,
            child: Text(
              inheritor.name.substring(0, 1),
              style: const TextStyle(
                fontSize: 48,
                fontWeight: FontWeight.bold,
                color: Colors.deepOrange,
              ),
            ),
          ),
          const SizedBox(height: 16),
          Text(
            inheritor.name,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 8),
          Container(
            padding: const EdgeInsets.symmetric(
              horizontal: 16,
              vertical: 6,
            ),
            decoration: BoxDecoration(
              color: inheritor.levelColor,
              borderRadius: BorderRadius.circular(16),
            ),
            child: Text(
              '${inheritor.level}传承人',
              style: const TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          const SizedBox(height: 8),
          Text(
            '${inheritor.gender} · ${inheritor.age}岁',
            style: const TextStyle(
              fontSize: 14,
              color: Colors.grey,
            ),
          ),
        ],
      ),
    );
  }

  // 获得荣誉
  Widget _buildAwards() {
    return Card(
      margin: const EdgeInsets.all(16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Row(
              children: [
                Icon(Icons.military_tech, color: Colors.deepOrange),
                SizedBox(width: 8),
                Text(
                  '获得荣誉',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            ...inheritor.awards.map((award) {
              return Container(
                margin: const EdgeInsets.only(bottom: 8),
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.deepOrange.withValues(alpha: 0.05),
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(
                    color: Colors.deepOrange.withValues(alpha: 0.2),
                  ),
                ),
                child: Row(
                  children: [
                    const Icon(
                      Icons.star,
                      color: Colors.deepOrange,
                      size: 20,
                    ),
                    const SizedBox(width: 8),
                    Text(
                      award,
                      style: const TextStyle(fontSize: 14),
                    ),
                  ],
                ),
              );
            }),
          ],
        ),
      ),
    );
  }
}

详情页结构

  1. 头部:渐变背景、大头像、姓名、等级标签、性别年龄
  2. 基本信息卡片:传承项目、地区、等级、认定时间
  3. 个人简介卡片:传承经历
  4. 主要成就卡片:成就介绍
  5. 技艺特长卡片:技艺描述
  6. 获得荣誉卡片:荣誉列表

设计要点

  • 大头像突出人物
  • 荣誉列表使用边框卡片
  • 星标图标装饰荣誉项
  • 渐变背景营造氛围

技术要点详解

1. NavigationBar底部导航

NavigationBar是Material Design 3中的底部导航组件,提供现代化的导航体验。

基本用法

NavigationBar(
  selectedIndex: _selectedIndex,
  onDestinationSelected: (index) {
    setState(() => _selectedIndex = index);
  },
  destinations: const [
    NavigationDestination(
      icon: Icon(Icons.category),
      label: '非遗项目',
    ),
    NavigationDestination(
      icon: Icon(Icons.people),
      label: '传承人',
    ),
    NavigationDestination(
      icon: Icon(Icons.map),
      label: '地域分布',
    ),
  ],
)

关键属性

  • selectedIndex:当前选中的索引
  • onDestinationSelected:选择回调函数
  • destinations:导航项列表

配合IndexedStack使用

IndexedStack(
  index: _selectedIndex,
  children: [
    _buildProjectsPage(),
    _buildInheritorsPage(),
    _buildMapPage(),
  ],
)

优势

  • 保持页面状态
  • 切换流畅无闪烁
  • 符合Material Design 3规范
  • 自动处理选中状态

2. 计算属性的高级应用

计算属性可以根据对象状态动态返回值,减少数据冗余。

示例

class HeritageProject {
  String category;
  
  // 根据分类返回颜色
  Color get categoryColor {
    switch (category) {
      case '民间文学': return Colors.purple;
      case '传统音乐': return Colors.blue;
      case '传统舞蹈': return Colors.pink;
      // ... 更多分类
      default: return Colors.grey;
    }
  }
  
  // 根据分类返回图标
  IconData get categoryIcon {
    switch (category) {
      case '民间文学': return Icons.menu_book;
      case '传统音乐': return Icons.music_note;
      case '传统舞蹈': return Icons.theater_comedy;
      // ... 更多分类
      default: return Icons.category;
    }
  }
  
  String level;
  
  // 根据等级返回颜色
  Color get levelColor {
    switch (level) {
      case '国家级': return Colors.red;
      case '省级': return Colors.orange;
      case '市级': return Colors.blue;
      case '县级': return Colors.green;
      default: return Colors.grey;
    }
  }
}

优势

  • 减少存储空间
  • 保持数据一致性
  • 简化代码逻辑
  • 便于维护和扩展
  • 避免重复计算

使用场景

  • UI颜色映射
  • 图标映射
  • 状态文本转换
  • 格式化显示

3. List集合操作

Dart的List提供了丰富的集合操作方法,用于数据筛选和处理。

筛选操作

// 使用where筛选
final filteredProjects = _allProjects.where((project) {
  if (_searchQuery.isNotEmpty) {
    final query = _searchQuery.toLowerCase();
    if (!project.name.toLowerCase().contains(query)) {
      return false;
    }
  }
  if (_selectedCategory != '全部' && project.category != _selectedCategory) {
    return false;
  }
  return true;
}).toList();

排序操作

// 按数量降序排序
final sortedProvinces = provinceCount.entries.toList()
  ..sort((a, b) => b.value.compareTo(a.value));

统计操作

// 统计各省份数量
final provinceCount = <String, int>{};
for (var project in _allProjects) {
  provinceCount[project.province] = (provinceCount[project.province] ?? 0) + 1;
}

映射操作

// 提取特定字段
final projectNames = _allProjects.map((p) => p.name).toList();

常用方法

  • where:筛选元素
  • map:转换元素
  • sort:排序
  • reduce:聚合
  • fold:累积
  • any:是否存在
  • every:是否全部满足

4. ModalBottomSheet对话框

ModalBottomSheet用于从底部弹出的对话框,适合展示选项和筛选条件。

基本用法

showModalBottomSheet(
  context: context,
  builder: (context) => Container(
    padding: const EdgeInsets.all(16),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        // 对话框内容
      ],
    ),
  ),
);

关键属性

  • context:上下文
  • builder:构建器函数
  • isScrollControlled:是否可滚动
  • shape:形状
  • backgroundColor:背景颜色

使用场景

  • 筛选条件选择
  • 选项列表
  • 操作菜单
  • 表单输入

设计要点

  • 使用mainAxisSize: MainAxisSize.min自适应高度
  • 添加圆角提升美观度
  • 选择后自动关闭对话框
  • 提供清晰的标题说明

5. DropdownButtonFormField下拉选择

DropdownButtonFormField是带表单样式的下拉选择组件。

基本用法

DropdownButtonFormField<String>(
  value: _selectedCategory,
  decoration: const InputDecoration(
    labelText: '分类',
    border: OutlineInputBorder(),
  ),
  items: _categories.map((category) {
    return DropdownMenuItem(
      value: category,
      child: Text(category),
    );
  }).toList(),
  onChanged: (value) {
    setState(() {
      _selectedCategory = value!;
      _applyFilters();
    });
  },
)

关键属性

  • value:当前选中值
  • decoration:装饰配置
  • items:选项列表
  • onChanged:选择回调

使用技巧

  • 使用泛型指定值类型
  • 提供清晰的标签
  • 选择后立即应用
  • 配合InputDecoration美化

6. LinearProgressIndicator进度条

LinearProgressIndicator用于显示线性进度,适合可视化数据占比。

基本用法

LinearProgressIndicator(
  value: percentage / 100,
  backgroundColor: Colors.grey[200],
  color: Colors.deepOrange,
)

关键属性

  • value:进度值(0-1)
  • backgroundColor:背景颜色
  • color:进度颜色
  • minHeight:最小高度

使用场景

  • 数据占比展示
  • 加载进度
  • 统计可视化
  • 完成度显示

设计要点

  • 配合百分比文字说明
  • 使用主题色保持一致性
  • 设置合适的高度
  • 添加背景色对比

7. 渐变背景设计

使用LinearGradient创建渐变背景,提升视觉效果。

基本用法

Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
      colors: [
        project.categoryColor.withValues(alpha: 0.3),
        project.categoryColor.withValues(alpha: 0.1),
      ],
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
    ),
  ),
  child: // 内容
)

关键属性

  • colors:颜色列表
  • begin:起始位置
  • end:结束位置
  • stops:颜色停止点

使用场景

  • 页面头部背景
  • 卡片背景
  • 按钮背景
  • 装饰元素

设计技巧

  • 使用透明度创建柔和效果
  • 颜色过渡自然
  • 配合内容颜色
  • 避免过于鲜艳

8. 状态管理最佳实践

在非遗文化查询应用中的状态管理。

状态变量

int _selectedIndex = 0;                    // 选中页面索引
List<HeritageProject> _allProjects = [];   // 所有项目
List<HeritageProject> _filteredProjects = []; // 筛选后项目
List<Inheritor> _allInheritors = [];       // 所有传承人
List<Inheritor> _filteredInheritors = [];  // 筛选后传承人
String _searchQuery = '';                  // 搜索关键词
String _selectedCategory = '全部';          // 选中分类
String _selectedLevel = '全部';             // 选中等级
String _selectedProvince = '全部';          // 选中省份

状态更新

void _applyFilters() {
  setState(() {
    // 更新筛选结果
    _filteredProjects = _allProjects.where((project) {
      // 筛选逻辑
      return true;
    }).toList();
    
    _filteredInheritors = _allInheritors.where((inheritor) {
      // 筛选逻辑
      return true;
    }).toList();
  });
}

最佳实践

  • 只在setState中修改状态
  • 避免在build方法中修改状态
  • 合理拆分Widget减少重建范围
  • 使用const构造函数优化性能
  • 及时清理不用的监听器

9. 数据模型设计

良好的数据模型设计是应用的基础。

模型层次

HeritageProject (非遗项目)
    ├── 基本信息(名称、分类、等级)
    ├── 地理信息(省份、城市)
    ├── 详细信息(简介、历史、特色)
    └── 元数据(批准时间、批次、状态)

Inheritor (传承人)
    ├── 个人信息(姓名、性别、年龄)
    ├── 传承信息(项目、等级)
    ├── 地理信息(省份、城市)
    └── 详细信息(简介、成就、技艺、荣誉)

设计原则

  • 字段命名清晰
  • 类型选择合理
  • 提供计算属性
  • 避免数据冗余
  • 便于扩展

10. 性能优化技巧

提升应用性能的实用技巧。

ListView优化

// 使用ListView.builder实现懒加载
ListView.builder(
  itemCount: _filteredProjects.length,
  itemBuilder: (context, index) {
    return _buildProjectCard(_filteredProjects[index]);
  },
)

const构造函数

// 使用const减少重建
const Text('非遗文化查询')
const Icon(Icons.category)
const SizedBox(height: 16)

避免不必要的重建

// 将不变的Widget提取为独立组件
class StaticHeader extends StatelessWidget {
  const StaticHeader({super.key});
  
  
  Widget build(BuildContext context) {
    return const Text('标题');
  }
}

优化筛选逻辑

// 提前返回,减少不必要的判断
if (_searchQuery.isNotEmpty) {
  final query = _searchQuery.toLowerCase();
  if (!project.name.toLowerCase().contains(query)) {
    return false; // 提前返回
  }
}

性能优化建议

  • 使用ListView.builder懒加载
  • 合理使用const
  • 避免在build中创建对象
  • 减少Widget嵌套层级
  • 使用Key优化列表性能

功能扩展方向

1. 真实数据接入

接入真实的非遗数据API,提供准确的项目和传承人信息。

实现思路

  • 接入文化和旅游部非遗数据库API
  • 使用地方非遗保护中心数据
  • 定期更新数据
  • 缓存数据到本地
  • 离线模式支持

数据来源

  • 国家级非遗名录
  • 省级非遗名录
  • 市县级非遗名录
  • 传承人数据库
  • 非遗保护单位信息

2. 地图可视化

在地图上标注非遗项目和传承人位置,提供地理可视化。

实现思路

  • 集成地图SDK
  • 标注项目位置
  • 显示传承人分布
  • 支持地图缩放
  • 点击标注查看详情

功能设计

  • 热力图展示密度
  • 聚合标注
  • 路线规划
  • 周边查询
  • 地图筛选

3. 视频和图片展示

添加多媒体内容,更直观展示非遗项目。

实现思路

  • 项目展示视频
  • 技艺演示视频
  • 作品图片展示
  • 传承人照片
  • 活动现场图片

功能设计

  • 视频播放器
  • 图片画廊
  • 全屏查看
  • 下载保存
  • 分享功能

4. 学习和体验

提供非遗学习和体验功能。

实现思路

  • 在线课程
  • 视频教程
  • 技艺讲解
  • 互动问答
  • 学习打卡

功能设计

  • 课程列表
  • 学习进度
  • 证书获取
  • 社区交流
  • 作品展示

5. 活动和展览

展示非遗相关活动和展览信息。

实现思路

  • 活动日历
  • 展览信息
  • 在线报名
  • 活动提醒
  • 签到打卡

功能设计

  • 活动列表
  • 详情页面
  • 地图导航
  • 评价反馈
  • 照片分享

6. 收藏和关注

支持收藏项目和关注传承人。

实现思路

  • 收藏项目列表
  • 关注传承人列表
  • 同步到云端
  • 推送更新通知
  • 分享收藏

功能设计

  • 收藏夹管理
  • 分类整理
  • 导出功能
  • 批量操作
  • 推荐相似

7. 社交互动

添加社交功能,促进文化交流。

实现思路

  • 评论功能
  • 点赞功能
  • 分享功能
  • 话题讨论
  • 用户互动

功能设计

  • 评论区
  • 点赞统计
  • 分享到社交平台
  • 话题标签
  • 用户主页

8. 数据统计分析

提供更详细的数据统计和分析。

实现思路

  • 项目数量趋势
  • 地域分布分析
  • 传承人年龄分析
  • 濒危项目统计
  • 保护成效评估

功能设计

  • 图表展示
  • 数据导出
  • 报告生成
  • 对比分析
  • 预测趋势

常见问题解答

1. 如何获取真实的非遗数据?

问题:模拟数据不够真实,如何获取真实的非遗数据?

解答
可以通过以下途径获取真实数据:

  1. 官方数据源

    • 文化和旅游部官网
    • 中国非物质文化遗产网
    • 各省文化厅官网
    • 地方非遗保护中心
  2. 开放数据平台

    • 政府开放数据平台
    • 文化数据开放平台
    • 学术数据库
  3. 数据采集

    • 网络爬虫采集
    • 人工整理
    • 合作获取

注意事项

  • 遵守数据使用规范
  • 注明数据来源
  • 定期更新数据
  • 保护隐私信息

2. 如何实现地图标注功能?

问题:如何在地图上标注非遗项目位置?

解答
在鸿蒙平台上,可以使用模拟的方式实现地图标注:

class MapMarker {
  final String id;
  final String name;
  final double latitude;
  final double longitude;
  final String type;

  MapMarker({
    required this.id,
    required this.name,
    required this.latitude,
    required this.longitude,
    required this.type,
  });
}

class SimpleMapView extends StatelessWidget {
  final List<MapMarker> markers;

  const SimpleMapView({super.key, required this.markers});

  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey[200],
      child: Stack(
        children: markers.map((marker) {
          // 将经纬度转换为屏幕坐标(简化版)
          final x = (marker.longitude - 70) * 10;
          final y = (marker.latitude - 15) * 10;
          
          return Positioned(
            left: x,
            top: y,
            child: GestureDetector(
              onTap: () {
                // 显示标注详情
              },
              child: Icon(
                Icons.location_on,
                color: Colors.red,
                size: 32,
              ),
            ),
          );
        }).toList(),
      ),
    );
  }
}

实现要点

  • 使用Stack布局标注
  • Positioned定位标注位置
  • GestureDetector处理点击
  • 坐标转换算法

3. 如何优化大量数据的加载?

问题:当非遗项目数量很多时,如何优化加载性能?

解答
可以采用以下优化策略:

  1. 分页加载
class PaginatedList extends StatefulWidget {
  
  State<PaginatedList> createState() => _PaginatedListState();
}

class _PaginatedListState extends State<PaginatedList> {
  final List<HeritageProject> _displayedProjects = [];
  int _currentPage = 0;
  final int _pageSize = 20;
  bool _isLoading = false;

  
  void initState() {
    super.initState();
    _loadMore();
  }

  void _loadMore() {
    if (_isLoading) return;
    
    setState(() {
      _isLoading = true;
    });

    // 模拟加载延迟
    Future.delayed(const Duration(milliseconds: 500), () {
      final start = _currentPage * _pageSize;
      final end = start + _pageSize;
      final newProjects = _allProjects.sublist(
        start,
        end > _allProjects.length ? _allProjects.length : end,
      );

      setState(() {
        _displayedProjects.addAll(newProjects);
        _currentPage++;
        _isLoading = false;
      });
    });
  }

  
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _displayedProjects.length + 1,
      itemBuilder: (context, index) {
        if (index == _displayedProjects.length) {
          if (_isLoading) {
            return const Center(child: CircularProgressIndicator());
          } else if (_displayedProjects.length < _allProjects.length) {
            return TextButton(
              onPressed: _loadMore,
              child: const Text('加载更多'),
            );
          } else {
            return const Center(child: Text('没有更多了'));
          }
        }
        return _buildProjectCard(_displayedProjects[index]);
      },
    );
  }
}
  1. 虚拟滚动

    • 使用ListView.builder
    • 只渲染可见区域
    • 自动回收不可见项
  2. 数据缓存

    • 缓存筛选结果
    • 缓存图片资源
    • 使用内存缓存
  3. 懒加载

    • 延迟加载详情
    • 按需加载图片
    • 预加载下一页

4. 如何实现离线功能?

问题:如何让应用支持离线查看?

解答
实现离线功能的关键是数据持久化:

import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';

class OfflineDataManager {
  static const String _projectsKey = 'cached_projects';
  static const String _inheritorsKey = 'cached_inheritors';

  // 保存项目数据
  Future<void> saveProjects(List<HeritageProject> projects) async {
    final prefs = await SharedPreferences.getInstance();
    final jsonList = projects.map((p) => p.toJson()).toList();
    await prefs.setString(_projectsKey, jsonEncode(jsonList));
  }

  // 加载项目数据
  Future<List<HeritageProject>> loadProjects() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonString = prefs.getString(_projectsKey);
    if (jsonString == null) return [];
    
    final jsonList = jsonDecode(jsonString) as List;
    return jsonList.map((json) => HeritageProject.fromJson(json)).toList();
  }

  // 保存传承人数据
  Future<void> saveInheritors(List<Inheritor> inheritors) async {
    final prefs = await SharedPreferences.getInstance();
    final jsonList = inheritors.map((i) => i.toJson()).toList();
    await prefs.setString(_inheritorsKey, jsonEncode(jsonList));
  }

  // 加载传承人数据
  Future<List<Inheritor>> loadInheritors() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonString = prefs.getString(_inheritorsKey);
    if (jsonString == null) return [];
    
    final jsonList = jsonDecode(jsonString) as List;
    return jsonList.map((json) => Inheritor.fromJson(json)).toList();
  }

  // 检查是否有缓存数据
  Future<bool> hasCachedData() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.containsKey(_projectsKey);
  }

  // 清除缓存
  Future<void> clearCache() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove(_projectsKey);
    await prefs.remove(_inheritorsKey);
  }
}

离线策略

  • 首次加载时缓存数据
  • 定期更新缓存
  • 离线时使用缓存
  • 提示数据更新时间

5. 如何添加多语言支持?

问题:如何让应用支持多语言?

解答
可以使用Flutter的国际化功能:

// 1. 定义语言资源
class AppLocalizations {
  final Locale locale;

  AppLocalizations(this.locale);

  static AppLocalizations of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
  }

  static const LocalizationsDelegate<AppLocalizations> delegate =
      _AppLocalizationsDelegate();

  static final Map<String, Map<String, String>> _localizedValues = {
    'zh': {
      'app_title': '非遗文化查询',
      'projects': '非遗项目',
      'inheritors': '传承人',
      'map': '地域分布',
      'search_hint': '搜索非遗项目、传承人或地区',
      'filter': '筛选',
      'category': '分类',
      'level': '等级',
      'province': '省份',
    },
    'en': {
      'app_title': 'Intangible Cultural Heritage',
      'projects': 'Projects',
      'inheritors': 'Inheritors',
      'map': 'Distribution',
      'search_hint': 'Search projects, inheritors or regions',
      'filter': 'Filter',
      'category': 'Category',
      'level': 'Level',
      'province': 'Province',
    },
  };

  String get appTitle => _localizedValues[locale.languageCode]!['app_title']!;
  String get projects => _localizedValues[locale.languageCode]!['projects']!;
  String get inheritors => _localizedValues[locale.languageCode]!['inheritors']!;
  // ... 更多翻译
}

// 2. 在MaterialApp中配置
MaterialApp(
  localizationsDelegates: [
    AppLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh', 'CN'),
    const Locale('en', 'US'),
  ],
  // ...
)

// 3. 使用翻译
Text(AppLocalizations.of(context).appTitle)

多语言支持要点

  • 提取所有文本为资源
  • 支持主要语言
  • 考虑文本长度变化
  • 测试不同语言显示

项目总结

核心功能流程

搜索

筛选

切换页面

点击项目

收藏

分享

启动应用

生成模拟数据

显示非遗项目列表

用户操作

实时筛选

打开筛选对话框

切换到传承人/地域分布

显示项目详情

选择筛选条件

显示对应页面

查看详细信息

详情页操作

添加到收藏

分享功能

数据流转

数据层状态管理界面用户数据层状态管理界面用户启动应用初始化状态生成模拟数据返回项目和传承人数据更新界面显示项目列表输入搜索关键词更新搜索查询筛选数据返回筛选结果更新列表显示搜索结果打开筛选对话框显示筛选选项选择筛选条件更新筛选条件应用筛选返回筛选结果更新列表显示筛选结果点击项目卡片获取项目详情查询项目数据返回详情数据显示详情页展示项目详情

技术架构

Flutter应用

UI层

业务逻辑层

数据层

主页面

项目列表

传承人列表

地域分布

详情页

状态管理

搜索筛选

数据生成

统计分析

数据模型

模拟数据

计算属性

项目特色

  1. 丰富的数据展示:50个非遗项目,30位传承人,涵盖10大分类
  2. 智能搜索筛选:支持关键词搜索和多维度筛选
  3. 详细信息展示:项目简介、历史渊源、特色、传承人档案
  4. 地域分布统计:可视化展示各省份非遗项目分布
  5. 等级标识清晰:国家级、省级、市级、县级四个等级
  6. 传承状态标注:传承良好、濒危状态一目了然
  7. 用户体验优秀:Material Design 3设计,交互流畅
  8. 无需额外依赖:纯Flutter实现,无需第三方包

学习收获

通过本项目,你将掌握:

  1. Flutter基础:Widget组合、状态管理、页面导航
  2. NavigationBar:Material Design 3底部导航使用
  3. 数据模型:复杂数据结构设计和计算属性
  4. 集合操作:筛选、排序、统计等List操作
  5. 搜索功能:实时搜索和多条件筛选实现
  6. 对话框:ModalBottomSheet和DropdownButton使用
  7. 卡片布局:Card和InkWell组合使用
  8. 渐变背景:LinearGradient创建渐变效果
  9. 数据可视化:LinearProgressIndicator展示统计数据
  10. UI设计:颜色搭配、图标使用、布局设计

性能优化建议

  1. 列表优化

    • 使用ListView.builder实现懒加载
    • 避免在itemBuilder中创建复杂对象
    • 合理使用Key优化列表性能
  2. 搜索优化

    • 添加防抖功能,避免频繁筛选
    • 缓存筛选结果
    • 异步处理大量数据
  3. 状态管理

    • 合理使用setState,避免不必要的重建
    • 拆分Widget减少重建范围
    • 使用const构造函数
  4. 内存管理

    • 及时释放不用的资源
    • 避免内存泄漏
    • 合理使用缓存
  5. 渲染优化

    • 减少Widget嵌套层级
    • 避免在build中执行耗时操作
    • 使用RepaintBoundary隔离重绘

后续优化方向

  1. 真实数据:接入真实的非遗数据API
  2. 地图功能:在地图上标注项目和传承人位置
  3. 多媒体:添加视频和图片展示
  4. 学习功能:提供在线课程和教程
  5. 活动信息:展示非遗相关活动和展览
  6. 收藏关注:支持收藏项目和关注传承人
  7. 社交互动:添加评论、点赞、分享功能
  8. 数据分析:提供更详细的统计分析
  9. 离线功能:支持离线查看缓存数据
  10. 多语言:支持中英文等多语言

文化价值

本项目不仅是一个技术实践,更是对中华优秀传统文化的传承和弘扬:

  1. 文化传播:让更多人了解非遗文化
  2. 保护意识:提高非遗保护意识
  3. 传承推广:促进非遗技艺传承
  4. 教育价值:作为文化教育工具
  5. 社会意义:推动文化自信建设

开发建议

  1. 循序渐进:从基础功能开始,逐步完善
  2. 注重体验:关注用户体验和交互设计
  3. 代码规范:保持代码整洁和可维护性
  4. 测试验证:充分测试各项功能
  5. 持续优化:根据反馈不断改进

项目亮点

  1. 教育意义:传播非遗文化知识
  2. 技术实践:Flutter开发完整实践
  3. 设计美观:Material Design 3设计
  4. 功能完整:查询、筛选、统计、详情
  5. 易于扩展:良好的代码结构
  6. 无需依赖:纯Flutter实现
  7. 性能优秀:流畅的用户体验
  8. 文化价值:弘扬传统文化

本项目提供了一个完整的非遗文化查询应用框架,涵盖了项目查询、传承人信息、地域分布统计等核心功能。你可以在此基础上继续扩展,打造更加专业的非遗文化传播平台,为中华优秀传统文化的保护和传承贡献力量。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐