轻任务表面越小,用户对反馈的容忍度就越低。一个圆形入口可能只展示任务名称和进度,展开后的轻量窗口则需要暂停、继续、取消等操作。如果点击后界面没有变化,用户容易重复触发;如果进度条能够随意拖到100%,又会把操作意图误写成真正的任务完成情况。解决问题的关键不是增加动画,而是把“触控指令”“任务执行状态”“可视反馈”分成三条清晰的数据链。

前面已经搭建轻任务队列、窄窗与宽窗布局以及球窗切换状态管理。本篇在这些结构上补充任务操作层:同一个任务在不同表面上具有相同的操作结果,所有进度必须来自任务执行事件,界面只能显示或发送命令,不能直接篡改业务完成率。系统级闪控窗和闪控球的接入形式应按目标设备和公开能力确认;这里的交互层采用常规ArkUI组件和普通业务服务,可用于应用内轻任务界面。

触控操作与任务进度反馈封面

一、任务进度和触控反馈不是同一个字段

以“素材批量整理”为例,一项任务包含总文件数、已处理数、当前阶段和最近更新时间。计算进度应基于业务事实,而非控件位置。用户的点击会产生pause、resume、cancel等命令,命令提交后可以进入“处理中”反馈,但只有执行服务返回新状态,才更新任务阶段。这样即使窗口从紧凑入口展开,任务的完成率也不会突然跳变。

信息数据来源界面用途
已处理数量执行服务进度事件计算实际完成率
当前阶段服务状态机决定操作按钮是否可用
正在提交的命令交互控制层防止重复点击与显示忙碌状态
控件触摸状态ArkUI事件回调提供按压、拖动等即时反馈
最近事件版本任务仓库丢弃顺序落后的更新

如果一项任务包含多个阶段,应先明确进度的业务含义:是按文件计数,还是按权重计算。不要在上传阶段使用字节进度、在处理阶段直接重置为零,否则用户看到的整体进度会倒退。对于阶段权重,定义固定映射并在任务开始时确定,避免根据实时耗时任意调整权重。

二、使用可比较的状态与事件版本

阶段状态以显式枚举表示,指令以字符串联合或枚举限制可选范围。任务仓库负责状态演进的合法性,UI从仓库拿快照。对同一任务,每个有效服务事件携带递增版本,旧事件晚到时直接忽略。

export enum TaskPhase {
  QUEUED = 'queued',
  RUNNING = 'running',
  PAUSED = 'paused',
  DONE = 'done',
  FAILED = 'failed',
  CANCELLED = 'cancelled'
}

export type TaskCommand = 'pause' | 'resume' | 'cancel'

export interface TaskSnapshot {
  id: string
  label: string
  phase: TaskPhase
  processed: number
  total: number
  revision: number
}

export interface ProgressEvent {
  id: string
  processed: number
  total: number
  phase: TaskPhase
  revision: number
}

export function toPercent(task: TaskSnapshot): number {
  const total = Math.max(1, task.total)
  const done = Math.max(0, Math.min(task.processed, total))
  return Math.floor(done * 100 / total)
}

toPercent对异常分母做保护,且把已处理数量夹在合法范围内。这是视图层的数据保护,不意味着后端可以传入任意负数。仓库接收事件时还需校验任务存在、版本递增、数量非负与状态流转。例如已经取消的任务收到较旧的运行事件,不应回到运行中。

三、命令网关负责去重,而不是按钮自身

应用内多个任务表面可能同时存在。球态与窗态上的暂停按钮看起来不同,但必须走同一个命令网关。命令提交中将对应任务标记为忙碌,防止用户连续点击制造多个相同请求。服务确认后再清除忙碌标记;失败时恢复按钮并给出可理解的错误原因。

export interface TaskGateway {
  send(taskId: string, command: TaskCommand): Promise<void>
}

export class TaskCommandController {
  private pending: Set<string> = new Set<string>()
  constructor(private gateway: TaskGateway) {}

  isBusy(taskId: string): boolean {
    return this.pending.has(taskId)
  }

  async issue(taskId: string, command: TaskCommand): Promise<boolean> {
    if (this.pending.has(taskId)) {
      return false
    }
    this.pending.add(taskId)
    try {
      await this.gateway.send(taskId, command)
      return true
    } finally {
      this.pending.delete(taskId)
    }
  }
}

这里的Set完成进程内并发去重。如果网络重试或进程恢复可能再次提交命令,服务端还应采用稳定的请求幂等键,单纯禁用按钮不足以覆盖跨进程重复提交。界面里issue的返回值表示命令是否被成功提交,而不是表示任务已经暂停或取消;任务最终阶段仍以执行服务返回为准。

四、进度只读,触控负责发出意图

对于任务完成率,Progress比Slider更符合语义。前者显示执行服务的结果,后者允许用户调节一个值。若业务支持手动调整执行优先级,可以单独放置一个Slider表示优先级,例如设置0到100的调度权重;不要把它与完成率共用状态字段。华为ArkUI的Slider.onChange会报告SliderChangeMode,能够区分拖动中、结束和点击轨道。可以在拖动中更新草稿值,在结束或点击时提交配置,从而避免每个移动事件都发起服务请求。

@Component
struct TaskOperationCard {
  @Prop task: TaskSnapshot
  @State private priorityDraft: number = 50
  @State private isSending: boolean = false

  private commitPriority(value: number): void {
    // 接入应用的配置服务,优先级与任务完成率分别存储。
    console.info(`priority changed: ${Math.round(value)}`)
  }

