[HarmonyOS NEXT 实战案例一:SideBarContainer] 侧边栏容器实战:新闻阅读应用侧边栏布局 进阶篇

2025-06-11 23:33:05
109次阅读
0个评论

[HarmonyOS NEXT 实战案例一:SideBarContainer] 侧边栏容器实战:新闻阅读应用侧边栏布局 进阶篇

项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

在基础篇中,我们学习了如何使用HarmonyOS NEXT的SideBarContainer组件创建新闻阅读应用的基本侧边栏布局。本篇教程将深入探讨如何为新闻阅读应用添加更多交互功能和状态管理,提升用户体验。

一、状态管理进阶

在实际应用中,状态管理是构建交互式UI的关键。对于新闻阅读应用,我们需要管理多种状态,如当前选中的分类、新闻列表数据、阅读历史等。

1.1 状态变量设计

首先,让我们扩展NewsApp组件的状态变量:

@Component
export struct NewsApp {
    // 侧边栏显示状态
    @State isSideBarShow: boolean = true
    // 当前选中的分类
    @State currentCategory: string = '热点'
    // 新闻数据模型
    @State newsItems: Array<NewsItem> = []
    // 阅读历史
    @State readHistory: Set<string> = new Set<string>()
    // 是否显示阅读历史
    @State showHistory: boolean = false
    
    // ...
}

1.2 数据模型定义

为了更好地组织新闻数据,我们定义一个NewsItem类:

class NewsItem {
    id: string
    title: string
    category: string
    publishTime: Date
    summary: string
    isRead: boolean
    
    constructor(id: string, title: string, category: string, publishTime: Date, summary: string) {
        this.id = id
        this.title = title
        this.category = category
        this.publishTime = publishTime
        this.summary = summary
        this.isRead = false
    }
}

1.3 模拟数据生成

在实际应用中,新闻数据通常来自网络请求。这里我们使用模拟数据:

// 生成模拟新闻数据
private generateMockNews(): Array<NewsItem> {
    const categories = ['热点', '科技', '财经', '体育', '娱乐', '国际']
    const mockNews: Array<NewsItem> = []
    
    // 为每个分类生成3条新闻
    categories.forEach((category, categoryIndex) => {
        for (let i = 1; i <= 3; i++) {
            const id = `news-${categoryIndex}-${i}`
            const title = `${category}新闻${i}: ${this.getRandomTitle(category)}`
            const publishTime = new Date(Date.now() - Math.random() * 86400000) // 随机时间,最近24小时内
            const summary = `这是一条关于${category}的新闻摘要,包含了重要信息和最新动态...`
            
            mockNews.push(new NewsItem(id, title, category, publishTime, summary))
        }
    })
    
    return mockNews
}

// 根据分类生成随机标题
private getRandomTitle(category: string): string {
    const titles = {
        '热点': ['重大事件持续发酵', '最新政策解读', '社会热点引关注'],
        '科技': ['华为发布HarmonyOS NEXT', '全球AI发展峰会召开', '量子计算取得突破'],
        '财经': ['股市创新高', '央行调整利率政策', '新兴产业投资机会'],
        '体育': ['世界杯最新战报', '奥运会筹备进展', '体坛明星专访'],
        '娱乐': ['电影节盛大开幕', '流行音乐排行榜', '明星慈善活动'],
        '国际': ['全球峰会召开', '国际关系新动向', '世界经济形势分析']
    }
    
    const categoryTitles = titles[category] || ['最新消息', '重要通知', '行业动态']
    return categoryTitles[Math.floor(Math.random() * categoryTitles.length)]
}

二、交互功能实现

2.1 分类切换功能

当用户点击侧边栏中的分类时,我们需要更新当前选中的分类,并筛选相应的新闻:

