Flutter for OpenHarmony 社团管理App实战 - 活动详情实现
本文介绍了活动详情页的设计实现,采用Flutter框架构建。页面接收Activity对象参数,通过Consumer监听数据变化确保实时更新。布局采用模块化设计,包含头部区域(带状态标识和渐变背景)、信息卡片(展示时间地点等)、进度卡片和操作按钮。头部根据活动状态显示不同颜色,信息卡片使用标准格式展示活动详情。整体设计注重数据实时性和视觉层次,提供报名/取消功能,确保用户体验流畅。

活动详情页展示活动的完整信息,包括时间地点、报名进度、活动介绍等,并提供报名和取消报名功能。
页面参数设计
详情页接收活动对象作为参数:
class ActivityDetailPage extends StatelessWidget {
final Activity activity;
const ActivityDetailPage({super.key, required this.activity});
通过构造函数传入activity对象,类型安全。
使用StatelessWidget因为页面本身不维护状态。
页面整体结构
使用Consumer监听数据变化:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('活动详情')),
body: Consumer<AppProvider>(
builder: (context, provider, _) {
final currentActivity = provider.activities.firstWhere(
(a) => a.id == activity.id,
orElse: () => activity
);
从Provider获取最新的活动数据,确保报名状态实时更新。
orElse处理找不到的情况,返回传入的原始数据。
页面布局:
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(currentActivity),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoCard(currentActivity),
const SizedBox(height: 16),
_buildProgressCard(currentActivity),
const SizedBox(height: 16),
_buildDescriptionCard(currentActivity),
const SizedBox(height: 24),
_buildActionButton(context, provider, currentActivity),
],
),
),
],
),
);
},
),
);
}
SingleChildScrollView让内容可滚动。
模块顺序:头部、信息卡片、进度卡片、详情卡片、操作按钮。
头部区域
根据状态设置颜色:
Widget _buildHeader(Activity activity) {
Color statusColor;
switch (activity.status) {
case '报名中':
statusColor = Colors.green;
break;
case '即将开始':
statusColor = Colors.orange;
break;
case '已结束':
statusColor = Colors.grey;
break;
default:
statusColor = Colors.blue;
}
switch语句根据状态返回对应颜色。
颜色语义和活动列表保持一致。
头部容器:
return Container(
height: 180,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFF4A90E2),
const Color(0xFF357ABD).withOpacity(0.8)
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
渐变背景从左上到右下,蓝色调和主题一致。
180像素的高度足够展示标题和状态。
背景图标和内容:
child: Stack(
children: [
Center(
child: Icon(
Icons.event,
size: 80,
color: Colors.white.withOpacity(0.3)
)
),
Positioned(
left: 16,
bottom: 16,
right: 16,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack叠加背景图标和前景内容。
Positioned把内容定位在左下角。
状态标签和标题:
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(12),
),
child: Text(
activity.status,
style: const TextStyle(color: Colors.white, fontSize: 12)
),
),
const SizedBox(height: 8),
Text(
activity.title,
style: const TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold
)
),
const SizedBox(height: 4),
Text(
activity.clubName,
style: const TextStyle(color: Colors.white70, fontSize: 14)
),
],
),
),
],
),
);
}
状态标签用实心背景,在渐变背景上更醒目。
标题22像素白色粗体,是头部最重要的信息。
信息卡片
构建信息卡片:
Widget _buildInfoCard(Activity activity) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildInfoRow(
Icons.access_time,
'开始时间',
DateFormat('yyyy-MM-dd HH:mm').format(activity.startTime)
),
const Divider(),
_buildInfoRow(
Icons.access_time_filled,
'结束时间',
DateFormat('yyyy-MM-dd HH:mm').format(activity.endTime)
),
开始和结束时间用不同的图标区分。
日期格式化成完整的年月日时分。
继续添加信息行:
const Divider(),
_buildInfoRow(Icons.location_on, '活动地点', activity.location),
const Divider(),
_buildInfoRow(Icons.person, '组织者', activity.organizer),
],
),
),
);
}
四行信息涵盖了活动的基本情况。
Divider分割线让各行更清晰。
封装信息行组件:
Widget _buildInfoRow(IconData icon, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Icon(icon, size: 20, color: const Color(0xFF4A90E2)),
const SizedBox(width: 12),
Text(label, style: const TextStyle(color: Colors.grey)),
const Spacer(),
Flexible(
child: Text(
value,
style: const TextStyle(fontWeight: FontWeight.w500),
textAlign: TextAlign.right
)
),
],
),
);
}
Flexible让值文字可以换行,避免溢出。
textAlign右对齐让布局更整齐。
报名进度卡片
构建进度卡片:
Widget _buildProgressCard(Activity activity) {
final progress = activity.currentParticipants / activity.maxParticipants;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'报名进度',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)
),
Text(
'${activity.currentParticipants}/${activity.maxParticipants}人',
style: const TextStyle(
color: Color(0xFF4A90E2),
fontWeight: FontWeight.bold
)
),
],
),
标题和人数分居两端。
人数用蓝色粗体突出显示。
进度条:
const SizedBox(height: 12),
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: LinearProgressIndicator(
value: progress,
minHeight: 10,
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation<Color>(
progress >= 1 ? Colors.red : const Color(0xFF4A90E2)
),
),
),
ClipRRect给进度条加圆角。
满员时变红色警示。
剩余名额提示:
const SizedBox(height: 8),
Text(
progress >= 1
? '名额已满'
: '还剩${activity.maxParticipants - activity.currentParticipants}个名额',
style: TextStyle(
color: progress >= 1 ? Colors.red : Colors.grey,
fontSize: 13
)
),
],
),
),
);
}
满员显示红色"名额已满",否则显示剩余名额。
文字颜色和进度条颜色保持一致。
活动详情卡片
构建详情卡片:
Widget _buildDescriptionCard(Activity activity) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'活动详情',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)
),
const SizedBox(height: 12),
Text(
activity.description,
style: const TextStyle(color: Colors.grey, height: 1.6)
),
],
),
),
);
}
行高1.6让多行文字阅读更舒适。
灰色文字作为正文内容。
操作按钮
处理已结束状态:
Widget _buildActionButton(
BuildContext context,
AppProvider provider,
Activity activity
) {
final canJoin = activity.status == '报名中' &&
activity.currentParticipants < activity.maxParticipants;
if (activity.status == '已结束') {
return SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24)
)
),
onPressed: null,
child: const Text(
'活动已结束',
style: TextStyle(fontSize: 16, color: Colors.white)
),
),
);
}
已结束的活动显示灰色禁用按钮。
onPressed为null时按钮自动禁用。
正常状态的按钮:
return SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: activity.isJoined
? Colors.red
: (canJoin ? const Color(0xFF4A90E2) : Colors.grey),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24)
),
),
已报名显示红色取消按钮,可报名显示蓝色报名按钮,满员显示灰色。
圆角24像素的胶囊形按钮。
点击事件处理:
onPressed: canJoin || activity.isJoined
? () {
if (activity.isJoined) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('确认取消'),
content: const Text('确定要取消报名吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('取消')
),
TextButton(
onPressed: () {
provider.cancelActivity(activity.id);
Navigator.pop(ctx);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已取消报名'))
);
},
child: const Text(
'确定',
style: TextStyle(color: Colors.red)
),
),
],
),
);
取消报名需要二次确认,防止误操作。
确认后调用provider方法并显示SnackBar反馈。
报名处理:
} else {
provider.joinActivity(activity.id);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('报名成功'))
);
}
}
: null,
child: Text(
activity.isJoined
? '取消报名'
: (canJoin ? '立即报名' : '名额已满'),
style: const TextStyle(fontSize: 16, color: Colors.white),
),
),
);
}
}
报名操作直接执行,不需要确认。
按钮文字根据状态动态变化。
小结
活动详情页通过渐变头部展示活动标题和状态,信息卡片展示时间地点等基本信息,进度卡片直观显示报名情况。操作按钮根据活动状态和用户报名状态动态变化,取消报名有二次确认,整体交互完善。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)