当前位置: 首页 > news >正文

HarmonyOS新闻卡片组件开发实战:自定义组件与List渲染深度解析

文章目录

    • 为什么“死磕”这个新闻项目?
    • 🚀 项目效果“卖家秀”
    • 跟我一步步“肝”代码
      • 第一步:设计新闻“身份证”(数据模型)
      • 第二步:造“轮子”!@Component 自定义卡片
      • 第三步:卡片“颜值担当”——图片区(Stack 布局)
      • 第四步:卡片“灵魂”——内容区(Column + Row)
      • 第四步(续):卡片的“脚”——底部信息栏(交互核心)
      • 第五步:“组装” App!——主页面架构
      • 第六步:实现“横向滚动”的分类筛选条
      • 第七步:“魔法”发生地!—— 响应式新闻列表
    • 😱 “萌新”踩坑(含泪)实录
      • 巨坑 1:我改了“爹”的数据!(@Prop vs @State)
      • 小坑 2:图片加载“翻车”
      • 小坑 3:布局“偏心眼”
    • “精装修”建议(下一步玩啥)
    • 总结:你又“行”了!

摘要: 本文详细讲解HarmonyOS新闻卡片组件的完整开发流程,涵盖自定义组件设计、List列表渲染、分类筛选功能实现。通过具体代码示例,分享新闻数据模型构建、复杂布局技巧、父子组件通信机制以及交互体验优化。适合HarmonyOS初学者学习组件化开发和列表展示功能,帮助开发者快速掌握构建现代资讯类应用的核心技术。

标签: HarmonyOS 新闻卡片 自定义组件 List渲染 分类筛选 组件通信 移动开发

大家好!鸿蒙学习“肝帝”又上线了!今天继续我的 HarmonyOS 学习之旅,这次的挑战是——搞一个功能“全家桶”的新闻资讯 App。

这个项目包含了自定义组件List 渲染分类筛选等超多实用技巧,绝对是“进阶”必备!特别适合想从“游击队”变成“正规军”,深入学习组件化开发的小伙伴!

为什么“死磕”这个新闻项目?

上次的“国风” Demo 只是开胃菜,这次我们要“来真的”了!

为啥选这个?因为“麻雀虽小,五脏俱全”啊!做一个资讯 App,我(和你们)可以一举拿下:

  • 怎么“造零件”:掌握 @Component 装饰器,造一个“高复用”卡片!
  • 怎么“刷列表”:精通 List + ForEach,还要学会用 filter 玩筛选!
  • 怎么“叠罗汉”StackColumnRow 嵌套使用,搞定复杂布局!
  • 怎么“点个赞”:实现点赞、分类这种真实的用户交互!

🚀 项目效果“卖家秀”

先上个“卖家秀”!想象一下:打开 App,一个专业的“今日头条”风界面映入眼帘。顶部是标题栏和(能横向滚动的)分类筛选条,下面是颜值超高的瀑布流新闻卡片。

image.png

每张卡片都有图有真相,标题、摘要、发布时间、阅读量、点赞功能一应俱全。点一下“科技”,列表“唰”一下就只剩科技新闻,丝滑!

跟我一步步“肝”代码

第一步:设计新闻“身份证”(数据模型)

image.png

老规矩,“兵马未动,粮草先行”。写 App 之前,先得搞清楚咱们的数据长啥样。

class NewsItem {id: number = 0;title: string = '';summary: string = '';imageUrl: string = '';category: string = '';publishTime: string = '';readCount: number = 0;isLiked: boolean = false;constructor(id: number,title: string,summary: string,imageUrl: string,category: string,publishTime: string,readCount: number,isLiked: boolean) {this.id = id;this.title = title;this.summary = summary;this.imageUrl = imageUrl;this.category = category;this.publishTime = publishTime;this.readCount = readCount;this.isLiked = isLiked;}
}

笔记:
这个 NewsItem 类就是咱们的新闻“身份证”模板。比上次的“国风”商品复杂了点,加了图片 URL、阅读量、是不是点过赞… 毕竟是新闻嘛,要素得齐全!