// 侧边栏内容 - 新闻分类
Column() {
    Text('新闻分类').fontSize(20).margin(10)
    List({ space: 10 }) {
        ForEach(['热点', '科技', '财经', '体育', '娱乐', '国际'], (item: string) => {
            ListItem() {
                Row() {
                    // 选中标记
                    if (this.currentCategory === item) {
                        Rect({ width: 4, height: 16 })
                            .fill('#0A59F7')
                            .margin({ right: 10 })
                    }
                    
                    Text(item)
                        .fontSize(16)
                        .fontColor(this.currentCategory === item ? '#0A59F7' : '#333')
                        .margin({ left: this.currentCategory === item ? 6 : 20, top: 10, bottom: 10 })
                }
            }
            .onClick(() => {
                this.currentCategory = item
                // 在小屏幕上自动隐藏侧边栏
                if (this.isSmallScreen()) {
                    this.isSideBarShow = false
                }
            })
        })
    }
    .width('100%')
    .layoutWeight(1)
    
    // 添加阅读历史切换按钮
    Button(this.showHistory ? '返回分类' : '阅读历史', { type: ButtonType.Capsule })
        .width('80%')
        .height(40)
        .margin({ bottom: 20 })
        .onClick(() => {
            this.showHistory = !this.showHistory
        })
}
.width('70%')
.backgroundColor('#f5f5f5')

2.2 新闻列表筛选与显示

根据当前选中的分类或是否显示阅读历史,筛选并显示相应的新闻:

// 主内容区 - 新闻列表
Column() {
    Row() {
        Image($r('app.media.01'))
            .width(30)
            .height(30)
            .onClick(() => {
                this.isSideBarShow = !this.isSideBarShow
            })
        Text(this.showHistory ? '阅读历史' : `${this.currentCategory}新闻`)
            .fontSize(20)
            .margin({ left: 10 })
    }
    .width('100%')
    .padding(10)

    List({ space: 10 }) {
        // 根据条件筛选新闻
        ForEach(this.getFilteredNews(), (item: NewsItem) => {
            ListItem() {
                Column() {
                    Row() {
                        Text(item.title)
                            .fontSize(16)
                            .fontColor(item.isRead ? '#999' : '#333')
                            .layoutWeight(1)
                        
                        Text(this.formatTime(item.publishTime))
                            .fontSize(12)
                            .fontColor('#999')
                    }
                    .width('100%')
                    .justifyContent(FlexAlign.SpaceBetween)
                    
                    Text(item.summary)
                        .fontSize(14)
                        .fontColor('#666')
                        .margin({ top: 5 })
                        .maxLines(2)
                        .textOverflow({ overflow: TextOverflow.Ellipsis })
                }
                .padding(10)
            }
            .borderRadius(8)
            .backgroundColor(Color.White)
            .shadow({ radius: 2, color: '#999', offsetX: 1, offsetY: 1 })
            .onClick(() => {
                // 标记为已读
                item.isRead = true
                this.readHistory.add(item.id)
                // 显示新闻详情(实际应用中可能跳转到详情页)
                this.showNewsDetail(item)
            })
        })
    }
    .width('100%')
    .layoutWeight(1)
}

2.3 辅助方法实现

// 获取筛选后的新闻列表
private getFilteredNews(): Array<NewsItem> {
    if (this.showHistory) {
        // 显示阅读历史
        return this.newsItems.filter(item => this.readHistory.has(item.id))
    } else {
        // 显示当前分类的新闻
        return this.newsItems.filter(item => item.category === this.currentCategory)
    }
}

// 格式化时间
private formatTime(date: Date): string {
    const now = new Date()
    const diff = now.getTime() - date.getTime()
    
    // 小于1小时显示分钟
    if (diff < 3600000) {
        const minutes = Math.floor(diff / 60000)
        return `${minutes}分钟前`
    }
    // 小于24小时显示小时
    else if (diff < 86400000) {
        const hours = Math.floor(diff / 3600000)
        return `${hours}小时前`
    }
    // 其他情况显示日期
    else {
        return `${date.getMonth() + 1}月${date.getDate()}日`
    }
}

// 判断是否为小屏幕
private isSmallScreen(): boolean {
    // 实际应用中可以使用媒体查询或获取屏幕尺寸
    return true
}

// 显示新闻详情(示例方法)
private showNewsDetail(item: NewsItem): void {
    console.log(`显示新闻详情: ${item.title}`)
    // 实际应用中可能跳转到详情页或显示弹窗
}

三、生命周期管理

在HarmonyOS NEXT中,组件的生命周期管理对于资源优化和用户体验至关重要。

