news 2025/12/18 3:44:06

Harmony开发之服务卡片开发——解锁原子化服务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Harmony开发之服务卡片开发——解锁原子化服务

Harmony开发之服务卡片开发——解锁原子化服务

引入:桌面卡片的便捷交互

当我们使用手机时,经常会发现一些应用在桌面上提供了小巧精致的卡片,比如天气卡片显示实时温度、运动卡片展示今日步数、音乐卡片提供播放控制。这些就是HarmonyOS的服务卡片(Service Widget),它们无需打开完整应用就能提供核心信息并支持快捷操作,极大地提升了用户体验和操作效率。

一、服务卡片核心概念

1.1 什么是服务卡片?

服务卡片是HarmonyOS原子化服务的一种呈现形式,是界面展示的控件。它作为应用的重要入口,通过在外围提供快捷访问特定功能的能力,实现应用功能的原子化。

1.2 服务卡片的优势

  • 免安装使用:用户无需安装完整应用即可使用核心功能
  • 即用即走:轻量化设计,快速响应
  • 多设备协同:卡片可在手机、平板、手表等多设备间流转
  • 动态更新:支持定时更新或数据驱动更新

1.3 卡片类型与规格

HarmonyOS支持多种规格的服务卡片,常见的有2x2、2x4、4x4等不同尺寸,开发者需要根据功能需求选择合适的卡片规格。

二、卡片创建与配置

2.1 创建卡片工程

在DevEco Studio中创建服务卡片项目时,选择"Service Widget"模板:

// 文件:entry/src/main/ets/entryability/EntryAbility.ets import { UIAbility } from '@ohos.app.ability.UIAbility'; import { widgetManager } from '@ohos.app.ability.widgetManager'; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { console.info('EntryAbility onCreate'); } // 注册卡片更新回调 onAddForm(want: Want): FormBindingData { let formData: Record<string, Object> = { 'title': '健康步数', 'steps': '8,256', 'target': '10,000', 'progress': 82 }; return new FormBindingData(JSON.stringify(formData)); } }

2.2 卡片配置文件

// 文件:entry/src/main/resources/base/profile/form_config.json { "forms": [ { "name": "widget_steps", "description": "健康步数卡片", "src": "./ets/widgets/StepsWidget.ets", "window": { "designWidth": 720, "autoDesignWidth": true }, "colorMode": "auto", "isDefault": true, "updateEnabled": true, "scheduledUpdateTime": "10:00", "updateDuration": 1, "defaultDimension": "2 * 2", "supportDimensions": ["2 * 2", "2 * 4"] } ] } // 文件:entry/src/main/module.json5 { "module": { "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "formsEnabled": true, "forms": [ { "name": "widget_steps", "description": "健康步数卡片", "src": "./ets/widgets/StepsWidget.ets", "window": { "designWidth": 720, "autoDesignWidth": true }, "colorMode": "auto", "isDefault": true, "updateEnabled": true, "scheduledUpdateTime": "10:00", "updateDuration": 1, "defaultDimension": "2 * 2", "supportDimensions": ["2 * 2", "2 * 4"] } ] } ] } }

三、卡片布局开发

3.1 基础卡片组件

