news 2026/6/16 0:03:02

HarmonyOS PC实战之手写进度条——flexBasis 百分比做分类占比条

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS PC实战之手写进度条——flexBasis 百分比做分类占比条

文章目录

    • 前言
      • 手写进度条的结构:两段 Column 拼接
      • 多段分类占比条
      • 完整代码
      • flexBasis 和 width 百分比的区别
      • clip 的必要性
      • 小结

前言

ArkUI 自带的Progress组件用起来方便,但定制空间有限:只支持单色、颜色不能随数据动态变化、圆角样式有限。

真实项目里经常碰到"支出分类占比条"这种需求——每个分类一种颜色,宽度按百分比,多个分类拼在一行。用Progress搞不定,用 Flex 手写反而更直接。

手写进度条的结构:两段 Column 拼接

最简单的进度条是两段:有色段 + 灰色段。

Row(){// 有色段(已完成)Column().width(`${percentage}%`).height(8).backgroundColor(color).borderRadius({topLeft:4,bottomLeft:4})// 灰色段(未完成)Column().layoutWeight(1)// 填满剩余.height(8).backgroundColor('#E5E7EB').borderRadius({topRight:4,bottomRight:4})}.width('100%')

有色段用width百分比设置宽度,灰色段用layoutWeight(1)填满剩余。

多段分类占比条

多个分类按比例拼在同一行,每段不同颜色:

Row(){ForEach(categories,(cat)=>{Column().flexBasis(`${cat.percentage}%`)// ← flexBasis 百分比.height(10).backgroundColor(cat.color)})}.width('100%').borderRadius(5).clip(true)// ← 裁掉超出圆角的部分

flexBasis而不是width,因为 Flex 子项默认不超出父容器,加在一起刚好 100% 时不会溢出。

完整代码

interfaceCategoryItem{name:stringamount:numbercolor:stringicon:string}interfaceMonthExpense{month:stringtotal:numberbudget:numbercategories:CategoryItem[]}@Entry@Componentstruct PcProgressBarPage{@StateselectedMonthIndex:number=0monthData:MonthExpense[]=[{month:'2024年10月',total:14800,budget:15000,categories:[{name:'餐饮',amount:3200,color:'#EF4444',icon:'🍜'},{name:'交通',amount:1800,color:'#F59E0B',icon:'🚇'},{name:'购物',amount:4600,color:'#8B5CF6',icon:'🛍️'},{name:'娱乐',amount:2200,color:'#3B82F6',icon:'🎮'},{name:'其他',amount:3000,color:'#6B7280',icon:'📦'},]},{month:'2024年11月',total:12400,budget:15000,categories:[{name:'餐饮',amount:2800,color:'#EF4444',icon:'🍜'},{name:'交通',amount:1400,color:'#F59E0B',icon:'🚇'},{name:'购物',amount:3600,color:'#8B5CF6',icon:'🛍️'},{name:'娱乐',amount:1800,color:'#3B82F6',icon:'🎮'},{name:'其他',amount:2800,color:'#6B7280',icon:'📦'},]},{month:'2024年12月',total:18600,budget:15000,categories:[{name:'餐饮',amount:4200,color:'#EF4444',icon:'🍜'},{name:'交通',amount:2200,color:'#F59E0B',icon:'🚇'},{name:'购物',amount:6800,color:'#8B5CF6',icon:'🛍️'},{name:'娱乐',amount:3400,color:'#3B82F6',icon:'🎮'},{name:'其他',amount:2000,color:'#6B7280',icon:'📦'},]}]getCurrentMonth():MonthExpense{constmonth=this.monthData[this.selectedMonthIndex]if(month){returnmonth}returnthis.monthData[0]}getbudgetUsagePercent():number{returnMath.min(100,Math.floor(this.getCurrentMonth().total/this.getCurrentMonth().budget*100))}getisOverBudget():boolean{returnthis.getCurrentMonth().total>this.getCurrentMonth().budget}getCategoryPercent(cat:CategoryItem):number{returnMath.floor(cat.amount/this.getCurrentMonth().total*100)}formatMoney(amount:number):string{return`¥${amount.toLocaleString()}`}@BuildermultiColorBar(categories:CategoryItem[]){Row(){ForEach(categories,(cat:CategoryItem)=>{Column().flexBasis(`${this.getCategoryPercent(cat)}%`).height(12).backgroundColor(cat.color)})}.width('100%').height(12).borderRadius(6).clip(true)}@BuildersingleBar(percentage:number,color:string,height:number=8){Row(){Column().width(`${percentage}%`).height(height).backgroundColor(color).borderRadius({topLeft:height/2,bottomLeft:height/2,topRight:percentage>=100?height/2:0,bottomRight:percentage>=100?height/2:0}).animation({duration:400,curve:Curve.EaseOut})Column().layoutWeight(1).height(height).backgroundColor('#F3F4F6').borderRadius({topRight:height/2,bottomRight:height/2,topLeft:0,bottomLeft:0})}.width('100%').borderRadius(height/2).clip(true)}@BuildercategoryRow(cat:CategoryItem){Column({space:6}){Row(){Row({space:8}){Text(cat.icon).fontSize(14)Text(cat.name).fontSize(13).fontColor('#374151').fontWeight(FontWeight.Medium)}.layoutWeight(1)Row({space:8}){Text(this.formatMoney(cat.amount)).fontSize(13).fontColor('#1F2937').fontWeight(FontWeight.Medium)Text(`${this.getCategoryPercent(cat)}%`).fontSize(11).fontColor(cat.color).padding({left:6,right:6,top:2,bottom:2}).backgroundColor(`${cat.color}20`).borderRadius(8)}}.width('100%').alignItems(VerticalAlign.Center)this.singleBar(this.getCategoryPercent(cat),cat.color,6)}.width('100%').padding({top:4,bottom:4})}build(){Scroll(){Column({space:20}){// 标题 + 月份切换Row(){Text('支出分析').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#111827').layoutWeight(1)Row({space:4}){ForEach(this.monthData,(m:MonthExpense,idx:number)=>{Text(m.month.slice(5)).fontSize(12).fontColor(this.selectedMonthIndex===idx?Color.White:'#6B7280').padding({left:10,right:10,top:6,bottom:6}).backgroundColor(this.selectedMonthIndex===idx?'#3B82F6':'#F3F4F6').borderRadius(8).onClick(()=>{this.selectedMonthIndex=idx})})}}.width('100%')// 总预算进度Column({space:12}){Row(){Column({space:4}){Text(`${this.getCurrentMonth().month}支出`).fontSize(12).fontColor('#6B7280')Text(this.formatMoney(this.getCurrentMonth().total)).fontSize(24).fontWeight(FontWeight.Bold).fontColor(this.isOverBudget?'#EF4444':'#111827')}.layoutWeight(1).alignItems(HorizontalAlign.Start)Column({space:4}){Text('月度预算').fontSize(12).fontColor('#6B7280').alignSelf(ItemAlign.End)Text(this.formatMoney(this.getCurrentMonth().budget)).fontSize(16).fontColor('#6B7280').alignSelf(ItemAlign.End)}}.width('100%')// 总进度条this.singleBar(this.budgetUsagePercent,this.isOverBudget?'#EF4444':'#10B981',12)Row(){Text(this.isOverBudget?'⚠️ 已超预算':`剩余${this.formatMoney(this.getCurrentMonth().budget-this.getCurrentMonth().total)}`).fontSize(12).fontColor(this.isOverBudget?'#EF4444':'#10B981')Text(`${this.budgetUsagePercent}%`).fontSize(12).fontColor('#6B7280')}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.padding(20).backgroundColor(Color.White).borderRadius(16).shadow({radius:8,color:'#08000000'})// 分类占比条(多色)Column({space:12}){Text('分类占比').fontSize(15).fontWeight(FontWeight.Medium).fontColor('#374151')this.multiColorBar(this.getCurrentMonth().categories)// 图例Flex({wrap:FlexWrap.Wrap}){ForEach(this.getCurrentMonth().categories,(cat:CategoryItem)=>{Row({space:4}){Row().width(10).height(10).borderRadius(2).backgroundColor(cat.color)Text(`${cat.name}${this.getCategoryPercent(cat)}%`).fontSize(11).fontColor('#6B7280')}.margin({right:16,bottom:6})})}.width('100%')}.padding(20).backgroundColor(Color.White).borderRadius(16).shadow({radius:8,color:'#08000000'})// 分类详情列表Column({space:4}){Text('分类明细').fontSize(15).fontWeight(FontWeight.Medium).fontColor('#374151').padding({bottom:12})ForEach(this.getCurrentMonth().categories,(cat:CategoryItem)=>{this.categoryRow(cat)Divider().strokeWidth(1).color('#F9FAFB').margin({top:4,bottom:4})})}.padding(20).backgroundColor(Color.White).borderRadius(16).shadow({radius:8,color:'#08000000'})}.padding({left:32,right:32,top:32,bottom:32}).constraintSize({minWidth:600,maxWidth:860}).margin({left:'auto',right:'auto'})}.width('100%').height('100%').backgroundColor('#F9FAFB')}}

flexBasis 和 width 百分比的区别

在这个场景里两者效果差不多,但有一个细节差异:

  • width('20%')是最终宽度,Flex 伸缩不影响它
  • flexBasis('20%')是初始尺寸,如果还有flexGrow,子项会继续增长

对于分类占比条,所有分类的百分比加起来 = 100%,刚好填满容器,不需要flexGrow。用哪个都一样,flexBasis语义上更准确(“基准尺寸”,配合 Flex 容器)。

clip 的必要性

多色占比条用borderRadius让两端圆角,但各个分类的小 Column 没有设圆角,它们的直角会超出父容器的圆角范围,视觉上露出方角。

.clip(true)让父容器裁掉超出自身圆角的内容:

Row(){/* 各分类色块 */}.borderRadius(6).clip(true)// ← 这行解决方角露出的问题

小结

手写进度条的两种形式:单色进度条用"有色段 + 灰色段"两段拼接;多色占比条用ForEach+flexBasis百分比。两种都不需要 Canvas,纯 Flex + Column 实现,样式完全可控。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/16 0:00:56

MPC866 SMC串口控制器:UART、透明、GCI模式配置与调试实战

1. MPC866 SMC串口控制器:从手册到实战的深度解析 在嵌入式系统开发,尤其是基于PowerPC架构的通信处理器领域,MPC866 PowerQUICC系列是一个绕不开的经典。它的强大之处不仅在于主频,更在于其高度集成的通信外设,其中两…

作者头像 李华
网站建设 2026/6/16 0:00:55

专业级Windows风扇控制方案:FanControl深度配置指南

专业级Windows风扇控制方案:FanControl深度配置指南 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/fa/F…

作者头像 李华
网站建设 2026/6/15 23:55:00

终极修复指南:彻底解决Windows程序启动依赖问题

终极修复指南:彻底解决Windows程序启动依赖问题 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 系统依赖修复工具为Windows用户提供了完整的自动化解…

作者头像 李华
网站建设 2026/6/15 23:52:54

嵌入式DMA链式描述符机制详解:从原理到NXP MSC8251实战

1. 项目概述:从CPU的“搬运工”到智能数据管家在嵌入式系统里干活,尤其是搞网络处理、音视频流或者高速数据采集的兄弟,肯定都跟DMA(Direct Memory Access,直接内存访问)打过交道。简单来说,DMA…

作者头像 李华
网站建设 2026/6/15 23:52:54

嵌入式语音通信VAD/CNG/DTX算法:原理、集成与Motorola库实战

1. 项目概述:从“静默”中榨取效率的语音通信核心技术在数字语音通信的世界里,我们每天都在传输海量的音频数据。但你是否想过,一次普通的通话中,大约有50%的时间双方都是沉默的?这些静默期,无论是思考的间…

作者头像 李华