3.1 组件生命周期函数

@Component
export struct NewsApp {
    // 状态变量...
    
    aboutToAppear() {
        // 组件即将出现时调用,可以进行初始化操作
        this.newsItems = this.generateMockNews()
        console.log('NewsApp组件即将出现,已生成模拟新闻数据')
    }
    
    aboutToDisappear() {
        // 组件即将消失时调用,可以进行资源释放操作
        console.log('NewsApp组件即将消失,释放资源')
    }
    
    // 其他方法...
}

3.2 页面状态保存与恢复

在实际应用中,我们可能需要保存用户的浏览状态,以便在应用重启后恢复:

// 保存用户状态到持久化存储
private saveUserState() {
    // 使用AppStorage或PersistentStorage保存状态
    AppStorage.SetOrCreate<string>('currentCategory', this.currentCategory)
    AppStorage.SetOrCreate<Array<string>>('readHistory', Array.from(this.readHistory))
}

// 从持久化存储恢复用户状态
private restoreUserState() {
    // 从AppStorage或PersistentStorage恢复状态
    const savedCategory = AppStorage.Get<string>('currentCategory')
    if (savedCategory) {
        this.currentCategory = savedCategory
    }
    
    const savedHistory = AppStorage.Get<Array<string>>('readHistory')
    if (savedHistory) {
        this.readHistory = new Set<string>(savedHistory)
    }
}

四、高级交互特性

4.1 下拉刷新

为新闻列表添加下拉刷新功能:

// 主内容区 - 新闻列表
Column() {
    // 顶部栏...
    
    Refresh({ refreshing: this.isRefreshing, offset: 120, friction: 100 }) {
        List({ space: 10 }) {
            // 新闻列表内容...
        }
        .width('100%')
        .layoutWeight(1)
    }
    .onRefreshing(() => {
        this.refreshNews()
    })
}

// 刷新新闻数据
private refreshNews() {
    this.isRefreshing = true
    
    // 模拟网络请求延迟
    setTimeout(() => {
        // 更新新闻数据
        this.newsItems = this.generateMockNews()
        this.isRefreshing = false
    }, 1500)
}

4.2 新闻搜索

添加搜索功能,允许用户搜索新闻:

// 顶部栏
Row() {
    Image($r('app.media.01'))
        .width(30)
        .height(30)
        .onClick(() => {
            this.isSideBarShow = !this.isSideBarShow
        })
    
    if (this.isSearchMode) {
        // 搜索模式
        TextInput({ placeholder: '搜索新闻...', text: this.searchText })
            .width('70%')
            .height(40)
            .margin({ left: 10 })
            .onChange((value: string) => {
                this.searchText = value
            })
        
        Button('取消')
            .height(40)
            .margin({ left: 10 })
            .onClick(() => {
                this.isSearchMode = false
                this.searchText = ''
            })
    } else {
        // 正常模式
        Text(this.showHistory ? '阅读历史' : `${this.currentCategory}新闻`)
            .fontSize(20)
            .margin({ left: 10 })
            .layoutWeight(1)
        
        Image($r('app.media.search'))
            .width(24)
            .height(24)
            .margin({ right: 10 })
            .onClick(() => {
                this.isSearchMode = true
            })
    }
}
.width('100%')
.padding(10)

4.3 手势交互

添加手势支持,允许用户通过滑动手势控制侧边栏:

// 主内容区添加手势识别
Column() {
    // 内容...
}
.gesture(
    GestureGroup(GestureMode.Parallel,
        SwipeGesture({ direction: SwipeDirection.Horizontal })
            .onAction((event: GestureEvent) => {
                if (event.direction === SwipeDirection.Right) {
                    // 向右滑动,显示侧边栏
                    this.isSideBarShow = true
                } else if (event.direction === SwipeDirection.Left) {
                    // 向左滑动,隐藏侧边栏
                    this.isSideBarShow = false
                }
            })
    )
)

五、实战案例:添加收藏功能

让我们为新闻应用添加收藏功能,允许用户收藏感兴趣的新闻。

5.1 状态变量扩展

@Component
export struct NewsApp {
    // 其他状态变量...
    
