[HarmonyOS NEXT 实战案例二] 新闻资讯网格列表(下)
2025-06-06 22:42:33
104次阅读
0个评论
[HarmonyOS NEXT 实战案例二] 新闻资讯网格列表(下)
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 引言
在上一篇教程中,我们介绍了如何使用HarmonyOS NEXT的GridRow和GridCol组件实现基本的新闻资讯列表布局。本篇教程将深入探讨如何优化新闻列表布局,并添加更多交互功能,提升用户体验。
2. 新闻列表布局优化
2.1 分类标签栏
在新闻应用中,通常会有不同的新闻分类,如头条、科技、体育等。我们可以在列表顶部添加一个分类标签栏:
@State currentCategory: string = '头条'
private categories: string[] = ['头条', '科技', '体育', '财经', '娱乐']
build() {
Column() {
// 分类标签栏
Scroll({ direction: ScrollDirection.Horizontal }) {
Row({ space: 16 }) {
ForEach(this.categories, (category: string) => {
Text(category)
.fontSize(16)
.fontColor(this.currentCategory === category ? '#FF5722' : '#333333')
.fontWeight(this.currentCategory === category ? FontWeight.Bold : FontWeight.Normal)
.onClick(() => {
this.currentCategory = category
// 加载对应分类的新闻
this.loadNewsByCategory(category)
})
})
}
.padding({ left: 12, right: 12, top: 12, bottom: 12 })
}
.scrollBar(BarState.Off)
// 新闻列表
GridRow({ columns: 1 }) {
// 新闻列表内容
}
}
}
2.2 新闻项布局变体
为了增加视觉多样性,我们可以实现多种新闻项布局变体:
2.2.1 左图右文布局(默认)
Row() {
Image(item.image)
.width(100)
.height(80)
.objectFit(ImageFit.Cover)
.borderRadius(4)
Column() {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 12 })
Text(item.summary)
.fontSize(14)
.margin({ left: 12, top: 4 })
}
.alignItems(HorizontalAlign.Start)
}
2.2.2 上图下文布局
Column() {
Image(item.image)
.width('100%')
.height(160)
.objectFit(ImageFit.Cover)
.borderRadius(4)
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ top: 8 })
.width('100%')
.textAlign(TextAlign.Start)
Text(item.summary)
.fontSize(14)
.margin({ top: 4 })
.width('100%')
.textAlign(TextAlign.Start)
}
2.2.3 三图布局
Column() {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
Row({ space: 8 }) {
ForEach(item.images, (image: ResourceStr) => {
Image(image)
.width('0%')
.aspectRatio(1)
.objectFit(ImageFit.Cover)
.borderRadius(4)
.layoutWeight(1)
})
}
.margin({ top: 8 })
Text(item.summary)
.fontSize(14)
.margin({ top: 8 })
.width('100%')
.textAlign(TextAlign.Start)
}
2.3 根据内容类型选择布局
我们可以根据新闻内容类型自动选择合适的布局:
interface NewsItemType {
title: string;
summary: string;
image: ResourceStr;
images?: ResourceStr[];
type: 'normal' | 'large' | 'multi';
}
build() {
Column() {
GridRow({ columns: 1 }) {
ForEach(this.newsItems, (item: NewsItemType) => {
GridCol({ span: 1 }) {
if (item.type === 'normal') {
// 左图右文布局
} else if (item.type === 'large') {
// 上图下文布局
} else if (item.type === 'multi') {
// 三图布局
}
}
})
}
}
}
3. 交互功能实现
3.1 下拉刷新
使用Refresh组件实现下拉刷新功能:
@State refreshing: boolean = false
build() {
Refresh({ refreshing: this.refreshing, offset: 120, friction: 66 }) {
Column() {
// 分类标签栏和新闻列表
}
}
.onRefreshing(() => {
// 刷新数据
this.refreshData()
})
}
private refreshData() {
this.refreshing = true
// 模拟网络请求
setTimeout(() => {
// 更新新闻数据
this.refreshing = false
}, 1000)
}
3.2 加载更多
在列表底部添加加载更多功能:
@State loading: boolean = false
@State hasMore: boolean = true
build() {
Column() {
GridRow({ columns: 1 }) {
// 新闻列表内容
}
if (this.loading) {
Row() {
LoadingProgress()
.width(24)
.height(24)
.color('#999999')
Text('加载中...')
.fontSize(14)
.fontColor('#999999')
.margin({ left: 8 })
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(16)
} else if (this.hasMore) {
Text('点击加载更多')
.fontSize(14)
.fontColor('#666666')
.width('100%')
.textAlign(TextAlign.Center)
.padding(16)
.onClick(() => {
this.loadMore()
})
} else {
Text('没有更多内容了')
.fontSize(14)
.fontColor('#999999')
.width('100%')
.textAlign(TextAlign.Center)
.padding(16)
}
}
}
private loadMore() {
this.loading = true
// 模拟网络请求
setTimeout(() => {
// 加载更多新闻
this.loading = false
// 判断是否还有更多
this.hasMore = this.newsItems.length < 20
}, 1000)
}
3.3 新闻项点击事件
为新闻项添加点击事件,实现跳转到新闻详情页的功能:
Row() {
// 新闻项内容
}
.onClick(() => {
// 跳转到新闻详情页
console.info(`点击新闻: ${item.title}`)
})
3.4 新闻收藏功能
添加新闻收藏功能:
interface NewsItemType {
title: string;
summary: string;
image: ResourceStr;
type: 'normal' | 'large' | 'multi';
isCollected: boolean;
}
Row() {
// 新闻内容
Image(item.isCollected ? $r('app.media.star_filled') : $r('app.media.star_outline'))
.width(24)
.height(24)
.margin({ left: 8 })
.onClick((event: ClickEvent) => {
// 阻止事件冒泡
event.stopPropagation()
// 切换收藏状态
this.toggleCollect(item)
})
}
private toggleCollect(item: NewsItemType) {
const index = this.newsItems.indexOf(item)
if (index !== -1) {
this.newsItems[index].isCollected = !this.newsItems[index].isCollected
}
}
4. 数据状态管理
4.1 加载状态
使用状态变量管理数据加载状态:
@State dataState: 'loading' | 'success' | 'empty' | 'error' = 'loading'
build() {
Column() {
if (this.dataState === 'loading') {
// 显示加载中状态
LoadingProgress()
.width(32)
.height(32)
.color('#FF5722')
} else if (this.dataState === 'empty') {
// 显示空状态
Column() {
Image($r('app.media.empty'))
.width(120)
.height(120)
Text('暂无新闻')
.fontSize(16)
.fontColor('#999999')
.margin({ top: 16 })
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(64)
} else if (this.dataState === 'error') {
// 显示错误状态
Column() {
Image($r('app.media.error'))
.width(120)
.height(120)
Text('加载失败,请重试')
.fontSize(16)
.fontColor('#999999')
.margin({ top: 16 })
Button('重新加载')
.margin({ top: 16 })
.onClick(() => {
this.loadData()
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(64)
} else {
// 显示新闻列表
GridRow({ columns: 1 }) {
// 新闻列表内容
}
}
}
}
4.2 数据过滤
实现新闻数据的过滤功能:
@State searchText: string = ''
build() {
Column() {
// 搜索框
TextInput({ placeholder: '搜索新闻' })
.width('100%')
.height(40)
.backgroundColor('#F5F5F5')
.borderRadius(20)
.padding({ left: 16, right: 16 })
.onChange((value: string) => {
this.searchText = value
})
// 新闻列表
GridRow({ columns: 1 }) {
ForEach(this.getFilteredNews(), (item: NewsItemType) => {
// 新闻项
})
}
}
}
private getFilteredNews(): NewsItemType[] {
if (!this.searchText) {
return this.newsItems
}
return this.newsItems.filter(item =>
item.title.includes(this.searchText) ||
item.summary.includes(this.searchText)
)
}
5. 完整代码示例
以下是优化后的新闻资讯列表完整代码示例:
@Component
export struct EnhancedNewsGrid {
@State refreshing: boolean = false
@State loading: boolean = false
@State hasMore: boolean = true
@State dataState: 'loading' | 'success' | 'empty' | 'error' = 'success'
@State currentCategory: string = '头条'
@State searchText: string = ''
private categories: string[] = ['头条', '科技', '体育', '财经', '娱乐']
@State newsItems: NewsItemType[] = [
{
title: 'HarmonyOS 4.0发布',
summary: '全新分布式能力升级',
image: $r("app.media.big1"),
type: 'normal',
isCollected: false
},
{
title: '华为开发者大会',
summary: '2023年HDC即将召开',
image: $r("app.media.big31"),
type: 'large',
isCollected: true
},
{
title: '智能家居新趋势',
summary: '全屋智能解决方案',
image: $r("app.media.big30"),
images: [$r("app.media.big30"), $r("app.media.big29"), $r("app.media.big28")],
type: 'multi',
isCollected: false
},
{
title: '移动办公新时代',
summary: '超级终端带来高效体验',
image: $r("app.media.big27"),
type: 'normal',
isCollected: false
}
]
private refreshData() {
this.refreshing = true
// 模拟网络请求
setTimeout(() => {
this.refreshing = false
}, 1000)
}
private loadMore() {
this.loading = true
// 模拟网络请求
setTimeout(() => {
this.loading = false
this.hasMore = this.newsItems.length < 20
}, 1000)
}
private loadNewsByCategory(category: string) {
this.dataState = 'loading'
// 模拟网络请求
setTimeout(() => {
this.dataState = 'success'
}, 500)
}
private toggleCollect(item: NewsItemType) {
const index = this.newsItems.indexOf(item)
if (index !== -1) {
this.newsItems[index].isCollected = !this.newsItems[index].isCollected
}
}
private getFilteredNews(): NewsItemType[] {
if (!this.searchText) {
return this.newsItems
}
return this.newsItems.filter(item =>
item.title.includes(this.searchText) ||
item.summary.includes(this.searchText)
)
}
build() {
Refresh({ refreshing: this.refreshing, offset: 120, friction: 66 }) {
Column() {
// 搜索框
TextInput({ placeholder: '搜索新闻' })
.width('100%')
.height(40)
.backgroundColor('#F5F5F5')
.borderRadius(20)
.padding({ left: 16, right: 16 })
.margin({ bottom: 12 })
.onChange((value: string) => {
this.searchText = value
})
// 分类标签栏
Scroll({ direction: ScrollDirection.Horizontal }) {
Row({ space: 16 }) {
ForEach(this.categories, (category: string) => {
Text(category)
.fontSize(16)
.fontColor(this.currentCategory === category ? '#FF5722' : '#333333')
.fontWeight(this.currentCategory === category ? FontWeight.Bold : FontWeight.Normal)
.onClick(() => {
this.currentCategory = category
this.loadNewsByCategory(category)
})
})
}
.padding({ left: 12, right: 12, top: 12, bottom: 12 })
}
.scrollBar(BarState.Off)
.margin({ bottom: 12 })
if (this.dataState === 'loading') {
// 显示加载中状态
LoadingProgress()
.width(32)
.height(32)
.color('#FF5722')
.margin(64)
} else if (this.dataState === 'empty') {
// 显示空状态
Column() {
Image($r('app.media.empty'))
.width(120)
.height(120)
Text('暂无新闻')
.fontSize(16)
.fontColor('#999999')
.margin({ top: 16 })
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(64)
} else if (this.dataState === 'error') {
// 显示错误状态
Column() {
Image($r('app.media.error'))
.width(120)
.height(120)
Text('加载失败,请重试')
.fontSize(16)
.fontColor('#999999')
.margin({ top: 16 })
Button('重新加载')
.margin({ top: 16 })
.onClick(() => {
this.loadNewsByCategory(this.currentCategory)
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(64)
} else {
// 显示新闻列表
GridRow({ columns: 1 }) {
ForEach(this.getFilteredNews(), (item: NewsItemType) => {
GridCol({ span: 1 }) {
if (item.type === 'normal') {
// 左图右文布局
Row() {
Image(item.image)
.width(100)
.height(80)
.objectFit(ImageFit.Cover)
.borderRadius(4)
Column() {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 12 })
Text(item.summary)
.fontSize(14)
.margin({ left: 12, top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Image(item.isCollected ? $r('app.media.star_filled') : $r('app.media.star_outline'))
.width(24)
.height(24)
.onClick((event: ClickEvent) => {
event.stopPropagation()
this.toggleCollect(item)
})
}
.padding(12)
.borderRadius(8)
.margin({ bottom: 8 })
.backgroundColor('#FFFFFF')
.onClick(() => {
console.info(`点击新闻: ${item.title}`)
})
} else if (item.type === 'large') {
// 上图下文布局
Column() {
Image(item.image)
.width('100%')
.height(160)
.objectFit(ImageFit.Cover)
.borderRadius(4)
Row() {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ top: 8 })
.layoutWeight(1)
Image(item.isCollected ? $r('app.media.star_filled') : $r('app.media.star_outline'))
.width(24)
.height(24)
.margin({ top: 8 })
.onClick((event: ClickEvent) => {
event.stopPropagation()
this.toggleCollect(item)
})
}
Text(item.summary)
.fontSize(14)
.margin({ top: 4 })
.width('100%')
.textAlign(TextAlign.Start)
}
.padding(12)
.borderRadius(8)
.margin({ bottom: 8 })
.backgroundColor('#FFFFFF')
.onClick(() => {
console.info(`点击新闻: ${item.title}`)
})
} else if (item.type === 'multi') {
// 三图布局
Column() {
Row() {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Image(item.isCollected ? $r('app.media.star_filled') : $r('app.media.star_outline'))
.width(24)
.height(24)
.onClick((event: ClickEvent) => {
event.stopPropagation()
this.toggleCollect(item)
})
}
Row({ space: 8 }) {
ForEach(item.images, (image: ResourceStr) => {
Image(image)
.width('0%')
.aspectRatio(1)
.objectFit(ImageFit.Cover)
.borderRadius(4)
.layoutWeight(1)
})
}
.margin({ top: 8 })
Text(item.summary)
.fontSize(14)
.margin({ top: 8 })
.width('100%')
.textAlign(TextAlign.Start)
}
.padding(12)
.borderRadius(8)
.margin({ bottom: 8 })
.backgroundColor('#FFFFFF')
.onClick(() => {
console.info(`点击新闻: ${item.title}`)
})
}
}
})
}
// 加载更多
if (this.loading) {
Row() {
LoadingProgress()
.width(24)
.height(24)
.color('#999999')
Text('加载中...')
.fontSize(14)
.fontColor('#999999')
.margin({ left: 8 })
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(16)
} else if (this.hasMore) {
Text('点击加载更多')
.fontSize(14)
.fontColor('#666666')
.width('100%')
.textAlign(TextAlign.Center)
.padding(16)
.onClick(() => {
this.loadMore()
})
} else {
Text('没有更多内容了')
.fontSize(14)
.fontColor('#999999')
.width('100%')
.textAlign(TextAlign.Center)
.padding(16)
}
}
}
.width('100%')
.padding(12)
}
.onRefreshing(() => {
this.refreshData()
})
}
}
interface NewsItemType {
title: string;
summary: string;
image: ResourceStr;
images?: ResourceStr[];
type: 'normal' | 'large' | 'multi';
isCollected: boolean;
}
6. 总结
本教程深入探讨了如何优化HarmonyOS NEXT的新闻资讯列表布局,并添加了丰富的交互功能。通过实现分类标签栏、多种布局变体、下拉刷新、加载更多、新闻收藏等功能,我们打造了一个功能完善、用户体验良好的新闻列表页面。这些技术可以应用于实际的新闻、资讯类应用开发中,提升应用的用户体验和视觉效果。
00
- 0回答
- 3粉丝
- 0关注
相关话题
- [HarmonyOS NEXT 实战案例二] 新闻资讯网格列表(上)
- [HarmonyOS NEXT 实战案例三] 音乐专辑网格展示(下)
- [HarmonyOS NEXT 实战案例四] 天气应用网格布局(下)
- [HarmonyOS NEXT 实战案例六] 餐饮菜单网格布局(下)
- [HarmonyOS NEXT 实战案例七] 健身课程网格布局(下)
- [HarmonyOS NEXT 实战案例九] 旅游景点网格布局(下)
- [HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(下)
- [HarmonyOS NEXT 实战案例一] 电商首页商品网格布局(下)
- [HarmonyOS NEXT 实战案例八] 电影票务网格布局(下)
- [HarmonyOS NEXT 实战案例三] 音乐专辑网格展示(上)
- [HarmonyOS NEXT 实战案例四] 天气应用网格布局(上)
- [HarmonyOS NEXT 实战案例六] 餐饮菜单网格布局(上)
- [HarmonyOS NEXT 实战案例七] 健身课程网格布局(上)
- [HarmonyOS NEXT 实战案例九] 旅游景点网格布局(上)
- HarmonyOS NEXT 实战之元服务:静态案例效果(二)