news 2026/6/14 13:05:52

HarmonyOS PC 实战系列之flexBasis 百分比响应式网格——PC 端应用桌面怎么做自适应列数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS PC 实战系列之flexBasis 百分比响应式网格——PC 端应用桌面怎么做自适应列数

文章目录

      • flexBasis 百分比的工作原理
      • 动态列数切换
      • 完整代码
      • flexBasis 和 width 的区别
      • 间距的处理方式
      • 小结

做过应用桌面类布局的人大概都遇过这个问题:产品说"普通窗口显示 5 列,最大化窗口显示 7 列",然后你翻了一圈文档,发现 Grid 的列数要写死,只能靠监听窗口宽度变化手动算。

flexBasis百分比有更优雅的解法:不关心窗口多宽,只告诉每个格子"你占父容器的1/N",超出一行的自动换行。想换列数,改一个数字就行。

flexBasis 百分比的工作原理

flexBasis设置的是 Flex 子项在"分配剩余空间之前"的基准尺寸。配合FlexWrap.Wrap,每行放不下的子项自动换到下一行。

想让每行固定 N 列:

// 每行 5 列.flexBasis('20%')// 100 / 5 = 20%// 每行 4 列.flexBasis('25%')// 100 / 4 = 25%// 每行 6 列.flexBasis('16.66%')// 100 / 6 ≈ 16.67%

注意:如果子项有paddingmargin,实际宽度会超出百分比,导致一行排不下 N 个。要么用calc(20% - 16px)减去间距,要么把间距做在 padding 里而不是 margin 里。

动态列数切换

把列数作为@State变量,用户可以在 4/5/6 列之间切换:

@Statecolumns:number=5getbasisPercent():string{return`${(100/this.columns).toFixed(2)}%`}

切换列数时只改this.columnsbasisPercent自动更新,所有格子的宽度跟着变。

完整代码

