倒计时离开主页面也要继续:闪控窗如何保持同一进度![]()
倒计时进入闪控窗或闪控球以后,不能因为界面换了形态就重新从25分钟开始。
倒计时离开主页面也要继续:闪控窗如何保持同一进度需要的不只是首屏效果,还要补上计时阶段、总时长、剩余秒数、最近时间戳和页面生命周期。用绝对时间计算剩余秒数,让主页面、紧凑窗口和系统形态读取同一个计时会话。
代码沿着【建立计时会话 → 使用时间戳推进 → 实现暂停恢复 → 共享窗口状态 → 在销毁时清理任务】展开。这样出现依赖界面每秒减一,在页面暂停或系统调度延迟后会产生越来越大的误差时,可以直接定位到对应环节。
倒计时保存结束时刻
这里先不碰颜色和间距。计时阶段、总时长、剩余秒数、最近时间戳和页面生命周期如果没有确定,依赖界面每秒减一,在页面暂停或系统调度延迟后会产生越来越大的误差。
exporttypeTimerPhase='ready'|'running'|'paused'|'completed'exportclassFeatureController{privatetotalSeconds:number=1500privateremainingSeconds:number=1500privatephase:TimerPhase='ready'privatelastTickAt:number=0start(now:number):void{if(this.phase==='completed'){this.remainingSeconds=this.totalSeconds}this.phase='running'this.lastTickAt=now类型定义完成后,界面拿到的是可追踪状态,不再依赖某个卡片是否仍在组件树中。
暂停不是停止定时器这么简单
现在把实现暂停恢复接到模型上。先收窄参数,再生成新的业务状态。
动作函数先收窄输入,再决定是否写入,页面不承担边界判断。
}tick(now:number):number{if(this.phase!=='running'){returnthis.remainingSeconds}constelapsed:number=Math.floor((now-this.lastTickAt)/1000)if(elapsed<=0){returnthis.remainingSeconds}this.remainingSeconds=Math.max(0,this.remainingSeconds-elapsed)this.lastTickAt+=elapsed*1000if(this.remainingSeconds===0){this.phase='completed'即使回调顺序变化,版本判断也不会让依赖界面每秒减一,在页面暂停或系统调度延迟后会产生越来越大的误差。
主页面和闪控窗读取同一会话
运行图中的数据来自与控制器一致的模型。主页面展示任务与完整进度,紧凑形态只保留时间和暂停动作。
页面先呈现当前阶段,再给出下一步可执行动作;不能执行的入口不抢占主色。
import{FeatureController}from'../features/FeatureController'interfaceFeatureItem{id:numbertitle:stringdetail:string}@Entry@Componentstruct Index{privatefeatureController:FeatureController=newFeatureController()@StateselectedId:number=1@Statemessage:string='主窗口、闪控窗与闪控球状态同步'@Statewide:boolean=false@Statecompact:boolean=false@Staterunning:boolean=true@Statevalue:number=18privateitems:FeatureItem[]=[{id:1,title:'主应用',detail:'可交互能力 1'},{id:2,title:'闪控窗',detail:'可交互能力 2'},{id:3,title:'闪控球',detail:'可交互能力 3'}]privateselected():FeatureItem{returnthis.items.find((item:FeatureItem)=>item.id===this.selectedId)??this.items[0]}privatetick(delta:number):void{this.value=Math.max(0,Math.min(60,this.value+delta))this.message=`窗口状态已同步:${this.value}`}privatechangeWindow():void{this.compact=!this.compactthis.message=this.compact?'已切换到紧凑窗口':'已恢复标准窗口'}build(){Row({space:20}){Column({space:15}){Text('__TITLE__').fontSize(28).fontWeight(FontWeight.Bold).fontColor(Color.White);Text(this.message).fontSize(13).fontColor('#FFF0E7');Text(`${this.value}`).fontSize(74).fontWeight(FontWeight.Bold).fontColor(Color.White);Button(this.running?'暂停':'继续').backgroundColor('#35FFFFFF').onClick(()=>{this.running=!this.running})}.width('48%').height(this.compact?'55%':'100%').padding(22).borderRadius(this.compact?42:26).backgroundColor('__ACCENT__')Column({space:12}){Text('窗口控制').fontSize(23).fontWeight(FontWeight.Bold).width('100%');ForEach(this.items,(item:FeatureItem)=>{Row(){Text(item.title).fontSize(15).layoutWeight(1);Text(this.selectedId===item.id?'显示中':'').fontColor('__ACCENT__')}.padding(13).borderRadius(14).backgroundColor(this.selectedId===item.id?'#FFF0E8':'#F2F4F7').onClick(()=>{this.selectedId=item.id;this.message=`已切换:${item.title}`})},(item:FeatureItem)=>item.id.toString());Row({space:8}){Button('-1').onClick(()=>this.tick(-1));Button('+1').backgroundColor('__ACCENT__').onClick(()=>this.tick(1))};Button(this.compact?'展开窗口':'切到紧凑窗口').width('100%').onClick(()=>this.changeWindow())}.layoutWeight(1).height('100%').padding(20).borderRadius(24).backgroundColor(Color.White)}.width('100%').height('100%').padding(20).backgroundColor('#F5F6F8').onAreaChange((oldArea:Area,newArea:Area)=>{this.wide=Number(newArea.width)>=700})}}Index.ets在这里读取控制器结果并组织主页面展示任务与完整进度,紧凑形态只保留时间和暂停动作。计算仍留在功能层,因此更换设备形态只需调整区域组合。
重新进入页面时补算时间
页面离开、设备断开和输入无效分别产生不同结果,不能共用一个布尔值。
}returnthis.remainingSeconds}pause():void{if(this.phase==='running'){this.phase='paused'}}getPhase():TimerPhase{returnthis.phase}}恢复逻辑读取业务快照,而不是从组件默认值重新猜测当前阶段。
FlashTimer的会话结构
下面是FlashTimer当前使用的目录。pages负责主页面展示任务与完整进度,紧凑形态只保留时间和暂停动作,features保存本文展示的业务链路。
FlashTimer/ ├─ entry/src/main/ets/ │ ├─ pages/Index.ets # 首屏与响应式布局 │ ├─ features/FeatureController.ets # 模型、动作和边界 │ ├─ entryability/EntryAbility.ets # 页面入口与生命周期 │ └─ adapters/ # 真机能力或数据源接口 ├─ entry/src/main/resources/ # 颜色、文字和媒体资源 └─ build-profile.json5 # 工程构建配置Index.ets只组合界面,FeatureController.ets处理计时阶段、总时长、剩余秒数、最近时间戳和页面生命周期。如果后面接入真实设备或网络服务,只需增加适配器并把结果转换成控制器认识的状态。
项目完成后的关键不是多出几张卡片,而是实现暂停恢复能够稳定推动后续状态。