之前我们有开发了水果列表怎么能没有水果详情页面呢,这个时候需要添加核心场景。

1.水果详情页面的开发先看截图效果

该页面展示水果的详细信息,包括基本信息、营养功效、主要功效和营养成分等内容,采用卡片式布局设计。

2.页面效果

水果详情页面主要包含以下几个部分

2.1顶部导航栏

显示水果名称和返回按钮

2.2水果头部信息

展示水果图片、中英文名称、分类和简介

2.3营养功效卡片

详细描述水果的营养价值

2.4主要功效卡片

列举水果的各项功效特性

2.5营养成分卡片

以网格形式展示具体营养成分数据

3.数据设计

3.1 水果(Fruit)
class Fruit {
  final String chinese;              // 中文名
  final String name;                  // 名称
  final String english;               // 英文名
  final List<NutritionItem> nutrition; // 营养成分列表
  final String category;              // 分类
  final String colorImageUrl;         // 彩色图片URL
  final String transparentImageUrl;   // 透明图片URL
  final String benefits;              // 营养功效
  final List<String> detailImages;    // 详情图片列表
  final String detailDescription;     // 详细描述
  final String mainColor;             // 主题色
  final String mainBg;                // 背景色
  final List<Feature> features;       // 功效特性列表

  factory Fruit.fromJson(Map<String, dynamic> json) {
    return Fruit(
      chinese: json['chinese'] ?? '',
      name: json['name'] ?? '',
      english: json['english'] ?? '',
      nutrition: json['nutrition'] != null
          ? (json['nutrition'] as List)
                .map((e) => NutritionItem.fromJson(e))
                .toList()
          : [],
      category: json['category'] ?? '',
      colorImageUrl: json['colorImageUrl'] ?? '',
      transparentImageUrl: json['transparentImageUrl'] ?? '',
      benefits: json['benefits'] ?? '',
      detailImages: json['detailImages'] != null
          ? List<String>.from(json['detailImages'])
          : [],
      detailDescription: json['detailDescription'] ?? '',
      mainColor: json['mainColor'] ?? '#2196F3',
      mainBg: json['mainBg'] ?? '#E3F2FD',
      features: json['features'] != null
          ? (json['features'] as List).map((e) => Feature.fromJson(e)).toList()
          : [],
    );
  }
}
3.2 营养成分(NutritionItem)
class NutritionItem {
  final String name;   // 营养成分名称
  final String value;  // 营养成分值

  NutritionItem({required this.name, required this.value});

  factory NutritionItem.fromJson(List<dynamic> json) {
    return NutritionItem(
      name: json[0] ?? '', 
      value: json[1] ?? ''
    );
  }
}
3.3 功效特性(Feature)
class Feature {
  final String short;  // 简短描述
  final String long;   // 详细描述

  Feature({required this.short, required this.long});

  factory Feature.fromJson(Map<String, dynamic> json) {
    return Feature(
      short: json['short'] ?? '', 
      long: json['long'] ?? ''
    );
  }

4.页面实现

4.1 页面基础
class FruitDetailPage extends StatelessWidget {
  final Fruit fruit;

  const FruitDetailPage({super.key, required this.fruit});

