
文章目录完整代码最后总结壁纸页看起来是图片网格但真正要处理的是内容浏览链路用户先搜索或按分类缩小范围再点进某张壁纸预览最后决定收藏或设为当前壁纸。这个案例把这条链路压在一个页面里代码不算复杂但状态关系挺完整。WallpaperItem是这页的核心模型。它不只是记录壁纸标题还包含category、colors、isFavorite、isSet、downloads。其中colors用来模拟壁纸渐变背景isFavorite表示用户资产isSet表示当前系统选择downloads则给内容增加一点热度信息。也就是说一张壁纸同时承担展示、筛选、操作和状态标记四类职责。wallpapers是主数据activeCategory和searchText是筛选条件previewWallpaper是当前预览对象。这三个层级分得比较清楚主数据负责提供内容全集筛选状态负责决定网格里出现哪些项预览状态负责控制弹层。你读这种页面时先把这三层关系抓住后面 UI 结构就好理解很多。getFilteredWallpapers()是内容流的入口。它先从全部壁纸开始如果分类不是全部就按category过滤如果有搜索词再按标题做二次过滤。这个顺序很自然分类负责粗筛搜索负责细筛。最终 Grid 只关心这个函数返回什么不需要知道筛选规则具体怎么拼。这类函数最好集中写不要把过滤逻辑散在ForEach里。原因很简单壁纸内容流以后很可能继续加条件比如收藏筛选、下载量排序、颜色标签、最近使用。如果筛选逻辑一开始就收在getFilteredWallpapers()后面加条件只需要改一个入口。收藏逻辑用toggleFavorite(id)处理它根据id找到目标壁纸把isFavorite取反。网格卡片和预览弹层都能触发这个动作所以它必须是一个独立函数而不是写在某个按钮里。这里还有一个细节预览弹层里收藏后会重新从wallpapers里找到当前项并更新previewWallpaper这样弹层按钮文案能立刻同步。setWallpaper(id)的业务规则更强一点。收藏可以有很多个但当前壁纸只能有一个所以它不是简单地修改某一项而是遍历所有壁纸让目标项isSet true其他项全部变成false。这就是一个典型的“唯一选中”状态迁移。写设置类页面时这种规则一定要收在函数里否则很容易出现两个“当前”同时存在的 bug。预览弹层由previewWallpaper: WallpaperItem | null控制。点击网格项时把它设成当前壁纸遮罩点击或设置完成后再设回null。这比单独维护showPreview加currentId更直接因为弹层需要展示完整壁纸信息直接保存当前对象能少一次查找。不过这种写法也有取舍。对象如果来自wallpapers数组后续再改收藏状态时弹层里的对象可能不是最新引用所以代码里才需要收藏后重新find一次。正式项目里也可以只保存previewWallpaperId然后用计算逻辑拿到最新对象。两种方式都能用关键是要知道自己在处理的是“当前对象”还是“当前对象的 id”。网格里的视觉实现也挺适合教学。它没有引入真实图片资源而是用colors.map()生成渐变色再用 emoji 做中心视觉符号。这样读者不用关心图片加载、缓存、占位图这些额外问题可以把注意力放在状态和交互上。对于教程示例来说这是一个挺合理的取舍。搜索框的searchText和分类条的activeCategory都会影响同一个网格结果这也是页面最主要的响应链路输入变化或分类变化后getFilteredWallpapers()重新计算Grid 重新渲染。这里没有额外保存“筛选后的列表”避免了主数据和派生列表不同步的问题。如果继续往真实壁纸应用扩展我会优先做三件事。第一给空结果补一个空状态不然搜索不到内容时页面会突然变空。第二把toggleFavorite()和setWallpaper()改成不可变对象返回减少深层状态更新的风险。第三把分类列表从写死数组改成从壁纸数据或接口里生成避免新增分类时忘记同步categories。这个案例真正值得学的是内容型页面的状态分层wallpapers管内容activeCategory和searchText管筛选previewWallpaper管局部弹层isFavorite和isSet管用户操作结果。层级清楚之后壁纸页就不只是一个 Grid而是一条完整的浏览、预览、决策链路。完整代码下面保留案例完整代码方便你直接对照学习、复制和重构。// 壁纸切换中心: 手机壁纸切换 - 分类浏览、收藏、设置壁纸模拟interfaceWallpaperItem{id:numbertitle:stringcategory:stringemoji:stringcolors:string[]isFavorite:booleanisSet:booleandownloads:number}EntryComponentstruct WallpaperSwitchCenter{Statewallpapers:WallpaperItem[][{id:1,title:极光之夜,category:自然,emoji:,colors:[#0D1B2A,#1B4F72,#2ECC71],isFavorite:true,isSet:true,downloads:12400},{id:2,title:落日余晖,category:自然,emoji:,colors:[#FF6B35,#FF9500,#FFD700],isFavorite:false,isSet:false,downloads:9800},{id:3,title:霓虹城市,category:城市,emoji:,colors:[#1A1A2E,#16213E,#E94560],isFavorite:true,isSet:false,downloads:18600},{id:4,title:雪山清晨,category:自然,emoji:️,colors:[#E8F4FD,#AED6F1,#5DADE2],isFavorite:false,isSet:false,downloads:7200},{id:5,title:樱花飘落,category:植物,emoji:,colors:[#FADADD,#F8BBD9,#F48FB1],isFavorite:false,isSet:false,downloads:15300},{id:6,title:深海魅影,category:自然,emoji:,colors:[#001F3F,#0074D9,#7FDBFF],isFavorite:true,isSet:false,downloads:11200},{id:7,title:简约几何,category:抽象,emoji:,colors:[#F5F5F5,#E0E0E0,#007AFF],isFavorite:false,isSet:false,downloads:6800},{id:8,title:赛博朋克,category:抽象,emoji:⚡,colors:[#0D0D0D,#FF0080,#00FFFF],isFavorite:false,isSet:false,downloads:22100},{id:9,title:秋日枫林,category:自然,emoji:,colors:[#8B1A1A,#CC5500,#FFB300],isFavorite:false,isSet:false,downloads:8900},{id:10,title:粉色梦幻,category:抽象,emoji:,colors:[#FF9A9E,#FECFEF,#FF6B9D],isFavorite:false,isSet:false,downloads:13700},]StateactiveCategory:string全部StatepreviewWallpaper:WallpaperItem|nullnullStatesearchText:stringprivatecategories:string[][全部,自然,城市,植物,抽象]getFilteredWallpapers():WallpaperItem[]{letlistthis.wallpapersif(this.activeCategory!全部){listlist.filter((w:WallpaperItem)w.categorythis.activeCategory)}if(this.searchText){listlist.filter((w:WallpaperItem)w.title.includes(this.searchText))}returnlist}toggleFavorite(id:number){this.wallpapersthis.wallpapers.map((w){if(w.idid){w.isFavorite!w.isFavorite}returnw})}setWallpaper(id:number){this.wallpapersthis.wallpapers.map((w:WallpaperItem){w.isSet(w.idid);returnw})this.previewWallpapernull}build(){Column(){// 顶部Text(壁纸).fontSize(24).fontWeight(FontWeight.Bold).fontColor(#1A1A1A).padding({left:20,right:20,top:20,bottom:8}).alignSelf(ItemAlign.Start)// 搜索Row(){Text().fontSize(14)TextInput({placeholder:搜索壁纸...,text:this.searchText}).layoutWeight(1).height(36).backgroundColor(transparent).fontSize(13).margin({left:8}).onChange((v:string){this.searchTextv})}.height(44).backgroundColor(#F2F2F7).borderRadius(22).padding({left:14,right:14}).margin({left:20,right:20,bottom:12})// 分类筛选Scroll(){Row({space:8}){ForEach(this.categories,(cat:string){Text(cat).fontSize(13).fontColor(this.activeCategorycat?#FFFFFF:#666666).backgroundColor(this.activeCategorycat?#1A1A1A:#F2F2F7).borderRadius(16).padding({left:14,right:14,top:7,bottom:7}).onClick((){this.activeCategorycat})})}.padding({left:20,right:20})}.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).margin({bottom:16})// 壁纸网格Scroll(){Grid(){ForEach(this.getFilteredWallpapers(),(wp:WallpaperItem){GridItem(){Stack(){// 渐变背景代替图片Column().width(100%).height(100%).linearGradient({angle:135,colors:wp.colors.map((c:string,i:number)[c,i/(wp.colors.length-1)]as[ResourceColor,number])}).borderRadius(12)// 中心emojiText(wp.emoji).fontSize(36)// 底部信息Column(){Blank()Row(){Column(){Text(wp.title).fontSize(12).fontColor(#FFFFFF).fontWeight(FontWeight.Medium)Text(\⬇ \${(wp.downloads/1000).toFixed(1)}k\).fontSize(10).fontColor(#FFFFFF80).margin({top:2})}.alignItems(HorizontalAlign.Start).layoutWeight(1)Text(wp.isFavorite?❤️:).fontSize(16).onClick(()this.toggleFavorite(wp.id))}.width(100%).padding({left:8,right:8,bottom:8})}.width(100%).height(100%)// 当前使用标识if(wp.isSet){Text(当前).fontSize(10).fontColor(#FFFFFF).backgroundColor(#007AFF).borderRadius(8).padding({left:6,right:6,top:3,bottom:3}).position({x:8,y:8})}}.width(100%).height(160).borderRadius(12).clip(true).onClick((){this.previewWallpaperwp})}})}.columnsTemplate(1fr 1fr).columnsGap(12).rowsGap(12).padding({left:16,right:16,bottom:24})}.layoutWeight(1).scrollBar(BarState.Off)// 预览弹窗if(this.previewWallpaper!null){Stack(){Column().width(100%).height(100%).backgroundColor(#000000CC).onClick((){this.previewWallpapernull})Column({space:16}){// 预览Stack(){Column().width(200).height(300).linearGradient({angle:135,colors:this.previewWallpaper!.colors.map((c:string,i:number)[c,i/(this.previewWallpaper!.colors.length-1)]as[ResourceColor,number])}).borderRadius(20)Text(this.previewWallpaper!.emoji).fontSize(60)}.width(200).height(300).shadow({radius:20,color:#00000060,offsetX:0,offsetY:8})Text(this.previewWallpaper!.title).fontSize(18).fontWeight(FontWeight.Bold).fontColor(#FFFFFF)Text(\\${this.previewWallpaper!.category}· ⬇ \${(this.previewWallpaper!.downloads/1000).toFixed(1)}k\).fontSize(12).fontColor(#FFFFFF80)Row({space:12}){Button(this.previewWallpaper!.isFavorite?❤️ 已收藏: 收藏).fontSize(14).fontColor(#FFFFFF).backgroundColor(#FFFFFF30).borderRadius(22).height(44).padding({left:20,right:20}).onClick((){this.toggleFavorite(this.previewWallpaper!.id)this.previewWallpaperthis.wallpapers.find(ww.idthis.previewWallpaper!.id)??null})Button(this.previewWallpaper!.isSet?✓ 已使用:设为壁纸).fontSize(14).fontColor(#1A1A1A).backgroundColor(#FFFFFF).borderRadius(22).height(44).padding({left:20,right:20}).onClick((){if(this.previewWallpaper)this.setWallpaper(this.previewWallpaper.id)})}}.alignItems(HorizontalAlign.Center)}.width(100%).height(100%).position({x:0,y:0})}}.width(100%).height(100%).backgroundColor(#FFFFFF)}}最后总结壁纸切换中心 这个案例并不靠复杂技巧取胜它更像一个结构扎实、逻辑完整的业务页面样板。把这篇文章里的状态设计、函数组织和页面分层吃透之后你再去写同类型功能速度会明显快很多。