第二步:造“轮子”!@Component 自定义卡片

OK,核心中的核心来了!我们要“造轮子”——一个可复用的“新闻卡片”组件 buildNewsCard

image.png

这次我们不用上次的 @Builder 了,而是用更“重量级”的 @Component

为啥?因为 @Builder 只是个“UI 蓝图”(函数),而 @Component 是一个拥有自己生命周期和状态的“独立公民”(结构体)!这对于需要内部交互(比如点赞)的组件来说,至关重要。

@Component
struct buildNewsCard {@Prop news: NewsItem // @Prop: “爹给的”数据(父组件传来)@State localNews: NewsItem = new NewsItem(0, '', '', '', '', '', 0, false) // @State: “自己的”数据(内部状态)aboutToAppear() {// 这是组件“出生”时会调用的方法if (this.news) {// 把“爹给的”数据,复制一份到“自己的”地盘上this.localNews = new NewsItem(this.news.id,this.news.title,this.news.summary,this.news.imageUrl,this.news.category,this.news.publishTime,this.news.readCount,this.news.isLiked);}}// ... build() 方法马上就来 ...
}

笔记:
注意看 @Prop@State 的用法!@Prop 是“爹给的”(父组件传来的),@State 是“自己私有的”(内部状态)。

我们在 aboutToAppear 这个“出生”生命周期方法里,把“爹给的”news 数据“复制”一份到“自己的”localNews 里。这样,我们就可以在组件内部随便“折腾”(比如点赞)这个 localNews,还不会“坑爹”(污染父组件的数据)!


第三步:卡片“颜值担当”——图片区(Stack 布局)

image.png

“轮子”有了,开始画它的 build() 方法。一个卡片得有“颜值”担当,我们先用 Stack(堆叠)布局,把图片作为“背景板”。

// 这是 buildNewsCard 组件里的 build() 方法
build() {Column({ space: 12 }) { // 用一个 Column 把图片和文字包起来// 图片区域Stack() {// 网络图片Image(this.localNews.imageUrl).width('100%').height(200).objectFit(ImageFit.Cover) // 让图片不变形地填满,裁掉多余的.borderRadius(12).alt($r('app.media.avatar')) // 翻车(加载失败)时的占位图// 分类标签Text(this.localNews.category).fontSize(12).fontColor('#FFFFFF').backgroundColor('#007DFF').borderRadius(4).padding({ left: 8, right: 8, top: 4, bottom: 4 }).position({ x: 12, y: 12 }) // 精准“贴”在左上角}// ... 内容区(下一步) ...}.backgroundColor('#FFFFFF').borderRadius(12) // 整个卡片的背景和圆角
}

笔记:
Stack 布局就是“叠叠乐”,后写的会盖在先写的上面。我们用 position 把“分类标签”精准地“贴”在左上角。objectFit(ImageFit.Cover) 是个神技,能让各种比例的图片都好看!


第四步:卡片“灵魂”——内容区(Column + Row)

image.png

光有图不行,得有“灵魂”——标题和摘要。这部分紧跟在 Stack 后面,放在外层的 Column 里。

    // ... Stack 布局结束 ...// 内容区域Column({ space: 12 }) {// 标题区域Text(this.localNews.title).fontSize(18).fontWeight(FontWeight.Medium).fontColor('#1A1A1A').maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis }) // 超出2行变“...”// 摘要区域Text(this.localNews.summary).fontSize(14).fontColor('#666666').maxLines(3).textOverflow({ overflow: TextOverflow.Ellipsis }) // 超出3行变“...”.lineHeight(20)// ... 底部信息栏(下一步) ...}.padding({ left: 16, right: 16, bottom: 16 }) // 给内容区加点内边距

笔记:
这部分全是老朋友:fontSizefontWeightmaxLines(最多两行)、textOverflow(超出部分变“…”)。这套“组合拳”打出去,UI 一下子就清爽了。


第四步(续):卡片的“脚”——底部信息栏(交互核心)

image.png

卡片快完工了,还差个“脚”——底部信息栏。一个 Row(水平布局)搞定,左边放时间和阅读量,右边放点赞。

    // ... 摘要区域结束 ...// 底部信息栏Row() {// 左侧信息(发布时间、阅读量)Column({ space: 2 }) {Row({ space: 8 }) {Image($r('app.media.shijian')).width(18).height(18)Text(this.localNews.publishTime).fontSize(12).fontColor('#999999')}Row({ space: 8 }) {Image($r('app.media.yuedu')).width(18).height(18)Text(`${this.localNews.readCount}`).fontSize(12).fontColor('#999999')}}.layoutWeight(1) // 自动“抢”走所有剩余空间,把点赞按钮挤到最右边// 点赞按钮Row({ space: 6 }) {Image(this.localNews.isLiked ? $r('app.media.dianzan2') : $r('app.media.dianzan')).width(30).height(30)Text(this.localNews.isLiked ? '已赞' : '点赞').fontSize(12).fontColor(this.localNews.isLiked ? '#007DFF' : '#999999')}.onClick(() => { // 点击时,“反转”自己的内部状态this.localNews.isLiked = !this.localNews.isLiked;}).backgroundColor(this.localNews.isLiked ? '#E6F2FF' : '#F5F5F5').borderRadius(6).padding({left: 8, right: 8, top: 4, bottom: 4}) // 给点赞按钮加点内边距}} // 内容区的 Column 结束} // 整个卡片的 Column 结束
} // build() 方法结束

