我在一家普通公司当程序员,公司专门做航标,然后为一些海事和港航道部门提供软件服务。整个团队规模也不大,硬件软件加一起才 10几个人。刚入职那会,软件需求也比较简单。就是对航标进行遥测遥控。报警处置啊,日常维护这些。说起我和鸿蒙的故事。我印象最深的就是那个凌晨三点的夜晚调试和问题解决。

一:事情起因

当时公司为一个航道中心做系统。这个鸿蒙航标控制系统承载着港口调度员的期待。开始去港口调研时,老调度员王师傅指着布满按钮的实体控制台叹气:“小伙子,我们这眼睛早就花了,每次调航标参数,盯着屏幕等加载的那几秒,心都悬着。” 他的话让我在需求书上重重画了条红线:操作延迟必须控制在 0.3 秒内。回去进行开发时。​系统首页的结构比想象中复杂得多。最外层的 Scroll 要展示辖区内 12 个水域的总览列表,每个水域卡片里嵌套着横向 Scroll,用来陈列该区域的航标设备,而每个航标卡片内部还有一层实时状态监控条 —— 这三层 Scroll 就像港口的三道防波堤,少了哪一层都无法实现精准调度。最初的代码用传统 NestedScroll 实现,逻辑清晰得像港口的航道图。可实际运行起来的效果却像老式雷达的扫描线,滑动时屏幕会出现明显的残影。我盯着 Profiler 工具里锯齿状的帧率曲线,看着那些低于 50fps 的波谷,突然想起工程师同事老周说的话:“航标系统的每一行代码都系着船锚,你得让数据流动得像水流一样自然,该快的时候要如急流,该缓的时候要似静水。”是啊,这样肯定是不行的。当时系统也是急着使用。

初次嵌套代码:

// 最初的嵌套滑动实现
@Component
struct BeaconList {
  @State waterAreas: WaterArea[] = []
  @State currentTime: string = ''

  aboutToAppear() {
    this.loadWaterAreaData() // 加载12个水域的基础数据
    this.startRealTimeUpdate() // 启动每秒一次的状态刷新
  }

  build() {
    Scroll() {
      Column() {
        Text(`实时更新:${this.currentTime}`).fontSize(12).padding(10)
        ForEach(this.waterAreas, (area) => {
          Scroll({ direction: Axis.Horizontal }) {
            Row() {
              ForEach(area.beacons, (beacon) => {
                BeaconCard({ 
                  beacon: beacon,
                  onStatusChange: (newStatus) => this.updateBeaconStatus(beacon.id, newStatus)
                })
              })
            }.padding(15)
          }
          .border({ width: 1, color: '#e5e5e5' })
          .borderRadius(8)
          .margin(10)
        })
      }
    }
  }
}

 

解决其他问题,已经加班到凌晨3点。显示器蓝光在凌晨三点的房间里洇开一片冷色,键盘旁的速溶咖啡已经凉透,杯壁凝着的水珠顺着桌沿滴落在地板上,像在为我第 17 次按下运行键计数。模拟器里的航标控制页面又一次卡在了半空中 ——Scroll 组件的滑动条僵在水域分组卡片中间,实时刷新的航标状态数据像被冻住的浪花,明明 API 9 的文档里白纸黑字写着支持嵌套滑动,可我的代码就像驶入浅滩的船,怎么也驶不进流畅交互的深水区。是换方案?还是找答案?

二、从社区中得来的灵感

       回想起圈子里经常说的一句话“自己解决不了的问题,就多在社区看看别人怎么做的”。我开始在社区文章中进行查找,我在开发者社区翻到第 37 篇技术帖时,眼皮已经开始打架。桌上的手机突然震动,是值班室的发来的消息:“明天有大风预警,系统最好能赶在早班调试。” 这句话像冷水浇在头上,我猛地掐了下大腿,强迫自己清醒。​就在这时,一篇标题为《鸿蒙(HarmonyOS)性能优化实战-应用列表场景性能提升》的帖子跳进视野。之中提到的列表各种优化思路,给我当头棒喝,我拍着额头才反应过来 —— 辖区内 275个航标、12 个水域分组,每次滑动都要全量渲染,就像让所有航标同时闪烁,不卡顿才怪。​

       重构代码花了整整两个小时。我把外层的 ForEach 换成 LazyForEach,为每个水域卡片添加了可见性监听,只有当卡片进入屏幕视野时才加载完整数据,还把实时刷新的频率从每秒一次调整为按需更新 —— 当用户停止滑动 300 毫秒后,才触发状态同步。当模拟器里的界面第一次实现无卡顿滑动,航标状态随着指尖滑动流畅切换时,我盯着屏幕笑出了声。窗外的天已经泛白,晨光透过纱窗在代码上投下细碎的光斑,像洒在水面的阳光,那一刻突然觉得所有的熬夜都有了意义。

