文章目录
- 前言
- 如果准备复刻,先盯住这几块
- 这部分最容易被低估:状态关系
- 这一页的控制权主要在这里
- 第一段关键代码:页面是怎么被带起来的
- 第二段关键代码:真正决定交互手感的地方
- 第一次跑这个案例,我会怎么试
- 这一页真正落地时,建议额外处理什么
- 把它吃透之后,后面会省很多时间
- 完整代码
- 收个尾
前言
跳到子页面选完东西,再把结果带回上一页,这是业务里最常见的交互之一。地址选择、标签选择、头像裁剪,基本都属于这一类。这个案例很实用。 有些案例看完只会得到一个 demo 效果,有些案例看完会顺手带走一套页面组织方法。我的目标是把它往后者那边讲。
如果把这页当成练习材料,我会优先看它怎样围绕 返回上一页时携带结果数据 组织页面。因为这一层想清楚了,后面的代码基本就不会散。
这类写法尤其适合落在 选择器、编辑确认、表单辅助页 这些场景里,所以我下面不会只讲“组件怎么写”,而是更关心“放进页面之后为什么这样组织”。
如果准备复刻,先盯住这几块
很多人学示例会直接复制 UI 代码,结果复刻出来是像的,但后面一改就散。原因通常不是代码抄错了,而是结构关系没先看懂。
| 页面区域 | 主要职责 | 在代码里的典型表现 |
|---|---|---|
| 头部说明区 | 交代当前案例在演示什么 | Text标题、补充说明、标签文案 |
| 核心展示区 | 承载组件能力的主要效果 | Column、Row、业务组件本体 |
| 辅助信息区 | 补充状态、标签、分组或统计信息 | 次级文本、角标、分组标题、描述块 |
| 交互入口区 | 负责切换、返回、定位、选择等动作 | 点击事件、按钮、索引条、导航入口 |
这一页没有额外拆出接口,页面更多是直接围绕基础数据和组件参数展开。
这类页面最容易被忽略的往往不是功能,而是手感、反馈和节奏,我会把这些细点单独挑出来说。
这部分最容易被低估:状态关系
如果一个示例后续不好改,十有八九不是布局有问题,而是状态职责没分开。这个案例刚好适合拿来观察这件事。
isShow:建议优先确认它控制的是显示、选择、切换还是数据同步。showDetail:建议优先确认它控制的是显示、选择、切换还是数据同步。selectedItem:建议优先确认它控制的是显示、选择、切换还是数据同步。returnedResult:建议优先确认它控制的是显示、选择、切换还是数据同步。selectedColor:建议优先确认它控制的是显示、选择、切换还是数据同步。
真要自己上手改,我建议别一开始就扑到样式上,先把状态分层这件事处理好:
- 长期数据:列表内容、卡片信息、导航目标、角标基数这类不该跟着一次点击乱跳的内容。
- 交互状态:当前选中项、弹层开关、吸附开关、回显结果这类会频繁变化的值。
- 计算结果:过滤结果、统计文案、高亮判断这类更适合派生出来的内容。
这一页的控制权主要在这里
示例代码里最怕被忽略的,就是那些看起来普通、实际上掌控页面节奏的方法。这里建议优先把它们摸清。
- 这个案例没有太多额外方法,核心价值主要集中在页面结构和组件组合方式。
我自己读这类方法时,通常只抓一条线:谁触发、谁被改、页面哪里立刻有反馈。把这三件事连起来,很多交互代码一下就顺了。
第一段关键代码:页面是怎么被带起来的
这一段建议慢一点看。它通常决定了页面初始状态,也决定了后续哪些区域会跟着刷新。
@StateisShow:boolean=true@StateshowDetail:boolean=false@StateselectedItem:string=''@StatereturnedResult:string=''@StateselectedColor:string=''privateitems:string[]=['红色','蓝色','绿色','橙色']build(){Column(){if(this.isShow){Column({space:16}){Text('带参数返回').fontSize(20).fontWeight(FontWeight.Bold).fontColor(DEMO_THEME_COLOR)Text('NavDestination 返回时携带结果数据到上一页').fontSize(13).fontColor(DEMO_SUBTEXT_COLOR).width('100%')// 主页面if(!this.showDetail){Column({space:12}){这一段我通常不会一下翻过去,而是先确认下面这几个判断点:
@State字段到底在控制显示、选择、跳转结果还是模式切换。- 默认值是不是合理,页面一打开会不会就落在一个可理解的状态上。
- 字段命名能不能让后来的人一眼看懂用途,而不是还要翻半天 UI。
第二段关键代码:真正决定交互手感的地方
这一段代码先在点击时写入selectedItem和selectedColor,再切到确认页。等用户确认后,结果才会写进returnedResult并回到主页面。这个流程把“选择中”和“已返回”分开了,读起来比直接改主页面结果安全。
.onClick(()=>{this.selectedItem=itemthis.selectedColor=[DEMO_THEME_COLOR,PRESET_COLORS[0].value,PRESET_COLORS[1].value,PRESET_COLORS[2].value][index]this.showDetail=true})})}.width('100%')}.width('100%')}else{// 确认页面Column({space:16}){Row(){Text('← 取消').fontSize(14).fontColor(DEMO_SUBTEXT_COLOR).onClick(()=>{this.showDetail=false})Blank()Text('选择确认').fontSize(14).fontWeight(FontWeight.Medium).fontColor('#333333')}代码读到这里,我通常会顺手补三件事,不然后面很容易只看热闹:
- 这个交互入口接收的到底是什么输入。
- 输入进来之后,代码改了哪个状态,或者触发了哪次导航。
- 变化发生后,用户最先感知到的反馈会落在哪个区域。
这三个问题串起来之后,这一页基本就不只是“看过”,而是真的读懂了。
第一次跑这个案例,我会怎么试
第一次接触这个案例时,不要急着扩展功能。先把默认状态和一次完整交互跑顺,后面加东西才不会歪。
- 先进入页面,确认首屏是不是把带结果返回页面的主题交代清楚。
- 盯住核心展示区,观察默认状态下最醒目的内容是什么。
- 主动触发一次关键交互,比如点击、滑动、跳转、返回、切换或者选择。
- 回头检查状态区、提示区、标题区或者附属信息有没有跟着变化。
- 最后再打开源码,对照刚才那次交互,把状态变化链路串起来。
如果这五步你能边操作边说清楚页面发生了什么,后面再换成自己的数据和交互,心里会稳很多。
这一页真正落地时,建议额外处理什么
我自己迁示例代码时,最怕的是把展示逻辑和业务逻辑粘成一团。这个案例里有些地方可以提前规避。
- 导航类页面迁到业务里时,最先要补的是参数类型和返回结果约定,而不是视觉样式。
- 如果页面层级开始变多,建议尽早把跳转入口、回退逻辑和标题栏动作拆成清晰边界。
- 只要页面里有重复块,就别硬撑着手写到底;早点抽成小组件,后面改样式和改交互都会轻松很多。
- 示例数据最好和布局代码分开放,不然后面一接接口,页面文件很容易立刻变臃肿。
把它吃透之后,后面会省很多时间
我会把这类案例当成“工程感比炫技感更强”的材料来留存。后面真的要做同类页面时,能少走不少弯路。
- 页面结构比较稳,后续不管是换数据还是换皮肤,成本都不会特别高。
- 状态数量总体可控,适合拿来练“一个页面里如何分配职责”这件事。
- 组件参数和页面目标之间关系比较直观,不太会出现“能跑但看不懂为什么这么配”的情况。
完整代码
下面保留整理过命名的完整 ArkTS 代码,方便你直接对照学习。这里已经去掉原始的 Demo 命名,改成了更贴近当前案例语义的名称。
/** * 带结果返回页面 */import{PRESET_COLORS,generateListItems,DEMO_BG_COLOR,DEMO_CARD_COLOR,DEMO_THEME_COLOR,DEMO_SUBTEXT_COLOR}from'./types'@Entry@Componentstruct RouteReturnWithResult{@StateisShow:boolean=true@StateshowDetail:boolean=false@StateselectedItem:string=''@StatereturnedResult:string=''@StateselectedColor:string=''privateitems:string[]=['红色','蓝色','绿色','橙色']build(){Column(){if(this.isShow){Column({space:16}){Text('带参数返回').fontSize(20).fontWeight(FontWeight.Bold).fontColor(DEMO_THEME_COLOR)Text('NavDestination 返回时携带结果数据到上一页').fontSize(13).fontColor(DEMO_SUBTEXT_COLOR).width('100%')// 主页面if(!this.showDetail){Column({space:12}){// 返回结果展示if(this.returnedResult){Column({space:8}){Text('返回的结果').fontSize(13).fontWeight(FontWeight.Medium).fontColor('#333333').width('100%')Row({space:10}){Circle({width:20,height:20}).fill(this.selectedColor)Text(this.returnedResult).fontSize(14).fontWeight(FontWeight.Bold).fontColor(this.selectedColor).layoutWeight(1)}}.width('100%').padding(12).backgroundColor('#F5F5F5').borderRadius(8)}Text('请选择一个选项').fontSize(14).fontWeight(FontWeight.Medium).fontColor('#333333').width('100%')Row({space:10}){ForEach(this.items,(item:string,index:number)=>{Column({space:6}){Circle({width:36,height:36}).fill([DEMO_THEME_COLOR,PRESET_COLORS[0].value,PRESET_COLORS[1].value,PRESET_COLORS[2].value][index])Text(item).fontSize(12).fontColor('#333333')}.layoutWeight(1).padding(12).backgroundColor(DEMO_CARD_COLOR).borderRadius(10).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).onClick(()=>{this.selectedItem=itemthis.selectedColor=[DEMO_THEME_COLOR,PRESET_COLORS[0].value,PRESET_COLORS[1].value,PRESET_COLORS[2].value][index]this.showDetail=true})})}.width('100%')}.width('100%')}else{// 确认页面Column({space:16}){Row(){Text('← 取消').fontSize(14).fontColor(DEMO_SUBTEXT_COLOR).onClick(()=>{this.showDetail=false})Blank()Text('选择确认').fontSize(14).fontWeight(FontWeight.Medium).fontColor('#333333')}.width('100%')Column({space:12}){Circle({width:60,height:60}).fill(this.selectedColor)Text(this.selectedItem).fontSize(22).fontWeight(FontWeight.Bold).fontColor(this.selectedColor)Text('确认选择此选项并返回结果?').fontSize(13).fontColor(DEMO_SUBTEXT_COLOR)}.width('100%').padding(24).backgroundColor(DEMO_CARD_COLOR).borderRadius(12)Row({space:10}){Button('取消').fontSize(14).height(40).layoutWeight(1).backgroundColor('#EEEEEE').fontColor('#666666').borderRadius(20).onClick(()=>{this.showDetail=false})Button('确认返回').fontSize(14).height(40).layoutWeight(1).backgroundColor(this.selectedColor).fontColor('#FFFFFF').borderRadius(20).onClick(()=>{this.returnedResult='您选择了: '+this.selectedItemthis.showDetail=false})}.width('100%')}.width('100%').padding(16).backgroundColor(DEMO_CARD_COLOR).borderRadius(12)}// 流程说明Column({space:6}){Text('数据流程').fontSize(12).fontColor(DEMO_SUBTEXT_COLOR)Row({space:6}){Text('选择项').fontSize(11).fontColor('#FFFFFF').backgroundColor(DEMO_THEME_COLOR).padding({left:8,right:8,top:3,bottom:3}).borderRadius(6)Text('→').fontSize(11).fontColor('#999999')Text('NavDestination').fontSize(11).fontColor('#FFFFFF').backgroundColor(PRESET_COLORS[1].value).padding({left:8,right:8,top:3,bottom:3}).borderRadius(6)Text('→').fontSize(11).fontColor('#999999')Text('带参返回').fontSize(11).fontColor('#FFFFFF').backgroundColor(PRESET_COLORS[0].value).padding({left:8,right:8,top:3,bottom:3}).borderRadius(6)}}.width('100%').padding(10).backgroundColor('#FAFAFA').borderRadius(8)}.alignItems(HorizontalAlign.Start)}Text('带结果返回页面 - 带参数返回').fontSize(12).fontColor('#999999').margin({top:12})}.width('100%').height('100%').backgroundColor(DEMO_BG_COLOR).padding(16)}}收个尾
真放到日常开发里,最有参考价值的往往不是花样最多的页面,而是这种把关键决定摆得很清楚的案例。带结果返回页面 就属于这一类。