// 文件:entry/src/main/ets/widgets/StepsWidget.ets @Entry @Component struct StepsWidget { @State steps: string = '0' @State progress: number = 0 build() { Column() { // 标题栏 Row() { Image($r('app.media.ic_footprint')) .width(20) .height(20) .margin({ right: 8 }) Text('今日步数') .fontSize(16) .fontColor('#000000') .fontWeight(FontWeight.Medium) Blank() Image($r('app.media.ic_refresh')) .width(16) .height(16) .onClick(() => { this.updateStepsData() }) } .width('100%') .padding({ left: 12, right: 12, top: 8, bottom: 8 }) // 进度区域 Column() { Text(this.steps) .fontSize(24) .fontColor('#007DFF') .fontWeight(FontWeight.Bold) .margin({ bottom: 4 }) Text('目标: 10,000步') .fontSize(12) .fontColor('#99000000') .margin({ bottom: 8 }) // 进度条 Stack() { // 背景条 Row() .width('100%') .height(6) .backgroundColor('#20007DFF') .borderRadius(3) // 进度条 Row() .width(`${this.progress}%`) .height(6) .backgroundColor('#007DFF') .borderRadius(3) } .width('100%') .height(6) Text(`${this.progress}%完成`) .fontSize(10) .fontColor('#99000000') .margin({ top: 4 }) } .width('100%') .padding({ left: 12, right: 12, bottom: 12 }) } .width('100%') .height('100%') .backgroundColor('#FFFFFF') .borderRadius(12) } // 更新步数数据 updateStepsData() { // 模拟数据更新 const newSteps = Math.floor(Math.random() * 10000).toLocaleString() const newProgress = Math.floor(Math.random() * 100) this.steps = newSteps this.progress = newProgress } }

3.2 交互式卡片

// 文件:entry/src/main/ets/widgets/MusicWidget.ets @Entry @Component struct MusicWidget { @State isPlaying: boolean = false @State currentSong: string = 'HarmonyOS主题曲' @State artist: string = '华为音乐' @State progress: number = 40 build() { Column() { // 歌曲信息 Row() { Image($r('app.media.ic_music_cover')) .width(40) .height(40) .borderRadius(8) .margin({ right: 12 }) Column() { Text(this.currentSong) .fontSize(14) .fontColor('#000000') .fontWeight(FontWeight.Medium) .margin({ bottom: 2 }) Text(this.artist) .fontSize(12) .fontColor('#99000000') } .alignItems(HorizontalAlign.Start) Blank() } .width('100%') .padding({ left: 12, right: 12, top: 12 }) // 控制按钮 Row() { Image($r('app.media.ic_skip_previous')) .width(24) .height(24) .onClick(() => this.previousSong()) Blank() .width(20) Image(this.isPlaying ? $r('app.media.ic_pause') : $r('app.media.ic_play')) .width(32) .height(32) .onClick(() => this.togglePlay()) Blank() .width(20) Image($r('app.media.ic_skip_next')) .width(24) .height(24) .onClick(() => this.nextSong()) } .width('100%') .padding({ bottom: 12 }) .justifyContent(FlexAlign.Center) } .width('100%') .height('100%') .backgroundColor('#FFFFFF') .borderRadius(12) } togglePlay() { this.isPlaying = !this.isPlaying // 实际开发中这里应该控制音乐播放 } previousSong() { // 上一首逻辑 } nextSong() { // 下一首逻辑 } }

四、卡片更新机制

4.1 定时更新配置

{ "forms": [ { "name": "weather_widget", "updateEnabled": true, "scheduledUpdateTime": "08:00", "updateDuration": 1, "supportDimensions": ["2 * 2"] } ] }

4.2 手动更新实现

// 文件:entry/src/main/ets/utils/WidgetUtils.ets import { formHost } from '@ohos.app.ability.formHost'; export class WidgetUtils { // 更新指定卡片 static async updateWidget(formId: string): Promise<void> { try { const formBindingData = { 'temperature': '25°C', 'weather': '晴朗', 'location': '深圳市', 'updateTime': new Date().toLocaleTimeString() }; await formHost.updateForm(formId, { data: JSON.stringify(formBindingData) }); } catch (error) { console.error('更新卡片失败:', error); } } // 获取所有卡片ID static async getAllFormIds(): Promise<string[]> { try { const formInfos = await formHost.getAllFormsInfo(); return formInfos.map(info => info.formId); } catch (error) { console.error('获取卡片信息失败:', error); return []; } } }

4.3 数据驱动更新

