【开源鸿蒙跨平台开发先锋训练营】DAY15~DAY19为开源鸿蒙跨平台应用全面集成添加核心场景-水果详情页面
·
之前我们有开发了水果列表怎么能没有水果详情页面呢,这个时候需要添加核心场景。
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
更多推荐

所有评论(0)