笔记:
layoutWeight(1) 又是神技!它让左边的信息栏“霸道”地占据所有剩余空间,从而把点赞按钮“挤”到最右边,完美实现两端对齐!

注意看“点赞”按钮!我们用了一个“三元运算符” (this.localNews.isLiked ? ... : ...)。如果 isLikedtrue,就显示“已赞”图标和蓝色;如果是 false,就显示“点赞”图标和灰色。连背景色都换了!

onClick 时,我们只修改了 this.localNews 这个“内部状态”,这就是 @Component + @State 的威力!


第五步:“组装” App!——主页面架构

image.png

“零件”造好了,现在开始“组装”我们的 App 主页面!@Entry 登场!

@Entry
@Component
export struct NewsCardDemo {// 伪造一堆新闻数据,记得用你刚才定义的 NewsItem 类@State newsList: NewsItem[] = [new NewsItem(1, '鸿蒙新版发布!', '性能提升30%...', 'app.media.hm', '科技', '1小时前', 1024, false),new NewsItem(2, 'xx明星演唱会', '现场燃爆...', 'app.media.star', '娱乐', '2小时前', 5890, true),new NewsItem(3, '国足又...进球了!', '球员xx梅开二度...', 'app.media.gz', '体育', '3小时前', 888, false)// ... 伪造更多数据 ...];@State selectedCategory: string = '全部'; // 用@State管理当前选中的分类categories: string[] = ['全部', '科技', '娱乐', '体育', '财经'];build() {Column({ space: 0 }) {// 顶部标题栏(这里我偷懒,你也可以用@Builder写一个)Text('新闻资讯').fontSize(20).fontWeight(FontWeight.Bold).padding(16).width('100%')// 分类筛选this.buildCategoryFilter()// 新闻列表this.buildNewsList()}.width('100%').height('100%').backgroundColor('#F5F5F5')}// ... buildCategoryFilter 和 buildNewsList 马上就来 ...
}

笔记:
这个主页面也用 @State 来管理“全局”的新闻列表 newsList 和当前选中的分类 selectedCategorybuild 方法里,我们用 Column 把页面分成了“上(标题)”、“中(筛选)”、“下(列表)”三个部分。


第六步:实现“横向滚动”的分类筛选条

image.png

分类筛选条是个高频需求。用 Scroll 包一个 Row,就能让它“横向滚动”。