感谢社区大佬的各种分享,也给我打开了开发的新思路。

优化后,部分代码:

// 优化后的嵌套滑动实现
@Component
struct OptimizedBeaconList {
  @State waterAreas: WaterArea[] = []
  @State currentTime: string = ''
  @State isScrolling: boolean = false // 标记是否正在滑动

  private scroller = new Scroller()
  private areaDataSource: LazyForEachDataSource<WaterArea> = new class implements LazyForEachDataSource<WaterArea> {
    private data: WaterArea[] = []
    
    constructor(data: WaterArea[]) {
      this.data = data
    }
    
    totalCount(): number {
      return this.data.length
    }
    
    getData(index: number): WaterArea {
      return this.data[index]
    }
    
    registerDataChangeListener(listener: DataChangeListener): void {}
    unregisterDataChangeListener(listener: DataChangeListener): void {}
  }(this.waterAreas)

  aboutToAppear() {
    this.loadWaterAreaBasicData() // 仅加载基础数据
    this.startScrollListener() // 监听滑动状态
  }

  // 监听滑动状态,控制数据刷新时机
  startScrollListener() {
    this.scroller.onScroll(() => {
      this.isScrolling = true
      // 滑动停止300ms后允许刷新
      setTimeout(() => {
        this.isScrolling = false
        if (!this.isScrolling) {
          this.syncVisibleAreaStatus()
        }
      }, 300)
    })
  }

  // 只同步可见区域的航标状态
  syncVisibleAreaStatus() {
    const visibleAreas = this.getVisibleAreas() // 获取当前可见的水域卡片
    visibleAreas.forEach(area => {
      this.loadBeaconRealTimeData(area.id)
    })
  }

  build() {
    Scroll(this.scroller) {
      Column() {
        Text(`实时更新:${this.currentTime}`).fontSize(12).padding(10)
        LazyForEach(this.areaDataSource, (area) => {
          Scroll({ direction: Axis.Horizontal }) {
            Row() {
              LazyForEach(new BeaconDataSource(area.beacons), (beacon) => {
                BeaconCard({ 
                  beacon: beacon,
                  onStatusChange: (newStatus) => this.updateBeaconStatus(beacon.id, newStatus),
                  visible: this.isAreaVisible(area.id) // 控制卡片渲染时机
                })
              })
            }.padding(15)
          }
          .border({ width: 1, color: '#e5e5e5' })
          .borderRadius(8)
          .margin(10)
          .onVisibleAreaChange((isVisible: boolean) => {
            if (isVisible) {
              this.loadAreaDetailData(area.id) // 进入视野时加载详细数据
            }
          })
        })
      }
    }
  }
}

 

三、最终结局-指尖的 "航标灯"

     最后在港航中心进行现场测试时,老调度员王师傅用布满老茧的手指划过屏幕,航标状态在他指尖流畅切换。“以前调个数据要等三秒,现在跟摸实体控制台一样顺手。” 他指着屏幕上跳动的绿灯说,“上次大风天天气,就是因为系统反应慢了半秒,差点让巡逻艇走错航道。这才是能救命的系统。”​站在一旁的我突然想起那个被 Scroll 组件困住的夜晚。原来开发里的 “小确幸” 从不是突然降临的,它藏在反复调试的代码里,躲在社区前辈的经验分享中,藏在用户每一次流畅的操作里。就像港口的航标灯,看似只是微弱的光芒,却能在黑夜中为航船指引方向。而我们写下的每一行代码,或许也在悄悄守护着某个不为人知的角落,在技术的海洋里亮起温暖的光。​

 

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