Harmony os 卡片页面交互(postCardAction & FormLink)

卡片页面交互(postCardAction & FormLink)

鸿蒙第四期活动

1. 整体概念

在 HarmonyOS 里,服务卡片(Widget) 和「卡片提供方应用」(通常就是你的 App 模块)之间,可以做三类交互:

  1. 页面跳转:从卡片点进去进入应用页面
  2. 拉起进程:在后台启动应用做一些工作(不一定切到前台)
  3. 消息传递:卡片和 FormExtensionAbility 之间交换数据、刷新卡片

对应到 ArkTS API:

卡片类型 交互入口 支持事件类型
动态卡片 postCardAction() router / message / call
静态卡片 FormLink 组件 router / message / call 华为开发者+1

注意

  • postCardAction 只能在卡片页面中调用
  • FormLink 只能在静态卡片 中使用。CSDN博客+1

2. postCardAction 接口说明(动态卡片)

函数签名

复制代码
postCardAction(component: Object, action: Object): void
  • component:一般直接传 this(当前组件实例)
  • action:一个对象,描述这次交互的类型和参数 CSDN博客

action 常用字段

字段名 类型 说明
action string 必填,`'router'
bundleName string 可选,目标应用包名(一般省略,默认当前包)
moduleName string 可选,目标模块名
abilityName string 目标 UIAbility 名(router / call 常用)
uri string Deeplink 跳转用的 URI(router 专用,和 abilityName 同时写时优先 abilityName)
params object 自定义参数,对方通过 want.parameters.params 或 message 接收

call 特别规则

action: 'call' 时,params 必须 带一个 method: string 字段,用于指定 UIAbility 中要执行的方法名;同时 UIAbility 的 launchType 必须是 singleton,并且应用需要有 ohos.permission.KEEP_BACKGROUND_RUNNING 权限。CSDN博客+1


静态卡片不能直接绑定事件处理函数,它通过一个专门的容器组件 FormLink 来触发交互。华为开发者

基本用法:

复制代码
FormLink({
  action: 'router',           // 'router' / 'message' / 'call'
  abilityName: 'EntryAbility',
  params: { message: 'from static widget' }
}) {
  // 这里写卡片内部具体 UI
  Column() {
    Text('静态卡片入口')
  }
}

内部参数含义和 postCardActionaction 对象是一致的,只是写法从「函数」变成了「组件属性」。


4. 三种事件类型详解

4.1 router 事件(页面跳转)

作用:

  • 从卡片跳转到指定 UIAbility
  • 对于普通三方应用,只能跳到自己应用的 UIAbility (也就是"卡片提供方"应用)。Gitee+1

特点:

  • 只能在点击事件里触发(Button/Row 的 onClick 里去调)
  • 可以通过 params 携带数据到 UIAbility
  • UIAbility 在 onCreate / onNewWant 中通过 want.parameters 拿到这些参数博客园+1

常见场景:

  • 点击卡片「查看详情」,跳到应用详情页
  • 点击卡片某个 tab,直接跳到应用内部对应页面
  • 点击日历卡片某一天,跳到应用里的当天待办列表

4.2 message 事件(卡片 ↔ FormExtensionAbility)

作用:

  • FormExtensionAbility 发送一条自定义消息 ,会触发它的 onFormEvent(formId, message) 回调,用来做:
    • 卡片内容刷新
    • 简单业务操作(切换开关、点赞等)CSDN博客+1

特点:

  • 不会启动 UIAbility(不会拉起应用界面)
  • 适合「只改卡片、不跳转」的交互,如刷新、切换状态

4.3 call 事件(后台拉起 UIAbility)

作用:

  • 后台 启动指定 UIAbility 做一些耗时任务,比如:
    • 后台拉取数据
    • 后台执行某个算法
    • 后台更新卡片数据后再推送回来 Gitee+1

特点:

  • 不会把 UIAbility 调度到前台(用户看不到界面)
  • 需要 KEEP_BACKGROUND_RUNNING 权限
  • params.method 必须设置,UIAbility 内通过约定的方法名来执行对应逻辑

5. 综合案例:动态卡片 + router/message/call

下面做一个完整的小例子:

场景

做一个「学习打卡」卡片:

  • 显示今日学习时长
  • 按钮1:「打开详情」 → router,跳到 App 内 StudyDetail 页面
  • 按钮2:「刷新数据」 → message,触发 FormExtensionAbility 拉一次最新学习记录并更新卡片
  • 按钮3:「后台同步」 → call,在后台启动 UIAbility 做一次云同步

5.1 卡片页面 WidgetCard.ets(动态卡片)

ts 复制代码
// WidgetCard.ets(动态卡片示例)
// 注意:这里省略 import,一般创建 ArkTS 卡片工程后默认就能使用 postCardAction、LocalStorage 等。

let storage = new LocalStorage()

@Entry(storage)
@Component
struct WidgetCard {
  @LocalStorageProp('todayMinutes') todayMinutes: number = 0
  @LocalStorageProp('formId') formId: string = ''

  private readonly ACTION_ROUTER: string = 'router'
  private readonly ACTION_MESSAGE: string = 'message'
  private readonly ACTION_CALL: string = 'call'

  build() {
    Column() {
      // 标题 & 当天学习时长
      Column() {
        Text('今日学习')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .margin({ bottom: 4 })

        Text(`${this.todayMinutes} 分钟`)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
      }
      .margin({ bottom: 12 })

      // 按钮区域
      Row({ space: 8 }) {
        // 1. router:打开详情页
        Button('打开详情')
          .type(ButtonType.Capsule)
          .onClick(() => {
            postCardAction(this, {
              action: this.ACTION_ROUTER,
              // 一般只需 abilityName,默认当前 bundle/module
              abilityName: 'EntryAbility',
              params: {
                targetPage: 'studyDetail',  // 约定的目标页面标识
                from: 'widget',
                formId: this.formId
              }
            })
          })

        // 2. message:刷新卡片数据
        Button('刷新')
          .type(ButtonType.Capsule)
          .onClick(() => {
            postCardAction(this, {
              action: this.ACTION_MESSAGE,
              // 不写 abilityName 时,默认触发当前模块的 FormExtensionAbility.onFormEvent
              params: {
                op: 'refresh',
                time: Date.now()
              }
            })
          })
      }

      Row({ space: 8, margin: { top: 8 } }) {
        // 3. call:后台同步学习记录
        Button('后台同步')
          .type(ButtonType.Capsule)
          .onClick(() => {
            postCardAction(this, {
              action: this.ACTION_CALL,
              abilityName: 'EntryAbility',
              params: {
                method: 'syncStudyData',   // UIAbility 中约定的方法名
                formId: this.formId
              }
            })
          })
      }
    }
    .padding(12)
    .width('100%')
    .height('100%')
  }
}
  • @LocalStorageProp('formId') 对应的是 EntryFormAbility onAddForm 里写入的字段,用来区分是哪张卡片。CSDN博客+1

5.2 FormExtensionAbility:处理 message 事件 & 初始化数据

ts 复制代码
// EntryFormAbility.ts
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formBindingData from '@ohos.app.form.formBindingData';
import formInfo from '@ohos.app.form.formInfo';
import formProvider from '@ohos.app.form.formProvider';

export default class EntryFormAbility extends FormExtensionAbility {
  // 创建卡片时给它一份初始数据
  onAddForm(want): formBindingData.FormBindingData {
    const formId = want.parameters[formInfo.FormParam.IDENTITY_KEY] as string

    const data: Record<string, string> = {
      formId: formId,
      todayMinutes: '0'
    }
    return formBindingData.createFormBindingData(data)
  }

  // 处理 message 事件
  onFormEvent(formId: string, message: string) {
    // message 是一个 JSON 字符串
    let payload: { op?: string, [key: string]: unknown } = {}
    try {
      payload = JSON.parse(message)
    } catch (e) {
      console.error(`onFormEvent parse message error: ${JSON.stringify(e)}`)
    }

    if (payload.op === 'refresh') {
      // 这里可以从本地数据库 / 网络获取最新学习时长,这里举例写死
      const latestMinutes = 45

      const data: Record<string, string> = {
        formId: formId,
        todayMinutes: latestMinutes.toString()
      }
      const binding = formBindingData.createFormBindingData(data)
      formProvider.updateForm(formId, binding)
    }
  }
}
  • 当卡片点击「刷新」按钮时,触发 message 事件 → onFormEvent → 重新计算数据 → updateForm → 卡片 UI 自动更新。CSDN博客

5.3 EntryAbility:处理 router / call 事件

ts 复制代码
// EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import type Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import window from '@ohos.window';
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';

let currentTargetPage: string = 'index';
let cachedWindowStage: window.WindowStage | null = null;

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    this.handleRouterOrCall(want);
  }

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) {
    this.handleRouterOrCall(want);
    if (cachedWindowStage) {
      this.onWindowStageCreate(cachedWindowStage);
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    cachedWindowStage = windowStage;

    // 根据 currentTargetPage 决定加载哪个页面
    const page = currentTargetPage === 'studyDetail'
      ? 'pages/StudyDetail'
      : 'pages/Index';

    windowStage.loadContent(page);
  }

  private handleRouterOrCall(want: Want) {
    // 处理 router / call 传进来的 params
    if (want.parameters && want.parameters.params) {
      try {
        const params = JSON.parse(want.parameters.params as string);
        currentTargetPage = params.targetPage ?? currentTargetPage;

        // 如果是 call 事件发起的后台同步,也会走到这里
        if (params.method === 'syncStudyData') {
          this.doBackgroundSync(params.formId as string);
        }
      } catch (e) {
        console.error(`handleRouterOrCall parse params error: ${JSON.stringify(e)}`)
      }
    }
  }

  // call 事件:后台同步学习数据,并刷新卡片
  private doBackgroundSync(formId: string) {
    // 这里模拟:把 todayMinutes 同步为 60
    const data: Record<string, string> = {
      formId: formId,
      todayMinutes: '60'
    }
    const binding = formBindingData.createFormBindingData(data)
    formProvider.updateForm(formId, binding)
  }
}
  • router 事件:主要通过 params.targetPage 告诉 UIAbility 要显示哪个页面。
  • call 事件:通过 params.method 和其它参数,让 UIAbility 在后台执行逻辑(这里是 doBackgroundSync),完成后用 updateForm 刷新卡片内容。seaxiang.com+1

6. 静态卡片使用 router 的一个小例子(FormLink)

如果你有一个 静态卡片 ,只能用 FormLink 触发事件,例如:

ts 复制代码
// 静态卡片 WidgetCard.ets
@Entry
@Component
struct WidgetCard {
  readonly ACTION_TYPE: string = 'router'
  readonly ABILITY_NAME: string = 'EntryAbility'

  build() {
    FormLink({
      action: this.ACTION_TYPE,
      abilityName: this.ABILITY_NAME,
      params: {
        targetPage: 'studyDetail',
        from: 'staticWidget'
      }
    }) {
      // 卡片内部 UI
      Column() {
        Text('点我查看今日学习详情')
          .fontSize(18)
          .margin(12)
      }
    }
  }
}

UIAbility 侧的处理逻辑和前面 router 的例子是一模一样的,只是触发入口从 postCardAction 换成了 FormLinkharmonyosdev.csdn.net+1

相关推荐
HONG````4 小时前
鸿蒙List组件深度使用指南:从数据绑定到极致性能优化
list·harmonyos
SuperHeroWu74 小时前
鸿蒙应用实现横竖屏切换有几种方式?注意事项有什么?
华为·harmonyos·动态·横屏·竖屏·横竖屏·静态配置
长沙红胖子Qt4 小时前
VTK开发笔记(九):示例Cone6,使用3D交互控件,在Qt窗口中详解复现对应的Demo
3d·vtk·交互·qt三维开发
jackiendsc4 小时前
基于ESP32实现物联网远程可视化遥控小船的主要过程
物联网·esp32·鸿蒙·遥控
国服第二切图仔5 小时前
Electron for 鸿蒙PC项目实战案例之简单统计组件
javascript·electron·harmonyos
奔跑的露西ly5 小时前
【HarmonyOS NEXT】组件化与模块化的理解
华为·harmonyos
晚霞的不甘5 小时前
Flutter 与开源鸿蒙(OpenHarmony)深度集成:从插件开发到分布式能力实战(续篇)
flutter·开源·harmonyos
晚霞的不甘5 小时前
Flutter 与开源鸿蒙(OpenHarmony)生态融合:从 UI 渲染到系统级能力调用的全链路开发范式
flutter·开源·harmonyos
花先锋队长5 小时前
华为Mate X7:高级感,从何而来?
科技·华为·智能手机·harmonyos
IT从业者张某某5 小时前
DAY2-Open Harmony PC 命令行适配指南(Windows版)-Tree命令行工具下载篇
harmonyos