// 在 NewsCardDemo struct 内部
@Builder
buildCategoryFilter() {Scroll() { // 横向滚动容器Row({ space: 12 }) {ForEach(this.categories, (category: string) => {Text(category).fontSize(16).fontColor(this.selectedCategory === category ? '#FFFFFF' : '#666666') // 选中高亮.backgroundColor(this.selectedCategory === category ? '#007DFF' : '#F0F0F0') // 选中高亮.borderRadius(20).padding({left: 16, right: 16, top: 8, bottom: 8}).onClick(() => { // 关键:点击时,更新@State变量this.selectedCategory = category; })})}.padding({ left: 16, right: 16, top: 12, bottom: 12 })}.scrollBar(BarState.Off) // 关掉丑丑的滚动条
}

笔记:
又是“三元运算符”的胜利!this.selectedCategory === category ? ... : ...

最最关键的是 onClick:点击谁,谁就高亮,同时把 @State 变量 this.selectedCategory 的值改成被点击的 category

这个 @State 一变,奇迹发生了…(请看下一步)


第七步:“魔法”发生地!—— 响应式新闻列表

image.png

奇迹来了!还记得吗?ArkTS 是“响应式”的!当你点击分类条,@StateselectedCategory 一变,所有“依赖”了它的 UI 都会“自动”重新渲染!

buildNewsList 就“依赖”了它!

// 在 NewsCardDemo struct 内部
@Builder
buildNewsList() {List({ space: 12 }) {// 魔法!在这里用 filter 过滤数据!ForEach(this.newsList.filter(item =>// 如果选的是“全部”,或者 item 的分类 匹配 选中的分类this.selectedCategory === '全部' || item.category === this.selectedCategory), (news: NewsItem) => {ListItem() {// 调用我们刚才辛辛苦苦“造的轮子”buildNewsCard({ news: news })}}, (news: NewsItem) => news.id.toString()) // 用 id 作为唯一标识,提升性能}.width('100%').layoutWeight(1) // 占满剩余所有空间.padding({ left: 16, right: 16, top: 12, bottom: 12 })
}

笔记:
ForEach 里的 this.newsList.filter(...)!这就是“魔法”核心!

filter 方法会根据 this.selectedCategory 筛选出“应该”显示的新闻。

整个流程是:

  1. 你点击“科技”按钮。
  2. onClick@State 变量 selectedCategory 改成了 “科技”。
  3. ArkTS 框架发现 @State 变了,马上“通知”所有用到它的地方。
  4. buildNewsList 重新执行 build()
  5. ForEach 里的 filter 重新计算,这次只返回了 category === '科技' 的新闻。
  6. List 自动更新!

丝滑!这就是“数据驱动 UI”的魅力!


😱 “萌新”踩坑(含泪)实录

“常在河边走,哪能不湿鞋”。下面是我(含泪)总结的“踩坑”经验,各位“萌新”请拿好,可以少走几公里弯路!

巨坑 1:我改了“爹”的数据!(@Prop vs @State)

  • 现象: 我一开始在子组件 buildNewsCard 里,想点赞时直接改 this.news.isLiked… 结果,卒!要么不生效,要么“污染”了父组件。
  • 血泪教训: @Prop 是“爹”给的,是只读的! 你不能在“儿子”组件里直接改“爹”的数据!
  • 正解: 就像我们前面做的,aboutToAppear 里把 @Propnews 复制@StatelocalNews。组件内部只修改 localNews,实现“自给自足”。
// 正确做法:使用内部状态管理
@State localNews: NewsItem = new NewsItem(0, '', '', '', '', '', 0, false)aboutToAppear() {if (this.news) {this.localNews = new NewsItem(/* 复制数据 */);}
}

小坑 2:图片加载“翻车”

  • 现象: 网速不好时,图片加载失败,卡片上出现一个大“窟窿”,丑爆了。
  • 正解: Image 组件有两个好基友 .alt().onError()
Image(this.localNews.imageUrl).alt($r('app.media.avatar')) // 加载失败/加载中 显示的占位图.onError(() => {console.log(`图片加载失败: ${this.localNews.title}`);})

小坑 3:布局“偏心眼”

  • 现象: 底部信息栏,左边的日期和右边的点赞,挤在一起了,或者对不齐。
  • 正解: 善用 layoutWeight(1)!给那个你希望它“尽可能伸展”的组件(比如我们左边的信息栏 Column)加上它,它就会自动“抢”走所有剩余空间。
Column({ space: 2 }) {// 左侧信息内容
}
.layoutWeight(1) // 占据剩余空间

“精装修”建议(下一步玩啥)

这个 Demo 只是个“毛坯房”,想“精装修”?你可以试试:

  1. 搜索功能:在标题栏下面加个搜索框,用 filter 按“标题” (title.includes(searchText)) 搜索。
  2. 详情页面:点击卡片,跳转到完整的新闻详情页(Navigation 组件该出场了)。
  3. 下拉刷新:给 List 加上下拉刷新功能,更新 newsList 里的数据。
  4. 上拉加载:滚动到底部时,自动加载“下一页”数据(onReachEnd 事件)。

总结:你又“行”了!

呼——(擦汗)。这个项目“肝”下来,是不是感觉自己又“行”了?

image.png

我们从一个“空架子”开始,亲手“锻造”了数据模型,“封装”了高复用性的 @Component 卡片(还搞懂了 @Prop@State 的“父子关系”),用 Stack 玩转了“叠罗汉”布局,最后用 @Statefilter 联手实现了“响应式”筛选列表。

这已经是一个准专业 App 的雏形了!

从“国风” Demo 到这个新闻 App,你已经从“新手村”毕业,拿到了“高级组件”的徽章。继续保持这份热情,下一个项目,我们去挑战更酷的!

http://www.dtcms.com/a/618225.html

相关文章:

  • 解决:jenkins Exception java.lang.NoSuchFieldError: SNAKE_CASE
  • 如何实现Redis安装与使用的详细教程
  • tensorflow+yolo图片训练和图片识别系统
  • 唯品会 一家专门做特卖的网站现在前端开发用什么技术
  • 图神经网络分享系列-GraphSage(Inductive Representation Learning on Large Graphs) (一)
  • leetcode对称二叉树
  • 网站开发设计心得及体会河南建设工程造价管理协会网站
  • 深度学习实战:(2)用 TensorFlow 1.x 构建手语识别模型
  • 人工智能、机器学习、深度学习:技术革命的深度解析
  • 东营seo网站建设费用广告设计专业自我介绍
  • 【Linux】进程状态、进程优先级、进程切换和调度
  • 【Android】View 的工作原理
  • 行人跌倒智能检测系统:YOLOv8/V5/V6/V7 多模型 + PySide6 界面 深度学习 多场景适配 大数据 (建议收藏)✅
  • 山东网络推广图片福州seo网站管理
  • C#中Task的详细用法
  • 自己怎么做企业网站建设免费代理服务器ip地址
  • 前端 css selector 的层叠 优先级与继承
  • 基于python二手房数据分析系统 可视化 Scrapy 爬虫 链家二手房数据 Django框架 基于用户的协同过滤推荐 二手房推荐系统 (源码)✅
  • Rust 内部可变性的访问器模式
  • ThinkPHP8学习篇(十二):模型关联(二)
  • 药品行业做网站windows wordpress
  • 【读代码】LightRAG轻量级知识图谱增强检索系统的架构与实现
  • arm架构设备使用FISCO BCOS上搭建多机区块链网络
  • 【Android】LRU 与 Android 缓存策略
  • 使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 26--数据驱动--参数化处理 Excel 文件 3
  • 第41节:第三阶段总结:打造一个AR家具摆放应用
  • 建设网站流程2022年最新新闻播报稿件
  • 网站地图的作用长沙网站开发设计
  • 【读代码】最新端侧TTS模型NeuTTS-Air
  • 做装修网站多少钱四川成都住建局官网