interfaceAppItem{id:numbername:stringcategory:stringversion:stringicon:stringcolor:stringhasUpdate:booleanrating:number}@Entry@Componentstruct PcAppGridPage{@Statecolumns:number=5@StateactiveCategory:string='全部'@StatecategoryList:string[]=['全部']@StatedisplayApps:AppItem[]=[]apps:AppItem[]=[{id:1,name:'浏览器',category:'工具',version:'1.2.1',icon:'🌐',color:'#3B82F6',hasUpdate:false,rating:4.8},{id:2,name:'文件管理',category:'系统',version:'2.0.0',icon:'📁',color:'#10B981',hasUpdate:true,rating:4.6},{id:3,name:'备忘录',category:'效率',version:'3.1.0',icon:'📝',color:'#F59E0B',hasUpdate:false,rating:4.7},{id:4,name:'日历',category:'效率',version:'1.8.2',icon:'📅',color:'#8B5CF6',hasUpdate:true,rating:4.5},{id:5,name:'计算器',category:'工具',version:'1.0.5',icon:'🔢',color:'#EF4444',hasUpdate:false,rating:4.9},{id:6,name:'相册',category:'媒体',version:'4.2.1',icon:'🖼️',color:'#EC4899',hasUpdate:false,rating:4.8},{id:7,name:'音乐',category:'媒体',version:'5.0.1',icon:'🎵',color:'#6366F1',hasUpdate:true,rating:4.7},{id:8,name:'视频',category:'媒体',version:'3.3.0',icon:'🎬',color:'#F43F5E',hasUpdate:false,rating:4.6},{id:9,name:'设置',category:'系统',version:'1.5.0',icon:'⚙️',color:'#6B7280',hasUpdate:false,rating:4.4},{id:10,name:'AppGallery',category:'系统',version:'12.0',icon:'🛒',color:'#CF1322',hasUpdate:true,rating:4.8},{id:11,name:'邮件',category:'通讯',version:'2.4.0',icon:'📧',color:'#0EA5E9',hasUpdate:false,rating:4.3},{id:12,name:'地图',category:'工具',version:'6.1.0',icon:'🗺️',color:'#22C55E',hasUpdate:false,rating:4.7},{id:13,name:'天气',category:'工具',version:'1.9.0',icon:'🌤️',color:'#06B6D4',hasUpdate:true,rating:4.6},{id:14,name:'代码编辑器',category:'效率',version:'2.0.0',icon:'💻',color:'#1D4ED8',hasUpdate:false,rating:4.9},{id:15,name:'终端',category:'系统',version:'1.1.0',icon:'⬛',color:'#111827',hasUpdate:false,rating:4.8},]aboutToAppear():void{this.initData()}initData():void{constcats:string[]=['全部']this.apps.forEach((app:AppItem)=>{if(cats.indexOf(app.category)===-1){cats.push(app.category)}})this.categoryList=catsthis.updateDisplayApps()}updateDisplayApps():void{if(this.activeCategory==='全部'){this.displayApps=this.appsreturn}this.displayApps=this.apps.filter((app:AppItem)=>app.category===this.activeCategory)}getbasisPercent():string{return`${(100/this.columns).toFixed(2)}%`}@BuilderappIcon(app:AppItem){Column({space:8}){Stack({alignContent:Alignment.TopEnd}){// 应用图标Column(){Text(app.icon).fontSize(32)}.width(64).height(64).borderRadius(16).backgroundColor(app.color).justifyContent(FlexAlign.Center).shadow({radius:8,color:`${app.color}30`,offsetY:4})// 更新角标if(app.hasUpdate){Circle({width:12,height:12}).fill('#EF4444').border({width:2,color:Color.White}).offset({x:4,y:-4})}}Text(app.name).fontSize(11).fontColor('#374151').maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis}).width(72).textAlign(TextAlign.Center)}.alignItems(HorizontalAlign.Center).padding({top:12,bottom:12}).onClick(()=>{})}@BuildercolumnSelector(){Row({space:4}){Text('列数:').fontSize(13).fontColor('#6B7280')ForEach([4,5,6,7],(col:number)=>{Text(`${col}`).fontSize(13).fontColor(this.columns===col?Color.White:'#374151').padding({left:12,right:12,top:6,bottom:6}).backgroundColor(this.columns===col?'#3B82F6':'#F3F4F6').borderRadius(8).onClick(()=>{this.columns=col})},(col:number)=>col.toString())}}build(){Column({space:0}){// 顶部工具栏Row({space:16}){Text('应用桌面').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#111827').layoutWeight(1)this.columnSelector()}.padding({left:24,right:24,top:20,bottom:16}).backgroundColor(Color.White).width('100%')// 分类筛选Scroll(){Row({space:4}){ForEach(this.categoryList,(cat:string)=>{Text(cat).fontSize(13).fontColor(this.activeCategory===cat?'#3B82F6':'#6B7280').fontWeight(this.activeCategory===cat?FontWeight.Medium:FontWeight.Normal).padding({left:16,right:16,top:8,bottom:8}).backgroundColor(this.activeCategory===cat?'#EFF6FF':Color.Transparent).borderRadius(20).onClick(()=>{this.activeCategory=catthis.updateDisplayApps()})},(cat:string)=>cat)}.padding({left:24,right:24})}.scrollable(ScrollDirection.Horizontal).width('100%').height(44).backgroundColor(Color.White)Divider().strokeWidth(1).color('#F3F4F6')// 应用网格Scroll(){Flex({wrap:FlexWrap.Wrap,alignContent:FlexAlign.Start}){ForEach(this.displayApps,(app:AppItem)=>{Column(){this.appIcon(app)}.flexBasis(this.basisPercent).alignItems(HorizontalAlign.Center)},(app:AppItem)=>app.id.toString())}.width('100%').padding({left:12,right:12,top:16,bottom:24})}.layoutWeight(1).backgroundColor('#F9FAFB')}.width('100%').height('100%').constraintSize({minWidth:640})}}

flexBasis 和 width 的区别

width是最终宽度,不参与 Flex 伸缩计算。flexBasis是分配剩余空间前的初始尺寸,如果同时有flexGrow,子项还会继续伸长。

对于应用网格这种场景,我们想要的是"精确按百分比切割",不希望伸长:

.flexBasis(this.basisPercent)// 不设 flexGrow,默认是 0,不会伸长

如果写成.width(this.basisPercent),在没有 Flex 上下文的 Column 里是一样的效果,但在 Flex 容器里语义不同。

间距的处理方式

格子之间不用margin(会破坏百分比宽度的计算),改成在内容区域内设padding

Column(){this.appIcon(app)}.flexBasis('20%').padding({left:4,right:4})// 内部 padding,不影响 flexBasis 计算

或者用gap属性(需要 Flex 容器支持,视版本而定)。

小结

flexBasis百分比 +FlexWrap.Wrap是实现响应式网格最简洁的方式。改列数只改一个数字,不需要重新计算像素值,也不需要监听窗口宽度事件。

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

九大网盘直链下载3分钟终极指南:告别蜗牛速度的完整解决方案

九大网盘直链下载3分钟终极指南:告别蜗牛速度的完整解决方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 …

作者头像 李华
网站建设 2026/6/14 13:03:06

MPC8313E DDR内存控制器配置实战:从原理到调试

1. 项目概述:深入理解DDR内存控制器在嵌入式系统,尤其是基于PowerPC架构的MPC8313E这类处理器上,DDR内存控制器是决定系统性能与稳定性的核心引擎。它远不止是一个简单的“地址翻译器”,而是一个集成了复杂状态机、时序调度和信号…

作者头像 李华
网站建设 2026/6/14 13:01:53

3分钟上手:浏览器直接查看SQLite数据库的终极免费工具

3分钟上手:浏览器直接查看SQLite数据库的终极免费工具 【免费下载链接】sqlite-viewer View SQLite file online 项目地址: https://gitcode.com/gh_mirrors/sq/sqlite-viewer 还在为查看SQLite数据库文件而烦恼吗?想摆脱复杂的数据库软件安装过程…

作者头像 李华
网站建设 2026/6/14 13:00:51

Honey Select 2 游戏增强补丁:自动化翻译与去码优化架构解析

Honey Select 2 游戏增强补丁:自动化翻译与去码优化架构解析 【免费下载链接】HS2-HF_Patch Automatically translate, uncensor and update HoneySelect2! 项目地址: https://gitcode.com/gh_mirrors/hs/HS2-HF_Patch HS2-HF_Patch 是一个专为 Honey Select…

作者头像 李华
网站建设 2026/6/14 13:00:51

Python之antspyt1w包语法、参数和实际应用案例

Python antspyt1w 包完整使用指南 一、包基础概述 1. 简介 antspyt1w 是基于 ANTs (Advanced Normalization Tools) 封装的 Python 专用工具包,核心面向神经影像(脑结构 T1 加权影像) 处理,是医学影像、脑科学领域主流工具。核心定…

作者头像 李华
网站建设 2026/6/14 12:59:55

深入解析EHCI数据结构:从USB主机控制器原理到MPC8313E实战

1. 项目概述与核心价值 USB主机控制器,尤其是遵循EHCI规范的控制器,是现代嵌入式系统和PC平台实现高速USB 2.0功能的核心引擎。很多开发者在使用USB接口时,往往只关注上层驱动API,对底层硬件如何调度和管理数据流知之甚少。这就像…

作者头像 李华