// 文件:entry/src/main/ets/widgets/WeatherWidget.ets @Entry @Component struct WeatherWidget { @State temperature: string = '--' @State weather: string = '加载中...' @State updateTime: string = '' aboutToAppear() { this.fetchWeatherData() } build() { Column() { Text(this.temperature) .fontSize(28) .fontColor('#007DFF') .fontWeight(FontWeight.Bold) .margin({ bottom: 4 }) Text(this.weather) .fontSize(14) .fontColor('#666666') .margin({ bottom: 8 }) Text(`更新: ${this.updateTime}`) .fontSize(10) .fontColor('#999999') } .width('100%') .height('100%') .padding(12) .backgroundColor('#FFFFFF') .borderRadius(12) } async fetchWeatherData() { try { // 模拟API调用 const response = await this.mockWeatherAPI() this.temperature = response.temperature this.weather = response.weather this.updateTime = new Date().toLocaleTimeString() } catch (error) { console.error('获取天气数据失败:', error) } } async mockWeatherAPI() { return new Promise<{temperature: string, weather: string}>((resolve) => { setTimeout(() => { resolve({ temperature: '25°C', weather: '晴朗' }) }, 1000) }) } }

五、卡片跳转与交互

5.1 卡片跳转配置

// 文件:entry/src/main/ets/widgets/NewsWidget.ets @Entry @Component struct NewsWidget { @State newsItems: Array<{id: string, title: string, time: string}> = [ {id: '1', title: 'HarmonyOS 4.0发布新特性', time: '10:30'}, {id: '2', title: '开发者大会即将举行', time: '09:15'}, {id: '3', title: '新版本开发工具更新', time: '昨天'} ] build() { Column() { Text('最新资讯') .fontSize(16) .fontColor('#000000') .fontWeight(FontWeight.Medium) .margin({ bottom: 8, left: 12, top: 12 }) List({ space: 4 }) { ForEach(this.newsItems, (item: {id: string, title: string, time: string}) => { ListItem() { Row() { Column() { Text(item.title) .fontSize(12) .fontColor('#000000') .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text(item.time) .fontSize(10) .fontColor('#999999') } .alignItems(HorizontalAlign.Start) Blank() Image($r('app.media.ic_arrow_right')) .width(12) .height(12) } .width('100%') .padding({ left: 12, right: 12, top: 8, bottom: 8 }) .onClick(() => { this.navigateToDetail(item.id) }) } }, (item: {id: string, title: string, time: string}) => item.id) } .width('100%') .layoutWeight(1) } .width('100%') .height('100%') .backgroundColor('#FFFFFF') .borderRadius(12) } navigateToDetail(newsId: string) { // 跳转到详情页 let want = { deviceId: '', // 默认设备 bundleName: 'com.example.newsapp', abilityName: 'NewsDetailAbility', parameters: { newsId: newsId } }; // 使用featureAbility进行跳转 featureAbility.startAbility({ want: want }) .then(() => { console.info('跳转成功'); }) .catch((error) => { console.error('跳转失败:', error); }); } }

5.2 原子化服务配置

// 文件:entry/src/main/module.json5 { "module": { "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "formsEnabled": true, "forms": [ { "name": "news_widget", "description": "资讯卡片", "src": "./ets/widgets/NewsWidget.ets", "isDefault": true, "updateEnabled": true, "scheduledUpdateTime": "09:00", "defaultDimension": "2 * 4", "supportDimensions": ["2 * 4"] } ], "metadata": [ { "name": "ohos.extension.form", "resource": "$profile:form_config" } ] } ], "distro": { "deliveryWithInstall": true, "moduleName": "entry", "moduleType": "entry" }, "reqCapabilities": [] } }

六、多设备适配与流转

6.1 响应式布局设计

