文章目录筛选状态的设计分类列表也用 get 派生分类标签横向滚动选中状态的视觉设计完整代码搜索和分类的组合过滤分类标签里加计数徽章小结分类筛选是 PC 端应用桌面的标配功能。点击某个类别标签网格里只显示该类别的应用。听起来简单实现时有几个细节容易踩坑分类标签数量多了怎么处理、筛选结果怎么和标签状态保持同步、全部类别的逻辑怎么写干净。这篇把这几个问题一起解决。筛选状态的设计只需要一个State变量当前选中的分类名称。StateactiveCategory:string全部不需要再存一个当前显示的应用列表状态。用get派生getfilteredApps():AppItem[]{if(this.activeCategory全部)returnthis.appsreturnthis.apps.filter(aa.categorythis.activeCategory)}activeCategory变了filteredApps自动重新计算ForEach拿到新数组UI 刷新。整个流程只改一处状态。分类列表也用 get 派生分类列表从应用数据里提取不手动维护getcategories():string[]{constcatsnewSet(this.apps.map(aa.category))return[全部,...cats]}Set去重保证每个分类名只出现一次。最前面插入全部。这样好处是新增一个应用时如果它的分类之前没有分类列表会自动出现新分类不需要手动维护两份数据。分类标签横向滚动分类多了放不下的时候不能换行会占掉太多竖向空间用横向滚动Scroll(){Row({space:8}){ForEach(this.categories,(cat:string){Text(cat).fontSize(13).padding({left:16,right:16,top:8,bottom:8}).backgroundColor(this.activeCategorycat?#EFF6FF:Color.Transparent).fontColor(this.activeCategorycat?#3B82F6:#6B7280).borderRadius(20).onClick((){this.activeCategorycat})})}.padding({left:16,right:16})}.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)// 隐藏滚动条PC端标签行不需要滚动条.height(48)ScrollDirection.Horizontal设横向滚动scrollBar(BarState.Off)隐藏滚动条——标签行的滚动条一般没必要显示。选中状态的视觉设计选中的标签蓝色文字 浅蓝背景 字重加粗。未选中灰色文字 透明背景。这三个属性都由activeCategory cat这一个条件控制切换干净利落.fontColor(this.activeCategorycat?#3B82F6:#6B7280).backgroundColor(this.activeCategorycat?#EFF6FF:Color.Transparent).fontWeight(this.activeCategorycat?FontWeight.Medium:FontWeight.Normal)完整代码interfaceAppItem{id:numbername:stringcategory:stringversion:stringicon:stringcolor:stringhasUpdate:booleanrating:number}EntryComponentstruct PcAppGridPage{Statecolumns:number5StateactiveCategory: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.categoryListcatsthis.updateDisplayApps()}updateDisplayApps():void{if(this.activeCategory全部){this.displayAppsthis.appsreturn}this.displayAppsthis.apps.filter((app:AppItem)app.categorythis.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.columnscol?Color.White:#374151).padding({left:12,right:12,top:6,bottom:6}).backgroundColor(this.columnscol?#3B82F6:#F3F4F6).borderRadius(8).onClick((){this.columnscol})},(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.activeCategorycat?#3B82F6:#6B7280).fontWeight(this.activeCategorycat?FontWeight.Medium:FontWeight.Normal).padding({left:16,right:16,top:8,bottom:8}).backgroundColor(this.activeCategorycat?#EFF6FF:Color.Transparent).borderRadius(20).onClick((){this.activeCategorycatthis.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})}}搜索和分类的组合过滤两个过滤条件叠加时先按分类过滤再按搜索词过滤getfilteredApps():AppItem[]{letresultthis.activeCategory全部?this.apps:this.apps.filter(aa.categorythis.activeCategory)if(this.searchText.trim()){resultresult.filter(aa.name.includes(this.searchText.trim()))}returnresult}两个StateactiveCategory和searchText任意一个变化filteredApps就会重新计算UI 自动更新。分类标签里加计数徽章分类标签里加上该分类应用数量比如效率 3用户在点之前就知道里面有几个constcountthis.apps.filter(aa.categorycat).length这个数量是实时计算的如果应用列表变了数量自动跟着变。不需要在数据里预先存每个分类有多少个。小结分类筛选的核心设计原则只存一个筛选状态其他的派生。不要同时存当前分类和当前显示的应用列表两个状态这样只会给自己制造同步问题。让get访问器帮你做派生简单清晰。
HarmonyOS PC实战系列之应用桌面的分类筛选——get 派生属性与横向滚动标签行
发布时间:2026/6/14 12:22:26
文章目录筛选状态的设计分类列表也用 get 派生分类标签横向滚动选中状态的视觉设计完整代码搜索和分类的组合过滤分类标签里加计数徽章小结分类筛选是 PC 端应用桌面的标配功能。点击某个类别标签网格里只显示该类别的应用。听起来简单实现时有几个细节容易踩坑分类标签数量多了怎么处理、筛选结果怎么和标签状态保持同步、全部类别的逻辑怎么写干净。这篇把这几个问题一起解决。筛选状态的设计只需要一个State变量当前选中的分类名称。StateactiveCategory:string全部不需要再存一个当前显示的应用列表状态。用get派生getfilteredApps():AppItem[]{if(this.activeCategory全部)returnthis.appsreturnthis.apps.filter(aa.categorythis.activeCategory)}activeCategory变了filteredApps自动重新计算ForEach拿到新数组UI 刷新。整个流程只改一处状态。分类列表也用 get 派生分类列表从应用数据里提取不手动维护getcategories():string[]{constcatsnewSet(this.apps.map(aa.category))return[全部,...cats]}Set去重保证每个分类名只出现一次。最前面插入全部。这样好处是新增一个应用时如果它的分类之前没有分类列表会自动出现新分类不需要手动维护两份数据。分类标签横向滚动分类多了放不下的时候不能换行会占掉太多竖向空间用横向滚动Scroll(){Row({space:8}){ForEach(this.categories,(cat:string){Text(cat).fontSize(13).padding({left:16,right:16,top:8,bottom:8}).backgroundColor(this.activeCategorycat?#EFF6FF:Color.Transparent).fontColor(this.activeCategorycat?#3B82F6:#6B7280).borderRadius(20).onClick((){this.activeCategorycat})})}.padding({left:16,right:16})}.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)// 隐藏滚动条PC端标签行不需要滚动条.height(48)ScrollDirection.Horizontal设横向滚动scrollBar(BarState.Off)隐藏滚动条——标签行的滚动条一般没必要显示。选中状态的视觉设计选中的标签蓝色文字 浅蓝背景 字重加粗。未选中灰色文字 透明背景。这三个属性都由activeCategory cat这一个条件控制切换干净利落.fontColor(this.activeCategorycat?#3B82F6:#6B7280).backgroundColor(this.activeCategorycat?#EFF6FF:Color.Transparent).fontWeight(this.activeCategorycat?FontWeight.Medium:FontWeight.Normal)完整代码interfaceAppItem{id:numbername:stringcategory:stringversion:stringicon:stringcolor:stringhasUpdate:booleanrating:number}EntryComponentstruct PcAppGridPage{Statecolumns:number5StateactiveCategory: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.categoryListcatsthis.updateDisplayApps()}updateDisplayApps():void{if(this.activeCategory全部){this.displayAppsthis.appsreturn}this.displayAppsthis.apps.filter((app:AppItem)app.categorythis.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.columnscol?Color.White:#374151).padding({left:12,right:12,top:6,bottom:6}).backgroundColor(this.columnscol?#3B82F6:#F3F4F6).borderRadius(8).onClick((){this.columnscol})},(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.activeCategorycat?#3B82F6:#6B7280).fontWeight(this.activeCategorycat?FontWeight.Medium:FontWeight.Normal).padding({left:16,right:16,top:8,bottom:8}).backgroundColor(this.activeCategorycat?#EFF6FF:Color.Transparent).borderRadius(20).onClick((){this.activeCategorycatthis.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})}}搜索和分类的组合过滤两个过滤条件叠加时先按分类过滤再按搜索词过滤getfilteredApps():AppItem[]{letresultthis.activeCategory全部?this.apps:this.apps.filter(aa.categorythis.activeCategory)if(this.searchText.trim()){resultresult.filter(aa.name.includes(this.searchText.trim()))}returnresult}两个StateactiveCategory和searchText任意一个变化filteredApps就会重新计算UI 自动更新。分类标签里加计数徽章分类标签里加上该分类应用数量比如效率 3用户在点之前就知道里面有几个constcountthis.apps.filter(aa.categorycat).length这个数量是实时计算的如果应用列表变了数量自动跟着变。不需要在数据里预先存每个分类有多少个。小结分类筛选的核心设计原则只存一个筛选状态其他的派生。不要同时存当前分类和当前显示的应用列表两个状态这样只会给自己制造同步问题。让get访问器帮你做派生简单清晰。