Flutter & OpenHarmony 社交App动态卡片组件开发指南
本文介绍了如何在Flutter和OpenHarmony平台上实现动态卡片组件。Flutter部分详细讲解了Post数据模型的定义和PostCard组件的构建过程,包括用户信息展示、动态内容渲染、图片网格布局以及底部互动按钮的实现。组件采用响应式设计,支持点赞状态切换、内容条件渲染等功能,并通过回调函数处理用户交互。整体实现遵循了模块化和关注点分离的设计原则,代码结构清晰,可扩展性强,适用于社交应用

前言
动态卡片是社交应用信息流的核心展示单元,承载着用户发布的文字、图片、视频等多种内容形式。一个设计精良的动态卡片需要支持多种内容类型、丰富的交互操作以及良好的视觉层次。本文将深入讲解如何在Flutter和OpenHarmony平台上构建功能完善的动态卡片组件。
Flutter动态卡片实现
首先定义动态数据模型。
class Post {
final String id;
final String userId;
final String userName;
final String userAvatar;
final String content;
final List<String> images;
final DateTime createdAt;
final int likeCount;
final int commentCount;
final int shareCount;
final bool isLiked;
Post({
required this.id,
required this.userId,
required this.userName,
required this.userAvatar,
required this.content,
this.images = const [],
required this.createdAt,
this.likeCount = 0,
this.commentCount = 0,
this.shareCount = 0,
this.isLiked = false,
});
}
Post类包含动态的完整信息,images列表支持多图展示,各种计数字段记录互动数据。使用const空列表作为默认值避免每次创建新实例。
class PostCard extends StatelessWidget {
final Post post;
final VoidCallback onLike;
final VoidCallback onComment;
final VoidCallback onShare;
final VoidCallback onUserTap;
const PostCard({
Key? key,
required this.post,
required this.onLike,
required this.onComment,
required this.onShare,
required this.onUserTap,
}) : super(key: key);
PostCard组件接收Post数据和各种交互回调。将回调函数作为参数传入而不是在组件内部处理,遵循了关注点分离的设计原则。
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: onUserTap,
child: Row(
children: [
CircleAvatar(
radius: 20,
backgroundImage: NetworkImage(post.userAvatar),
),
SizedBox(width: 12),
Card组件提供卡片样式和阴影效果。头部区域显示用户信息,GestureDetector让整个区域可点击跳转到用户主页。CircleAvatar显示用户头像,radius设置为20像素适合列表项展示。
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
post.userName,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 15,
),
),
Text(
_formatTime(post.createdAt),
style: TextStyle(
color: Colors.grey,
fontSize: 12,
),
),
],
),
),
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () {},
),
],
),
),
用户名和发布时间垂直排列,Expanded确保文本区域自适应宽度。更多操作按钮放在右侧,点击可弹出举报、收藏等选项菜单。
SizedBox(height: 12),
Text(
post.content,
style: TextStyle(fontSize: 15),
),
if (post.images.isNotEmpty) ...[
SizedBox(height: 12),
_buildImageGrid(),
],
动态内容文本直接显示,字号15像素保证可读性。如果有图片则显示图片网格,使用展开运算符…将条件渲染的多个Widget添加到列表中。
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildActionButton(
icon: post.isLiked
? Icons.favorite
: Icons.favorite_border,
label: '${post.likeCount}',
color: post.isLiked ? Colors.red : Colors.grey,
onTap: onLike,
),
_buildActionButton(
icon: Icons.chat_bubble_outline,
label: '${post.commentCount}',
color: Colors.grey,
onTap: onComment,
),
_buildActionButton(
icon: Icons.share_outlined,
label: '${post.shareCount}',
color: Colors.grey,
onTap: onShare,
),
],
),
],
),
),
);
}
底部操作栏包含点赞、评论、分享三个按钮,使用spaceAround均匀分布。点赞按钮根据状态切换图标和颜色,提供清晰的视觉反馈。
Widget _buildActionButton({
required IconData icon,
required String label,
required Color color,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Row(
children: [
Icon(icon, size: 20, color: color),
SizedBox(width: 4),
Text(
label,
style: TextStyle(color: color, fontSize: 13),
),
],
),
);
}
_buildActionButton方法构建单个操作按钮,图标和数字水平排列。使用命名参数提高代码可读性,required确保必要参数不会遗漏。
Widget _buildImageGrid() {
final count = post.images.length;
if (count == 1) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
post.images[0],
fit: BoxFit.cover,
height: 200,
),
);
}
return GridView.count(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
crossAxisCount: count == 4 ? 2 : 3,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: post.images.take(9).map((url) {
return ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Image.network(url, fit: BoxFit.cover),
);
}).toList(),
);
}
String _formatTime(DateTime time) {
final diff = DateTime.now().difference(time);
if (diff.inMinutes < 60) return '${diff.inMinutes}分钟前';
if (diff.inHours < 24) return '${diff.inHours}小时前';
return '${diff.inDays}天前';
}
}
_buildImageGrid根据图片数量调整布局,单图全宽展示,多图使用网格布局。shrinkWrap和NeverScrollableScrollPhysics确保GridView在Column中正确显示。take(9)限制最多显示9张图片。
OpenHarmony ArkTS实现
鸿蒙系统上的动态卡片实现。
@Component
struct PostCard {
@Prop post: Post
onLike: () => void = () => {}
onComment: () => void = () => {}
onShare: () => void = () => {}
onUserTap: () => void = () => {}
build() {
Column() {
Row() {
Image(this.post.userAvatar)
.width(40)
.height(40)
.borderRadius(20)
Column() {
Text(this.post.userName)
.fontSize(15)
.fontWeight(FontWeight.Medium)
Text(this.formatTime(this.post.createdAt))
.fontSize(12)
.fontColor(Color.Gray)
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
Column作为根容器垂直排列各个区域。头部Row显示用户信息,布局结构与Flutter版本一致。
Image($r('app.media.ic_more'))
.width(24)
.height(24)
.onClick(() => {})
}
.width('100%')
.onClick(() => this.onUserTap())
Text(this.post.content)
.fontSize(15)
.width('100%')
.margin({ top: 12 })
更多按钮使用Image组件加载图标资源。动态内容设置width为100%确保左对齐。
if (this.post.images.length > 0) {
Grid() {
ForEach(this.post.images.slice(0, 9), (url: string) => {
GridItem() {
Image(url)
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.borderRadius(4)
}
})
}
.columnsTemplate(this.getColumnsTemplate())
.rowsGap(4)
.columnsGap(4)
.height(this.getGridHeight())
.margin({ top: 12 })
}
Grid组件实现图片网格,columnsTemplate动态设置列数。ForEach遍历图片列表,slice(0, 9)限制最多9张。
Row() {
this.ActionButton(
this.post.isLiked
? $r('app.media.ic_heart_filled')
: $r('app.media.ic_heart_outline'),
this.post.likeCount.toString(),
this.post.isLiked ? Color.Red : Color.Gray,
() => this.onLike()
)
this.ActionButton(
$r('app.media.ic_comment'),
this.post.commentCount.toString(),
Color.Gray,
() => this.onComment()
)
this.ActionButton(
$r('app.media.ic_share'),
this.post.shareCount.toString(),
Color.Gray,
() => this.onShare()
)
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.margin({ top: 12 })
}
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ left: 16, right: 16, top: 8, bottom: 8 })
}
底部操作栏使用Row和justifyContent实现均匀分布。整个卡片设置圆角和外边距。
@Builder
ActionButton(icon: Resource, label: string, color: ResourceColor, onTap: () => void) {
Row() {
Image(icon)
.width(20)
.height(20)
.fillColor(color)
Text(label)
.fontSize(13)
.fontColor(color)
.margin({ left: 4 })
}
.onClick(onTap)
}
getColumnsTemplate(): string {
const count = this.post.images.length
if (count === 1) return '1fr'
if (count === 4) return '1fr 1fr'
return '1fr 1fr 1fr'
}
getGridHeight(): number {
const count = this.post.images.length
if (count === 1) return 200
if (count <= 3) return 100
if (count <= 6) return 208
return 316
}
formatTime(timestamp: number): string {
const diff = Date.now() - timestamp
const minutes = Math.floor(diff / 60000)
if (minutes < 60) return `${minutes}分钟前`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `${hours}小时前`
return `${Math.floor(hours / 24)}天前`
}
}
@Builder定义操作按钮构建方法。getColumnsTemplate和getGridHeight根据图片数量返回不同的布局参数。formatTime格式化时间戳。
总结
本文详细介绍了动态卡片组件在Flutter和OpenHarmony两个平台上的实现。动态卡片是社交应用信息流的核心组件,需要支持多种内容类型和丰富的交互操作。两个平台的实现都采用了组件化设计和条件渲染。在实际项目中,还可以扩展视频播放、位置信息、话题标签等功能。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)