文章目录
- 标签云的核心需求
- 直接看实现
- Wrap 解决的是不确定性
- 状态更新的小细节
- 小结
标签云是一个特别容易暴露布局问题的组件。
标签长度不一样,数量也不固定。今天是 8 个标签,明天产品说要加到 18 个;中文标签还好,一旦混进TypeScript、HarmonyOS、iOS/Swift这种长文本,写死一行基本就崩了。
这时候就该用FlexWrap.Wrap。它能让子项在主轴空间不足时自动换行,非常适合标签、筛选条件、兴趣选择这类场景。
标签云的核心需求
这个页面做的是“选择感兴趣的技术标签”,它有几个典型需求:
- 标签数量多,需要自动换行;
- 点击后要切换选中状态;
- 选中标签要显示勾选标识和高亮边框;
- 底部实时展示已选内容;
- 至少选择 3 个后,确认按钮才可用。
布局和状态都不复杂,但组合起来很贴近真实业务。
直接看实现
最关键的是Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap })。
/** * Flex wrap:标签云布局 * 知识点:FlexWrap.Wrap 换行,多行标签自适应排列 */interfaceTagItem{id:numberlabel:stringselected:booleancategory:stringcolor:stringbgColor:string}@Entry@Componentstruct FlexTagCloudPage{@StatetagList:TagItem[]=[{id:1,label:'JavaScript',selected:false,category:'前端',color:'#F7A400',bgColor:'#FFF8E8'},{id:2,label:'TypeScript',selected:true,category:'前端',color:'#3178C6',bgColor:'#EAF3FF'},{id:3,label:'ArkTS',selected:true,category:'鸿蒙',color:'#007DFF',bgColor:'#EAF3FF'},{id:4,label:'HarmonyOS',selected:false,category:'鸿蒙',color:'#FF4D4D',bgColor:'#FFF0F0'},{id:5,label:'Vue.js',selected:false,category:'前端',color:'#42B883',bgColor:'#EDFAF4'},{id:6,label:'React',selected:false,category:'前端',color:'#61DAFB',bgColor:'#E8FBFF'},{id:7,label:'数据结构',selected:false,category:'基础',color:'#9B59B6',bgColor:'#F5EEFF'},{id:8,label:'算法',selected:true,category:'基础',color:'#E67E22',bgColor:'#FFF4EC'},{id:9,label:'Git',selected:false,category:'工具',color:'#F05032',bgColor:'#FFF0EC'},{id:10,label:'Docker',selected:false,category:'工具',color:'#2496ED',bgColor:'#EAF5FF'},{id:11,label:'Android',selected:false,category:'移动',color:'#3DDC84',bgColor:'#EBFEF4'},{id:12,label:'iOS/Swift',selected:false,category:'移动',color:'#FA7343',bgColor:'#FFF2EC'},{id:13,label:'Node.js',selected:false,category:'后端',color:'#339933',bgColor:'#EDFAED'},{id:14,label:'Python',selected:false,category:'后端',color:'#3776AB',bgColor:'#EAF3FF'},{id:15,label:'机器学习',selected:false,category:'AI',color:'#FF6B6B',bgColor:'#FFF0F0'},{id:16,label:'UI设计',selected:false,category:'设计',color:'#FF61B8',bgColor:'#FFF0F8'},{id:17,label:'产品思维',selected:false,category:'设计',color:'#8E44AD',bgColor:'#F8EEFF'},{id:18,label:'SQL',selected:false,category:'数据库',color:'#4479A1',bgColor:'#EAF3FF'},]privategetselectedCount():number{returnthis.tagList.filter(t=>t.selected).length}privategetselectedLabels():string{returnthis.tagList.filter(t=>t.selected).map(t=>t.label).join('、')}toggleTag(id:number):void{constidx=this.tagList.findIndex(t=>t.id===id)if(idx>=0){constupdated=this.tagList[idx]updated.selected=!updated.selectedthis.tagList[idx]=updatedthis.tagList=this.tagList.slice()}}build(){Column({space:0}){Column({space:4}){Text('选择你感兴趣的技术标签').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#1A1A1A')Text('已选 '+this.selectedCount+' 个标签,至少选择 3 个').fontSize(14).fontColor(this.selectedCount>=3?'#07C160':'#FF8C00')}.padding({left:16,right:16,top:24,bottom:20}).alignItems(HorizontalAlign.Start).width('100%')Flex({direction:FlexDirection.Row,wrap:FlexWrap.Wrap,alignContent:FlexAlign.Start}){ForEach(this.tagList,(tag:TagItem)=>{Row({space:6}){if(tag.selected){Text('✓').fontSize(12).fontColor(tag.color).fontWeight(FontWeight.Bold)}Text(tag.label).fontSize(14).fontColor(tag.selected?tag.color:'#666666').fontWeight(tag.selected?FontWeight.Medium:FontWeight.Normal)}.padding({left:14,right:14,top:8,bottom:8}).backgroundColor(tag.selected?tag.bgColor:'#FFFFFF').borderRadius(20).border({width:1.5,color:tag.selected?tag.color:'#E8E8E8'}).margin({right:10,bottom:10}).onClick(()=>{this.toggleTag(tag.id)})})}.padding({left:16,right:16}).width('100%')Blank()Column({space:10}){if(this.selectedCount>0){Column({space:6}){Text('已选:').fontSize(13).fontColor('#888888')Text(this.selectedLabels).fontSize(14).fontColor('#1A1A1A').maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis})}.alignItems(HorizontalAlign.Start).padding({left:14,right:14,top:12,bottom:12}).backgroundColor('#F5F5F5').borderRadius(10).width('100%')}Button('确认选择').width('100%').height(50).fontSize(16).fontWeight(FontWeight.Medium).backgroundColor(this.selectedCount>=3?'#007DFF':'#CCCCCC').borderRadius(25).enabled(this.selectedCount>=3)}.padding({left:16,right:16,bottom:32})}.width('100%').height('100%').backgroundColor('#F5F6FA')}}Wrap 解决的是不确定性
标签云最大的麻烦是不确定。
你不知道标签会有几个,也不知道每个标签有多长,更不知道用户手机屏幕宽度是多少。FlexWrap.Wrap的价值就在这里:空间够就继续横向排,空间不够就自动换到下一行。
这比手动计算每行放几个标签靠谱得多,也更符合 ArkUI 声明式 UI 的写法。
状态更新的小细节
toggleTag修改选中状态后,又写了一句:
this.tagList=this.tagList.slice()这不是多余操作。数组内部对象的属性变化,有时候不一定触发你期望的 UI 刷新。重新赋一个新数组引用,可以让状态变化更明确。
这类写法在列表选择、购物车勾选、筛选条件切换里都很常见。
小结
只要遇到“数量不固定、宽度不固定、需要自动换行”的布局,就优先考虑FlexWrap.Wrap。标签云只是一个例子,搜索历史、筛选条件、技能标签、兴趣选择都能用这套思路。