// 文件:entry/src/main/ets/widgets/UniversalWidget.ets @Entry @Component struct UniversalWidget { @State deviceType: string = 'phone' aboutToAppear() { this.detectDeviceType() } build() { Column() { if (this.deviceType === 'watch') { this.buildWatchLayout() } else if (this.deviceType === 'tablet') { this.buildTabletLayout() } else { this.buildPhoneLayout() } } .width('100%') .height('100%') .backgroundColor('#FFFFFF') .borderRadius(12) } @Builder buildPhoneLayout() { Column() { Text('手机版卡片') .fontSize(16) .fontColor('#000000') // 手机专用布局... } .padding(8) } @Builder buildTabletLayout() { Column() { Text('平板版卡片') .fontSize(20) .fontColor('#000000') // 平板专用布局... } .padding(16) } @Builder buildWatchLayout() { Column() { Text('手表版卡片') .fontSize(12) .fontColor('#000000') // 手表专用布局... } .padding(4) } detectDeviceType() { // 根据屏幕尺寸判断设备类型 const screenWidth = vp2px(display.getDefaultDisplaySync().width); if (screenWidth < 600) { this.deviceType = 'watch'; } else if (screenWidth < 1200) { this.deviceType = 'phone'; } else { this.deviceType = 'tablet'; } } }

6.2 卡片流转处理

// 文件:entry/src/main/ets/entryability/EntryAbility.ets import { distributedDeviceManager } from '@ohos.distributedDeviceManager'; export default class EntryAbility extends UIAbility { onConnect(want: Want): formBindingData.FormBindingData { // 处理卡片流转连接 console.info('卡片流转连接建立'); return this.handleFormRequest(want); } onDisconnect(want: Want): void { // 处理卡片流转断开 console.info('卡片流转连接断开'); } // 处理跨设备卡片请求 handleFormRequest(want: Want): formBindingData.FormBindingData { const deviceId = want.parameters?.deviceId as string; const formId = want.parameters?.formId as string; console.info(`处理来自设备 ${deviceId} 的卡片请求`); // 根据设备能力返回不同的卡片数据 return this.getAdaptiveFormData(deviceId); } getAdaptiveFormData(deviceId: string): formBindingData.FormBindingData { // 获取设备信息并返回适配的数据 const deviceInfo = this.getDeviceInfo(deviceId); let formData: Record<string, Object> = {}; if (deviceInfo.deviceType === 'watch') { formData = this.getWatchFormData(); } else { formData = this.getDefaultFormData(); } return new formBindingData.FormBindingData(JSON.stringify(formData)); } }

七、调试与优化

7.1 卡片调试技巧

// 文件:entry/src/main/ets/utils/DebugUtils.ets export class DebugUtils { // 卡片性能监控 static startPerformanceMonitor(formId: string): void { const startTime = new Date().getTime(); // 监控卡片加载性能 setTimeout(() => { const loadTime = new Date().getTime() - startTime; console.info(`卡片 ${formId} 加载耗时: ${loadTime}ms`); if (loadTime > 1000) { console.warn('卡片加载时间过长,建议优化'); } }, 0); } // 内存使用检查 static checkMemoryUsage(): void { const memoryInfo = process.getMemoryInfo(); console.info(`内存使用情况: ${JSON.stringify(memoryInfo)}`); if (memoryInfo.availMem < 100 * 1024 * 1024) { // 100MB console.warn('可用内存不足,可能影响卡片性能'); } } }

7.2 性能优化建议

  1. 图片资源优化:使用适当尺寸的图片,避免过大资源
  2. 数据缓存:合理使用缓存减少网络请求
  3. 布局简化:避免过于复杂的布局层次
  4. 按需更新:只在必要时更新卡片内容

总结

服务卡片是HarmonyOS原子化服务的核心载体,通过提供轻量级、即用即走的用户体验,极大地增强了应用的便捷性和实用性。本文从卡片创建、布局开发、更新机制、交互跳转到多设备适配等方面全面介绍了服务卡片的开发流程。

行动建议

  • 根据功能需求合理选择卡片尺寸和更新策略
  • 注重卡片的视觉设计和用户体验
  • 实现多设备适配,确保在不同设备上都有良好表现
  • 优化卡片性能,确保快速加载和流畅交互
  • 充分利用原子化服务的优势,提供免安装使用体验

通过精心设计的服务卡片,可以为用户提供更加便捷高效的服务入口,增强应用的用户粘性和使用价值。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!