  build() {
    Column({ space: 12 }) {
      Row() {
        Text(this.task.label).fontSize(18).fontWeight(FontWeight.Medium)
        Blank()
        Text(`${toPercent(this.task)}%`).fontSize(16)
      }
      Progress({ value: toPercent(this.task), total: 100, type: ProgressType.Linear })
        .width('100%')
      Text('执行优先级').fontSize(13).fontColor('#64748B')
      Slider({ value: this.priorityDraft, min: 0, max: 100, step: 5 })
        .onChange((value: number, mode: SliderChangeMode) => {
          this.priorityDraft = value
          if (mode === SliderChangeMode.End || mode === SliderChangeMode.Click) {
            this.commitPriority(value)
          }
        })
      Row({ space: 12 }) {
        Button('暂停').enabled(this.task.phase === TaskPhase.RUNNING && !this.isSending)
        Button('继续').enabled(this.task.phase === TaskPhase.PAUSED && !this.isSending)
      }
    }.padding(16).borderRadius(18).backgroundColor('#FFFFFF')
  }
}

组件保留了按钮可用性判定的位置,实际接入时让按钮调用上层注入的命令控制器,并通过同一个isSending状态刷新操作表面。ProgressType.Linear展示的百分比来自任务快照;即便用户拖动优先级滑块,也不会改变完成率。若优先级配置服务不能实时调整,则使用“保存设置”按钮确认提交,而非默认每次滑动结束都立即生效。

DevEco Studio中的触控与进度组件

五、危险操作需要明确的确认过程

轻量窗口上的取消按钮占用空间有限,但取消可能导致当前工作流程中止。可采用长按动作进入确认状态,也可以点击后打开明确的确认弹层。LongPressGesture允许设置触发时间。设计时不要将长按当成唯一入口,应同时考虑键盘、无障碍操作和用户对长按手势的不熟悉程度。

@Component
struct CancelTaskAction {
  @State private askConfirm: boolean = false

  build() {
    Column() {
      Button('取消当前任务')
        .backgroundColor('#FEE2E2')
        .fontColor('#991B1B')
        .onClick(() => { this.askConfirm = true })
        .gesture(LongPressGesture({ duration: 650 })
          .onAction(() => { this.askConfirm = true }))
      if (this.askConfirm) {
        Text('确定结束当前处理任务?').margin({ top: 12 })
        Row({ space: 12 }) {
          Button('返回').onClick(() => { this.askConfirm = false })
          Button('确认取消').onClick(() => {
            this.askConfirm = false
            // 通过统一命令网关提交取消请求。
          })
        }
      }
    }
  }
}

本段将确认过程保留在组件状态内,适合表达交互流程。需要与系统弹层保持一致时,可使用受支持的对话框组件,避免小面积弹层遮住关键进度信息。对于已经完成或已取消的任务,取消入口应变为禁用或转为“查看结果”,不能重复发送状态转换命令。

六、节流更新与前后台反馈

任务可能每几十毫秒发来一个计数事件。将所有事件逐条映射成组件树更新,会增加轻量窗口的刷新负担。仓库应完整接收业务事件、保留最新快照,视图层再按需要合并展示更新:只有进度整数百分比、阶段文字或重要提示改变时才触发对应UI刷新。这不是丢弃业务进度,而是将存储频率与绘制频率解耦。

如果界面暂时不可见,订阅端可以继续维护最新快照;重新打开时读取仓库当前值,而不是重放每一条旧事件制造跳动。球态可以只保留进度环、简短状态和展开入口,窗态展示文件数量、按钮与最近事件说明,两种表面从同一个快照派生输出。失败状态要体现可以重试的操作条件,网络异常时不要把“正在连接”误写为“已完成”。

轻量交互还应保留视觉以外的反馈:操作提交期间禁用重复按钮,任务完成时更新语义文字,重要失败提示使用可访问的文本,不把状态只编码为颜色。颜色、进度环与触控震动只能补充信息,不能替代明确状态。

轻任务触控与进度界面

七、按事件顺序排查问题

重复暂停。 先检查按钮状态,再看命令网关是否在请求开始前登记忙碌标记。若多个表面分别实例化网关,将产生各自的锁;此时需要把控制器提升到共享层或在服务端使用幂等键。

进度倒退。 核对服务事件的revision和processed;界面只接受版本更新的快照。对于任务进入新阶段时计数口径变化的情况,应调整进度计算模型,不要用Math.max掩盖业务语义错误。

滑块触发过量请求。 检查配置提交是否被放在Moving分支。如果只需要拖动结束后保存,可将请求移至End与Click,并保留草稿值显示,从而减少不必要的重复调用。

窗口重新展开后按钮状态错误。 检查是否把isSending作为局部组件字段,在销毁后丢失。提交状态和快照订阅应由共享控制器提供;重新展开时根据真实阶段与待处理命令推导按钮状态。

验收时可分别测试快速连点、长按后放弃、进度事件乱序、命令超时后重试、紧凑与展开表面切换,以及任务完成瞬间收到取消指令。关注状态收敛和界面可操作性,而不是用未经测量的响应时间数字替代判断。

这一篇将交互操作与执行进度分离:触控产生命令,服务更新任务,仓库协调事件,表面选择合适的反馈密度。后续跨页面状态同步只需要保持这一数据链唯一,便能减少球窗切换引发的误操作。

相关文档:Slider及交互状态、LongPressGesture、ArkUI开发框架。

Logo

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

更多推荐