HarmonyOS6半年磨一剑 - RcIcon组件使用最佳实践与性能优化

HarmonyOS6半年磨一剑 - RcIcon组件使用最佳实践与性能优化

文章目录

前言

各位开发者,大家好!我是若城。

在鸿蒙应用开发过程中,我发现许多组件样式和工具方法具有高度的复用性,但每次新项目都需要重复编写,这极大地降低了开发效率。因此,我决定投入半年时间,打造一款专为鸿蒙生态设计的 UI 组件库 —— rchoui

项目简介

rchoui 是一个面向 HarmonyOS6 的企业级 UI 组件库,旨在提供开箱即用的高质量组件,让开发者告别"重复造轮子"。

核心特性

  • 丰富组件:涵盖基础组件、表单组件、弹窗组件、布局组件等
  • 设计规范:遵循统一的色彩体系和设计语言
  • 工具集成:内置常用工具方法,提升开发效率
  • 完善文档:每个模块都配有详细的设计思路和使用说明

开源计划

项目预计于 2026 年 7 月中旬正式开源,届时可通过三方库直接下载使用。在此期间,我会通过系列文章逐一介绍每个模块的设计思路与实现细节。

rchoui官网

目前暂定 rchoui 官网地址:http://rchoui.ruocheng.site/

需要注意的是 当前官网还在完善当中, 会在后续更新中逐步完善。届时可以为大家提供更加完善的说明文档

一、实战应用案例

1.1 底部导航栏

import{ RcIcon, IconName }from"rchoui"/** * Tab 项配置接口 */interfaceTabItem{/** Tab 名称 */ name:string/** 激活状态图标 */ icon:string/** 未激活状态图标(线型) */ iconOutline:string}@Entry@Component struct RcIconTab {@State activeIndex:number=0private tabItems: TabItem[]=[{ name:'首页', icon: IconName.HOME, iconOutline: IconName.HOME_OUTLINE},{ name:'发现', icon: IconName.COMPASS, iconOutline: IconName.COMPASS_OUTLINE},{ name:'消息', icon: IconName.MESSAGE_CIRCLE, iconOutline: IconName.MESSAGE_CIRCLE_OUTLINE},{ name:'我的', icon: IconName.PERSON, iconOutline: IconName.PERSON_OUTLINE}]build(){Row(){ForEach(this.tabItems,(item:TabItem, index)=>{Column({ space:4}){RcIcon({ name:this.activeIndex === index ? item.icon : item.iconOutline, iconSize:24, color:this.activeIndex === index ?'#1890ff':'#666666',onIconClick:()=>{this.activeIndex = index }})Text(item.name).fontSize(12).fontColor(this.activeIndex === index ?'#1890ff':'#666666')}.layoutWeight(1).padding(8)})}.width('100%').height(56).backgroundColor('#ffffff')}}

实现要点:

  • 使用风格切换表达激活状态
  • 颜色与风格同步变化
  • 统一的视觉反馈

1.2 工具栏按钮组

interfaceToolsItem{ id:string name:string}@Component struct ToolbarExample {@State selectedTool:string=''private tools:ToolsItem[]=[{ id:'edit', name: IconName.EDIT},{ id:'copy', name: IconName.COPY},{ id:'trash', name: IconName.TRASH},{ id:'share', name: IconName.SHARE}]build(){Row({ space:16}){ForEach(this.tools,(tool:ToolsItem)=>{RcIcon({ name: tool.name, iconSize:20, color:this.selectedTool === tool.id ?'#1890ff':'#666666',onIconClick:()=>{this.selectedTool = tool.id this.handleToolClick(tool.id)}})})}.padding(12).backgroundColor('#f5f5f5').borderRadius(8)}handleToolClick(toolId:string){console.log(`工具 ${toolId} 被点击`)}}

1.3 列表项装饰