  @override
  Widget build(BuildContext context) {
    const Color mainColor = Color(0xFF4CAF50);
    final Color bgColor = mainColor.withValues(alpha: 0.1);

    return Scaffold(
      backgroundColor: const Color(0xFFF5F5F5),
      appBar: AppBar(
        backgroundColor: mainColor,
        elevation: 0,
        leading: IconButton(
          icon: const Icon(Icons.arrow_back, color: Colors.white),
          onPressed: () => Navigator.pop(context),
        ),
        title: Text(
          '${fruit.chinese}详情',
          style: const TextStyle(
            color: Colors.white,
            fontSize: 18,
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildFruitHeader(mainColor, bgColor),
            _buildNutritionSection(),
            _buildMainEffectsSection(bgColor),
            _buildNutritionComponentsSection(bgColor, mainColor),
          ],
        ),
      ),
    );
  }
}
4.2 水果头部信息卡片

Widget _buildFruitHeader(Color mainColor, Color bgColor) {
  return Container(
    width: double.infinity,
    padding: const EdgeInsets.all(20),
    decoration: const BoxDecoration(color: Colors.white),
    child: Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 水果图标
        Container(
          width: 80,
          height: 80,
          decoration: BoxDecoration(
            color: bgColor,
            borderRadius: BorderRadius.circular(12),
          ),
          child: ClipRRect(
            borderRadius: BorderRadius.circular(12),
            child: Image.network(
              fruit.colorImageUrl,
              width: 80,
              height: 80,
              fit: BoxFit.cover,
              errorBuilder: (context, error, stackTrace) {
                return const Center(
                  child: Icon(Icons.image_not_supported, size: 40),
                );
              },
            ),
          ),
        ),
        const SizedBox(width: 16),
        // 水果信息
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  Text(
                    fruit.chinese,
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.w600,
                      color: mainColor,
                    ),
                  ),
                  const SizedBox(width: 8),
                  Flexible(
                    child: Text(
                      fruit.english,
                      style: const TextStyle(
                        fontSize: 13,
                        color: Color(0xFF9CA3AF),
                      ),
                      overflow: TextOverflow.ellipsis,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 8),
              Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: 8,
                  vertical: 4,
                ),
                decoration: BoxDecoration(
                  color: bgColor,
                  borderRadius: BorderRadius.circular(4),
                ),
                child: Text(
                  fruit.category,
                  style: TextStyle(fontSize: 12, color: mainColor),
                ),
              ),
              const SizedBox(height: 8),
              Text(
                fruit.detailDescription.isNotEmpty
                    ? fruit.detailDescription
                    : fruit.benefits,
                style: const TextStyle(
                  fontSize: 12,
                  color: Color(0xFF9CA3AF),
                  height: 1.5,
                ),
                maxLines: 3,
                overflow: TextOverflow.ellipsis,
              ),
            ],
          ),
        ),
      ],
    ),
  );
}
4.3  营养功效卡片
Widget _buildNutritionSection() {
  return Container(
    margin: const EdgeInsets.symmetric(horizontal: 16),
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12),
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          '营养功效',
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
            color: Color(0xFF4CAF50),
          ),
        ),
        const SizedBox(height: 10),
        Text(
          fruit.benefits,
          style: const TextStyle(
            fontSize: 13,
            color: Color(0xFF9CA3AF),
            height: 1.5,
          ),
        ),
      ],
    ),
  );
}
4.4 主要功效卡片
Widget _buildMainEffectsSection(Color bgColor) {
  if (fruit.features.isEmpty) return const SizedBox.shrink();

  return Container(
    margin: const EdgeInsets.symmetric(horizontal: 16),
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: bgColor,
      borderRadius: BorderRadius.circular(12),
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          '主要功效',
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
            color: Color(0xFF4CAF50),
          ),
        ),
        const SizedBox(height: 10),
        ...fruit.features.map((feature) => _buildEffectItem(feature)),
      ],
    ),
  );
}

Widget _buildEffectItem(Feature feature) {
  return Container(
    width: double.infinity,
    margin: const EdgeInsets.only(bottom: 10),
    padding: const EdgeInsets.all(12),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(8),
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          feature.short,
          style: const TextStyle(
            fontSize: 14,
            fontWeight: FontWeight.w600,
            color: Color(0xFF1F2937),
          ),
        ),
        const SizedBox(height: 6),
        Text(
          feature.long,
          style: const TextStyle(
            fontSize: 13,
            color: Color(0xFF9CA3AF),
            height: 1.5,
          ),
          softWrap: true,
        ),
      ],
    ),
  );
}
4.5 营养成分网格卡片

Widget _buildNutritionComponentsSection(Color bgColor, Color mainColor) {
  if (fruit.nutrition.isEmpty) return const SizedBox.shrink();

  return Container(
    margin: const EdgeInsets.symmetric(horizontal: 16),
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: bgColor,
      borderRadius: BorderRadius.circular(12),
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          '营养成分',
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
            color: Color(0xFF4CAF50),
          ),
        ),
        const SizedBox(height: 12),
        GridView.builder(
          shrinkWrap: true,
          physics: const NeverScrollableScrollPhysics(),
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            mainAxisSpacing: 10,
            crossAxisSpacing: 10,
            childAspectRatio: 1.8,
          ),
          itemCount: fruit.nutrition.length,
          itemBuilder: (context, index) {
            return _buildNutritionCard(fruit.nutrition[index], mainColor);
          },
        ),
      ],
    ),
  );
}

Widget _buildNutritionCard(NutritionItem item, Color mainColor) {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(8),
    ),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(
          item.name,
          style: const TextStyle(fontSize: 13, color: Color(0xFF6B7280)),
          maxLines: 1,
          overflow: TextOverflow.ellipsis,
        ),
        const SizedBox(height: 6),
        Flexible(
          child: FittedBox(
            fit: BoxFit.scaleDown,
            child: Text(
              item.value,
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: mainColor,
              ),
              maxLines: 1,
            ),
          ),
        ),
      ],
    ),
  );
}
4.5.1 GridView 配置
shrinkWrap: true让 GridView 适应内容高度
physics: NeverScrollableScrollPhysics()禁用 GridView 自身滚动
crossAxisCount: 2每行显示 2 个营养成分卡片
childAspectRatio: 1.8设置卡片宽高比为 1.8:1
FittedBox自动缩放文本以适应容器

5.颜色推荐

个人觉得做的非常好看,所以就把颜色也分享出来了

颜色用途颜色值说明
主题色0xFF4CAF50标题
浅绿背景mainColor.withValues(alpha: 0.1)半透明绿色
页面背景0xFFF5F5F5浅灰色
主文本0xFF1F2937深灰色
次要文本0xFF9CA3AF中灰色
标签文本0xFF6B7280灰色

这个页面主要是用来展示的。

6.最后

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

Logo

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

更多推荐