    // 收藏的新闻ID集合
    @State favorites: Set<string> = new Set<string>()
    // 是否显示收藏列表
    @State showFavorites: boolean = false
    
    // ...
}

5.2 收藏按钮实现

在新闻列表项中添加收藏按钮:

ListItem() {
    Column() {
        Row() {
            Text(item.title)
                .fontSize(16)
                .fontColor(item.isRead ? '#999' : '#333')
                .layoutWeight(1)
            
            // 收藏按钮
            Image(this.favorites.has(item.id) ? $r('app.media.star_filled') : $r('app.media.star_outline'))
                .width(24)
                .height(24)
                .margin({ left: 10 })
                .onClick((event: ClickEvent) => {
                    event.stopPropagation() // 阻止事件冒泡
                    this.toggleFavorite(item.id)
                })
            
            Text(this.formatTime(item.publishTime))
                .fontSize(12)
                .fontColor('#999')
                .margin({ left: 10 })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        
        // 新闻摘要...
    }
    .padding(10)
}

5.3 收藏功能实现

// 切换收藏状态
private toggleFavorite(id: string): void {
    if (this.favorites.has(id)) {
        this.favorites.delete(id)
    } else {
        this.favorites.add(id)
    }
    
    // 强制更新Set(因为Set的变化可能不会触发UI更新)
    this.favorites = new Set(this.favorites)
    
    // 保存收藏状态
    this.saveFavorites()
}

// 保存收藏状态到持久化存储
private saveFavorites(): void {
    AppStorage.SetOrCreate<Array<string>>('favorites', Array.from(this.favorites))
}

// 从持久化存储恢复收藏状态
private restoreFavorites(): void {
    const savedFavorites = AppStorage.Get<Array<string>>('favorites')
    if (savedFavorites) {
        this.favorites = new Set<string>(savedFavorites)
    }
}

5.4 收藏列表切换

在侧边栏中添加收藏列表切换按钮:

// 侧边栏底部添加按钮组
Row() {
    Button('分类', { type: ButtonType.Capsule })
        .width('45%')
        .height(40)
        .backgroundColor(this.showHistory || this.showFavorites ? '#ccc' : '#0A59F7')
        .onClick(() => {
            this.showHistory = false
            this.showFavorites = false
        })
    
    Button('收藏', { type: ButtonType.Capsule })
        .width('45%')
        .height(40)
        .backgroundColor(this.showFavorites ? '#0A59F7' : '#ccc')
        .onClick(() => {
            this.showHistory = false
            this.showFavorites = true
        })
}
.width('90%')
.margin({ bottom: 10 })
.justifyContent(FlexAlign.SpaceBetween)

Button('阅读历史', { type: ButtonType.Capsule })
    .width('90%')
    .height(40)
    .backgroundColor(this.showHistory ? '#0A59F7' : '#ccc')
    .margin({ bottom: 20 })
    .onClick(() => {
        this.showHistory = true
        this.showFavorites = false
    })

5.5 筛选逻辑更新

更新筛选逻辑以支持收藏列表:

// 获取筛选后的新闻列表
private getFilteredNews(): Array<NewsItem> {
    if (this.showHistory) {
        // 显示阅读历史
        return this.newsItems.filter(item => this.readHistory.has(item.id))
    } else if (this.showFavorites) {
        // 显示收藏列表
        return this.newsItems.filter(item => this.favorites.has(item.id))
    } else {
        // 显示当前分类的新闻
        return this.newsItems.filter(item => item.category === this.currentCategory)
    }
}

六、总结

本教程深入探讨了如何为新闻阅读应用添加交互功能和状态管理, 通过这些功能的实现,我们的新闻阅读应用变得更加交互式和用户友好。SideBarContainer组件与状态管理的结合,为用户提供了流畅的导航体验和丰富的功能。 在实际开发中,可以根据具体需求进一步扩展这些功能,例如添加用户登录、云同步、推送通知等。HarmonyOS NEXT提供的丰富组件和状态管理机制,为开发高质量的应用提供了强大支持。

收藏00

登录 后评论。没有帐号? 注册 一个。

全栈若城

  • 0回答
  • 4粉丝
  • 0关注
相关话题