互动服务卡片既要保持静态状态下的信息效率,也要在摇动后给出清楚、短暂且可以恢复的动态反馈。
服务卡片不只显示一行文字:摇一摇让前景真正动起来需要的不只是首屏效果,还要补上互动状态、动画进度、焦点位置、触发节流和恢复时间。把摇动事件转换成受控的动画状态,而不是让传感器回调直接修改多个视觉属性。
功能推进顺序保持为【设计静态卡片 → 接入摇动事件 → 增加触发节流 → 驱动前景动画 → 动画结束恢复摘要】。这里最先关心的是状态能否连续,而不是首屏能放多少卡片。
1|摇动回调不能直接拉扯界面
若让组件临时保存状态,很快就会遇到连续摇动会重复启动动画,导致前景层停在中间状态或无法回到静态布局。所以代码先描述对象和阶段。
exportinterfaceVisualState{intensity:numberfocusX:numberfocusY:numberactive:booleanmode:string}exportclassFeatureController{privatestate:VisualState={intensity:0.68,focusX:0.5,focusY:0.42,active:true,mode:'互动服务卡片'}moveFocus(x:number,y:number):VisualState{至此,静态摘要保持稳定,动态前景允许适度出框但不能遮住主要信息不再要求复制状态。
2|节流窗口挡住连续触发
实现增加触发节流时先约束输入来源,避免设备回调和按钮点击产生两套分支。
控制器返回处理结果,按钮文案只负责把结果显示出来。
this.state.focusX=Math.max(0,Math.min(1,x))this.state.focusY=Math.max(0,Math.min(1,y))returnthis.getState()}updateIntensity(value:number):number{constintensity:number=Math.max(0,Math.min(1,value))this.state.intensity=intensityreturnintensity当前请求拥有自己的版本,因而不会出现连续摇动会重复启动动画,导致前景层停在中间状态或无法回到静态布局。
3|动画结束要回到静态摘要
首屏需要一眼看出项目正在做什么,因此静态摘要保持稳定,动态前景允许适度出框但不能遮住主要信息。
这一版首屏强调互动状态,其余字段放进辅助区域,截图也更容易看清功能重点。
import{FeatureController}from'../features/FeatureController'interfaceFeatureItem{id:numbertitle:stringdetail:string}@Entry@Componentstruct Index{privatefeatureController:FeatureController=newFeatureController()@StateselectedId:number=1@Statemessage:string='从静态摘要切换到动态前景'@Statewide:boolean=false@Stateanimated:boolean=false@StateshakeCount:number=0@StatecardDepth:number=8privateitems: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]}privateshake():void{this.animated=!this.animatedthis.shakeCount++this.cardDepth=this.animated?24:8this.selectedId=(this.selectedId%this.items.length)+1this.message=`第${this.shakeCount}次互动:${this.selected().title}`}build(){Column({space:18}){Row(){Column({space:4}){Text('__TITLE__').fontSize(29).fontWeight(FontWeight.Bold);Text(this.message).fontSize(13).fontColor('#667085')}.alignItems(HorizontalAlign.Start);Blank();Text(`互动${this.shakeCount}`).fontColor('__ACCENT__')}.width('100%');Stack(){Rect().width(this.animated?'88%':'76%').height(this.animated?'88%':'72%').radius(this.cardDepth).fill('__ACCENT__');Circle({width:this.animated?190:120,height:this.animated?190:120}).fill('#42FFFFFF');Column({space:10}){Text(this.selected().title).fontSize(27).fontWeight(FontWeight.Bold).fontColor(Color.White);Text(this.animated?'动态前景已经出框':'静态卡片等待互动').fontSize(13).fontColor('#F4ECFF')}}.layoutWeight(1).width('100%');Row({space:10}){Button('摇一摇').layoutWeight(1).height(48).backgroundColor('__ACCENT__').onClick(()=>this.shake());Button(this.animated?'恢复静态':'播放动效').layoutWeight(1).height(48).onClick(()=>this.shake())}.width('100%')}.width('100%').height('100%').padding(22).backgroundColor('#F5F3F8').onAreaChange((oldArea:Area,newArea:Area)=>{this.wide=Number(newArea.width)>=700})}}Index.ets在这里读取控制器结果并组织静态摘要保持稳定,动态前景允许适度出框但不能遮住主要信息。计算仍留在功能层,因此更换设备形态只需调整区域组合。
4|卡片前景允许动但信息不能跳
如果页面在处理中重建,控制器仍保存当前阶段,视图回来后直接订阅。
}toggle():boolean{this.state.active=!this.state.activereturnthis.state.active}getState():VisualState{return{intensity:this.state.intensity,focusX:this.state.focusX,focusY:this.state.focusY,active:this.state.active,mode:this.state.mode}}}这里让旧请求失效,而不是等待它完成后再覆盖一次新状态。
5|ShakeCardLab的状态驱动方式
下面是ShakeCardLab当前使用的目录。pages负责静态摘要保持稳定,动态前景允许适度出框但不能遮住主要信息,features保存本文展示的业务链路。
ShakeCardLab/ ├─ 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处理互动状态、动画进度、焦点位置、触发节流和恢复时间。如果后面接入真实设备或网络服务,只需增加适配器并把结果转换成控制器认识的状态。
最终页面保持清楚,底层的互动状态、动画进度、焦点位置、触发节流和恢复时间也可以继续替换为真实能力实现。