在这里插入图片描述

前言

动态卡片是社交应用信息流的核心展示单元,承载着用户发布的文字、图片、视频等多种内容形式。一个设计精良的动态卡片需要支持多种内容类型、丰富的交互操作以及良好的视觉层次。本文将深入讲解如何在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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