interfaceListItem{ id:string title:string icon:string status:'success'|'warning'|'error'|'info'}@Component struct ListExample {private items: ListItem[]=[{ id:'1', title:'任务已完成', icon: IconName.CHECKMARK_CIRCLE, status:'success'},{ id:'2', title:'待处理事项', icon: IconName.ALERT_CIRCLE, status:'warning'},{ id:'3', title:'操作失败', icon: IconName.CLOSE_CIRCLE, status:'error'},{ id:'4', title:'通知消息', icon: IconName.INFO, status:'info'}]build(){List({ space:12}){ForEach(this.items,(item: ListItem)=>{ListItem(){Row({ space:12}){RcIcon({ name: item.icon, iconSize:20, color:this.getStatusColor(item.status)})Text(item.title).fontSize(16).layoutWeight(1)RcIcon({ name: IconName.CHEVRON_RIGHT, iconSize:16, color:'#cccccc'})}.width('100%').padding(16).backgroundColor('#ffffff').borderRadius(8)}})}.padding(16)}getStatusColor(status:string): ResourceColor {const colorMap ={'success':'#52c41a','warning':'#faad14','error':'#ff4d4f','info':'#1890ff'}return colorMap[status]||'#666666'}}

1.4 输入框前后缀

@Component struct InputExample {@State searchText:string=''@State showClear:boolean=falsebuild(){Row(){// 前缀符号RcIcon({ name: IconName.SEARCH, iconSize:18, color:'#999999'}).margin({ left:12})// 输入框TextInput({ placeholder:'搜索内容', text:this.searchText }).layoutWeight(1).backgroundColor(Color.Transparent).padding({ left:8, right:8}).onChange((value:string)=>{this.searchText = value this.showClear = value.length >0})// 后缀清除按钮if(this.showClear){RcIcon({ name: IconName.CLOSE_CIRCLE, iconSize:16, color:'#cccccc',onIconClick:()=>{this.searchText =''this.showClear =false}}).margin({ right:12})}}.width('100%').height(40).backgroundColor('#f5f5f5').borderRadius(20)}}

1.5 加载状态指示

@Component struct LoadingExample {@State isLoading:boolean=false@State rotationAngle:number=0build(){Column({ space:16}){// 旋转加载动画if(this.isLoading){RcIcon({ name: IconName.LOADER_OUTLINE, iconSize:32, color:'#1890ff', iconAnimation:{ duration:1000, curve: Curve.Linear, iterations:-1}}).rotate({ angle:this.rotationAngle }).onAppear(()=>{this.rotationAngle =360})Text('加载中...').fontSize(14).fontColor('#666666')}else{RcIcon({ name: IconName.CHECKMARK_CIRCLE, iconSize:32, color:'#52c41a'})Text('加载完成').fontSize(14).fontColor('#52c41a')}Button(this.isLoading ?'停止':'开始加载').onClick(()=>{this.isLoading =!this.isLoading if(!this.isLoading){this.rotationAngle =0}})}}}

1.6 空状态页面

@Component struct EmptyStateExample {@Prop emptyType:'noData'|'noNetwork'|'error'='noData'build(){Column({ space:20}){RcIcon({ name:this.getEmptyIcon(), iconSize:80, color:'#cccccc'})Text(this.getEmptyText()).fontSize(16).fontColor('#666666')if(this.emptyType ==='error'){Button('重新加载').onClick(()=>{// 重新加载逻辑})}}.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor('#f5f5f5')}getEmptyIcon():string{const iconMap ={'noData': IconName.FILE_OUTLINE,'noNetwork': IconName.WIFI_OFF_OUTLINE,'error': IconName.ALERT_TRIANGLE_OUTLINE}return iconMap[this.emptyType]}getEmptyText():string{const textMap ={'noData':'暂无数据','noNetwork':'网络连接失败','error':'加载失败'}return textMap[this.emptyType]}}

1.7 评分组件

@Component struct RatingExample {@State rating:number=0private maxRating:number=5build(){Row({ space:8}){ForEach(Array.from({ length:this.maxRating }),(_, index)=>{RcIcon({ name: index <this.rating ? IconName.STAR: IconName.STAR_OUTLINE, iconSize:24, color: index <this.rating ?'#faad14':'#d9d9d9',onIconClick:()=>{this.rating = index +1}})})}}}

1.8 标签页组件

@Component struct TabsExample {@State activeTab:number=0private tabs =[{ title:'推荐', icon: IconName.HOME},{ title:'关注', icon: IconName.HEART},{ title:'热门', icon: IconName.FLASH}]build(){Column(){// 标签页头部Row(){ForEach(this.tabs,(tab, index)=>{Column({ space:4}){RcIcon({ name: tab.icon, iconSize:20, color:this.activeTab === index ?'#1890ff':'#666666'})Text(tab.title).fontSize(14).fontColor(this.activeTab === index ?'#1890ff':'#666666')}.layoutWeight(1).padding(12).onClick(()=>{this.activeTab = index })})}.width('100%').backgroundColor('#ffffff')// 标签页内容区if(this.activeTab ===0){Text('推荐内容')}elseif(this.activeTab ===1){Text('关注内容')}else{Text('热门内容')}}}}

二、性能优化实践

2.1 避免过度渲染

❌ 不推荐:每次都重新创建

@Component struct BadExample {@State count:number=0build(){Column(){// 问题:每次count变化,所有符号都重新渲染RcIcon({ name: IconName.HOME, iconSize:20})RcIcon({ name: IconName.SEARCH, iconSize:20})RcIcon({ name: IconName.PERSON, iconSize:20})Text(this.count.toString())}}}

✅ 推荐:使用@Reusable优化

@Reusable@Component struct IconItem {@Param iconName:string=''build(){RcIcon({ name:this.iconName, iconSize:20})}}@Component struct GoodExample {@State count:number=0build(){Column(){IconItem({ iconName: IconName.HOME})IconItem({ iconName: IconName.SEARCH})IconItem({ iconName: IconName.PERSON})Text(this.count.toString())}}}

2.2 列表性能优化

使用LazyForEach优化大列表

classBasicDataSourceimplementsIDataSource{private listeners: DataChangeListener[]=[]private data:string[]=[]getData(index:number):string{returnthis.data[index]}totalCount():number{returnthis.data.length }registerDataChangeListener(listener: DataChangeListener):void{this.listeners.push(listener)}unregisterDataChangeListener(listener: DataChangeListener):void{const index =this.listeners.indexOf(listener)if(index >=0){this.listeners.splice(index,1)}}}@Component struct OptimizedListExample {private dataSource: BasicDataSource =newBasicDataSource()build(){List(){LazyForEach(this.dataSource,(item:string, index:number)=>{ListItem(){Row({ space:12}){RcIcon({ name: IconName.FILE, iconSize:20, color:'#666666'})Text(item).fontSize(16)}.padding(16)}},(item:string)=> item)}}}

2.3 动画性能优化

控制动画数量

@Component struct AnimationExample {@State visibleIcons:number=5private maxIcons:number=100build(){Column(){// 只为可见的符号添加动画ForEach(Array.from({ length: Math.min(this.visibleIcons,this.maxIcons)}),(_, index)=>{RcIcon({ name: IconName.STAR, iconSize:20, iconAnimation:{ duration:300, delay: index *50// 错开动画时间}})})}}}

使用动画开关

@StorageLink('enableAnimations') enableAnimations:boolean=trueRcIcon({ name: IconName.LOADER_OUTLINE, iconSize:24, iconAnimation:this.enableAnimations ?{ duration:1000, iterations:-1}:undefined})

2.4 资源加载优化

延迟加载非关键符号

@Component struct LazyLoadExample {@State showDetails:boolean=falsebuild(){Column({ space:12}){// 始终显示的关键符号RcIcon({ name: IconName.HOME, iconSize:24})Button('显示详情').onClick(()=>{this.showDetails =true})// 延迟加载的符号if(this.showDetails){Row({ space:8}){RcIcon({ name: IconName.EDIT, iconSize:20})RcIcon({ name: IconName.TRASH, iconSize:20})RcIcon({ name: IconName.SHARE, iconSize:20})}}}}}

2.5 内存优化

及时清理资源

@Component struct MemoryOptimizedExample {@State rotationAngle:number=0private animationTimer?:numberaboutToAppear(){this.startAnimation()}aboutToDisappear(){// 清理定时器if(this.animationTimer){clearInterval(this.animationTimer)this.animationTimer =undefined}}startAnimation(){this.animationTimer =setInterval(()=>{this.rotationAngle =(this.rotationAngle +10)%360},50)}build(){RcIcon({ name: IconName.SYNC, iconSize:30}).rotate({ angle:this.rotationAngle })}}

三、可访问性支持

3.1 语义化标签

RcIcon({ name: IconName.CLOSE}).accessibilityText('关闭按钮').accessibilityDescription('点击关闭当前窗口')

3.2 焦点管理

@State isFocused:boolean=falseRcIcon({ name: IconName.BUTTON, color:this.isFocused ?'#0066cc':'#0080ff'}).focusable(true).onFocus(()=>{this.isFocused =true}).onBlur(()=>{this.isFocused =false})

3.3 触摸区域扩展

// 符号本身较小时,扩大触摸区域Stack(){RcIcon({ name: IconName.SMALL, iconSize:16})}.width(44)// 最小44x44的触摸区域.height(44).onClick(()=>{// 点击处理})

四、跨平台适配

4.1 屏幕密度适配

@State screenDensity:number= display.getDefaultDisplaySync().densityDPI /160RcIcon({ name: IconName.LOGO, iconSize:24*this.screenDensity // 根据密度缩放})

4.2 暗黑模式适配

@StorageLink('colorMode') colorMode: ThemeColorMode = ThemeColorMode.LIGHTRcIcon({ name: IconName.THEME, color:this.colorMode === ThemeColorMode.DARK?'#ffffff':'#000000'})

4.3 多语言支持

// 对于方向性符号,考虑RTL布局@StorageLink('isRTL') isRTL:boolean=falseRcIcon({ name:this.isRTL ? IconName.ARROW_LEFT: IconName.ARROW_RIGHT, iconSize:20})

五、调试技巧

5.1 符号渲染检查

// 开发模式下显示符号名称@State showDebugInfo:boolean= BuildProfile.DEBUGRcIcon({ name: IconName.TEST}).border({ width:this.showDebugInfo ?1:0, color:'#ff0000'})if(this.showDebugInfo){Text('IconName.TEST').fontSize(10).fontColor('#ff0000')}

5.2 性能监控

@Component struct PerformanceMonitor {@State renderCount:number=0aboutToAppear(){this.renderCount++console.log(`组件渲染次数: ${this.renderCount}`)}build(){RcIcon({ name: IconName.MONITOR})}}

六、常见错误与解决方案

6.1 符号不显示

问题: 符号名称拼写错误

// ❌ 错误:拼写错误RcIcon({ name:'icon-houi_hom'})// ✅ 正确:使用常量避免拼写错误RcIcon({ name: IconName.HOME})

问题: 字体未加载

// 确保字体文件存在于rawfile目录// 路径:entry/src/main/resources/rawfile/rc_font.woff

6.2 颜色不生效

问题: 图片格式不支持颜色填充

// ❌ PNG图片不支持fillColorRcIcon({ name:'https://example.com/logo.png', color:'#ff0000'// 无效})// ✅ 使用SVG或字体符号RcIcon({ name: IconName.LOGO, color:'#ff0000'// 有效})

6.3 动画卡顿

问题: 同时运行过多动画

// ❌ 100个符号同时动画ForEach(Array.from({ length:100}),()=>{RcIcon({ name: IconName.STAR, iconAnimation:{...}})})// ✅ 限制同时动画数量ForEach(Array.from({ length:100}),(_, index)=>{RcIcon({ name: IconName.STAR, iconAnimation: index <10?{...}:undefined})})

6.4 内存泄漏

问题: 动画未清理

// ❌ 忘记清理定时器privatestartRotation(){setInterval(()=>{this.angle +=10},50)}// ✅ 在生命周期中清理private timer?:numberaboutToAppear(){this.timer =setInterval(()=>{this.angle +=10},50)}aboutToDisappear(){if(this.timer){clearInterval(this.timer)}}

七、版本兼容性

7.1 API版本检查

import{ systemCapability }from'@ohos.ability.ability'if(systemCapability.querySystemCapability('SystemCapability.ArkUI.ArkUI.Full')){// 使用新APIRcIcon({ name: IconName.NEW_FEATURE, iconAnimation:{...}})}else{// 降级处理RcIcon({ name: IconName.NEW_FEATURE})}

7.2 功能降级

// 检测是否支持动画@State supportsAnimation:boolean=trueRcIcon({ name: IconName.LOADER, iconAnimation:this.supportsAnimation ?{ duration:1000, iterations:-1}:undefined})

八、总结

8.1 核心要点

  1. 类型安全优先:使用IconName常量避免错误
  2. 性能意识:合理控制渲染和动画
  3. 用户体验:提供清晰的视觉反馈
  4. 可访问性:支持无障碍功能
  5. 可维护性:编写清晰、可测试的代码

8.2 应用价值

RcIcon作为UI库的基础组件,在实际项目中:

  • 减少50%以上的符号集成时间
  • 提供统一的视觉语言
  • 保证跨平台一致性
  • 支持主题定制和扩展
  • 性能优化开箱即用

Read more

Flutter for OpenHarmony: Flutter 三方库 barcode_image 为鸿蒙应用提供全场景条形码与二维码绘制生成方案(识别码专家)

Flutter for OpenHarmony: Flutter 三方库 barcode_image 为鸿蒙应用提供全场景条形码与二维码绘制生成方案(识别码专家)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在进行 OpenHarmony 的物流、商超支付、凭证分享或资产管理应用开发时,生成可视化的条形码(Barcode)和二维码(QR Code)是一项基础技能。 1. 会员卡演示:如何为用户展示一个清晰的 Code 128 条码? 2. 离线分享:如何生成一个带自定义色彩的二维码以便用户扫码? 3. 资产审计:如何将成千上万个资产编号快速生成为可打印的 EAN-13 码图? barcode_image 软件包基于强大的 barcode 引擎,专门解决了在 Flutter 环境下如何将这些抽象的代码逻辑“渲染(Rendering)”为精美图像的问题。 一、可视化生成架构模型 该库实现了从“原始数据串”到“位图/矢量图”的像素级映射。

By Ne0inhk

Flutter 三方库 holiday_jp 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、透明、全维度的日本法定节假日(公休日)查询与日历调度引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 holiday_jp 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、透明、全维度的日本法定节假日(公休日)查询与日历调度引擎 在鸿蒙(OpenHarmony)系统的全球化(Globalization)出海应用、针对日本市场的日程管理、财务结算系统(需考虑日本银行休假)或带有国际化特色的鸿蒙版日历组件中,如何瞬间获取任意年份日本的法定节假日、判定当前是否为公休日?holiday_jp 为开发者提供了一套工业级的、基于官方精细化数据集的日本节假日处理方案。本文将深入实战其在鸿蒙出海应用逻辑层中的应用。 前言 什么是 Holiday JP?它是一个专注于提供日本法定假期(祝日)数据的专业库。它涵盖了从传统的“元日”到现代的“体育之日”等所有官方假期,并能自动处理由于由于由于由于“振替休日(补休)”产生的动态调休逻辑。在 Flutter

By Ne0inhk

手把手教你“养龙虾”:OpenClaw本地部署完全指南(Windows/Mac/Linux全兼容)

目录 1.为什么要本地部署?先搞懂这三点 1.数据隐私可控 2.长期零成本 3.断网也能用 2.部署前的“全家桶”准备 3.Windows 11本地部署(最全版本) 第一步:以管理员身份打开PowerShell 第二步:解锁执行策略 第三步:一键安装核心依赖 第四步:验证安装 第五步:配置国内镜像(加速下载) 第六步:全局安装OpenClaw 第七步:初始化配置 第八步:启动服务 第九步:生成访问Token 第十步:访问Web控制台 4.MacOS本地部署(Intel芯片/M芯片通用) 第一步:打开终端 第二步:安装Homebrew(包管理工具) 第三步:安装Node.

By Ne0inhk