鸿蒙万能卡片开发实战:手把手实现一个天气服务卡片,支持多尺寸+动态刷新+跨设备
本文介绍了鸿蒙NEXT开发实战系列的第27篇,重点讲解如何开发一个天气服务卡片。主要内容包括:1) 卡片配置流程,通过form_config.json定义卡片属性和刷新规则;2) FormExtensionAbility生命周期管理;3) 多尺寸卡片适配(2×2和2×4);4) 数据模型与服务实现,包括天气数据获取和存储;5) 卡片UI开发,展示天气信息;6) 定时刷新机制,支持自动和手动刷新;7
📖 鸿蒙NEXT开发实战系列 | 第27篇 | 实战篇 🎯 适合人群:了解元服务基础的开发者 ⏰ 阅读时间:约20分钟 | 💻 开发环境:DevEco Studio 5.0+
上一篇:鸿蒙NEXT开发实战系列-26-实战篇-跨设备分布式数据同步实战 下一篇:鸿蒙NEXT开发实战系列-28-实战篇-新闻资讯卡片开发实战
前言
万能卡片是 HarmonyOS 最具特色的功能之一,它可以让用户在桌面上直接查看应用的关键信息,无需打开应用。今天我们来实战开发一个天气服务卡片,这个项目将带你掌握:
-
✅ 服务卡片的完整配置流程
-
✅ FormExtensionAbility 生命周期管理
-
✅ 多尺寸卡片适配(2×2 和 2×4)
-
✅ 定时刷新与数据绑定
-
✅ 跨设备数据同步
一、项目结构总览
1.1 创建元服务项目
首先在 DevEco Studio 中创建一个新的元服务项目,选择"Empty Ability"模板。
WeatherCard/
├── entry/
│ └── src/
│ └── main/
│ ├── ets/
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets # 入口Ability
│ │ ├── pages/
│ │ │ └── Index.ets # 主页面
│ │ ├── weathercard/
│ │ │ ├── WeatherCard2x2.ets # 2×2卡片
│ │ │ ├── WeatherCard2x4.ets # 2×4卡片
│ │ │ └── WeatherFormAbility.ets # FormExtensionAbility
│ │ ├── services/
│ │ │ └── WeatherService.ets # 天气数据服务
│ │ └── common/
│ │ └── WeatherData.ets # 数据模型
│ ├── resources/
│ │ └── base/
│ │ ├── profile/
│ │ │ ├── form_config.json # 卡片配置
│ │ │ └── weather_config.json # 天气配置
│ │ └── media/ # 图标资源
│ └── module.json5 # 模块配置
└── oh-package.json5
1.2 核心概念说明
在开始编码前,先了解几个核心概念:
|
概念 |
说明 |
|---|---|
|
FormExtensionAbility |
卡片扩展能力,管理卡片生命周期 |
|
form_config.json |
卡片配置文件,定义尺寸、刷新规则等 |
|
formBindingData |
卡片数据绑定,用于向卡片传递数据 |
|
Want |
用于启动卡片和传递参数 |
二、卡片配置详解
2.1 配置 form_config.json
在 resources/base/profile/ 目录下创建 form_config.json:
{
"forms": [
{
"name": "weather_2x2",
"description": "天气卡片 2×2",
"src": "./ets/weathercard/WeatherCard2x2.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"isDefault": true,
"colorMode": "auto",
"supportDimensions": ["2*2"],
"defaultDimension": "2*2",
"updateEnabled": true,
"scheduledUpdateTime": "30:00",
"updateDuration": 30,
"formConfigAbility": "pages/Index",
"metadata": {
"customData": "weather_2x2_custom_data"
}
},
{
"name": "weather_2x4",
"description": "天气卡片 2×4",
"src": "./ets/weathercard/WeatherCard2x4.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"isDefault": false,
"colorMode": "auto",
"supportDimensions": ["2*4"],
"defaultDimension": "2*4",
"updateEnabled": true,
"scheduledUpdateTime": "30:00",
"updateDuration": 30,
"formConfigAbility": "pages/Index",
"metadata": {
"customData": "weather_2x4_custom_data"
}
}
]
}
配置项说明:
-
name: 卡片名称,需要在代码中引用 -
src: 卡片 UI 页面路径 -
supportDimensions: 支持的尺寸,2*2表示 2 行 2 列 -
updateDuration: 刷新间隔,单位为分钟(30分钟) -
scheduledUpdateTime: 定时刷新时间点
2.2 配置 module.json5
在 module.json5 中注册 FormExtensionAbility:
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": ["phone", "tablet", "2in1"],
"deliveryWithInstall": true,
"installationFree": true,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}
]
},
{
"name": "WeatherFormAbility",
"srcEntry": "./ets/weathercard/WeatherFormAbility.ets",
"description": "天气服务卡片",
"icon": "$media:icon",
"label": "天气卡片",
"type": "form",
"metadata": [
{
"name": "ohos.extension.form",
"resource": "$profile:form_config"
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
三、数据模型与服务
3.1 定义天气数据模型
创建 ets/common/WeatherData.ets:
// 天气数据模型
export interface WeatherData {
// 城市名称
cityName: string
// 当前温度
temperature: number
// 天气状况
condition: string
// 天气图标
icon: string
// 湿度
humidity: number
// 风速
windSpeed: string
// 最高温
highTemp: number
// 最低温
lowTemp: number
// 更新时间
updateTime: string
// 未来几天预报
forecast: ForecastData[]
}
export interface ForecastData {
day: string
icon: string
highTemp: number
lowTemp: number
}
// 卡片数据结构
export interface CardData {
// 基础数据
title: string
detail: string
// 天气相关数据
cityName: string
temperature: string
condition: string
humidity: string
windSpeed: string
highTemp: string
lowTemp: string
updateTime: string
forecast: ForecastData[]
}
3.2 天气数据服务
创建 ets/services/WeatherService.ets:
import { WeatherData, ForecastData, CardData } from '../common/WeatherData'
import { preferences } from '@kit.ArkData'
import { distributedKVStore } from '@kit.DistributedService'
const STORE_NAME = 'WeatherStore'
const KEY_WEATHER_DATA = 'weather_data'
export class WeatherService {
private static instance: WeatherService | null = null
private kvStore: distributedKVStore.SingleKVStore | null = null
// 单例模式
static getInstance(): WeatherService {
if (!WeatherService.instance) {
WeatherService.instance = new WeatherService()
}
return WeatherService.instance
}
// 初始化分布式数据库
async initKVStore(context: Context): Promise<void> {
try {
const kvManager = distributedKVStore.createKVManager({
bundleName: 'com.example.weathercard',
context: context
})
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION
}
this.kvStore = await kvManager.getKVStore(STORE_NAME, options)
} catch (err) {
console.error(`Failed to create KVStore: ${err}`)
}
}
// 获取天气数据
async getWeatherData(): Promise<WeatherData> {
// 模拟获取天气数据
// 实际项目中应调用天气 API
const weatherData: WeatherData = {
cityName: '北京',
temperature: 25,
condition: '晴',
icon: 'sunny',
humidity: 60,
windSpeed: '3级',
highTemp: 28,
lowTemp: 18,
updateTime: this.formatTime(new Date()),
forecast: [
{ day: '明天', icon: 'cloudy', highTemp: 26, lowTemp: 17 },
{ day: '后天', icon: 'rainy', highTemp: 22, lowTemp: 15 },
{ day: '大后天', icon: 'sunny', highTemp: 27, lowTemp: 19 }
]
}
// 保存到本地存储
await this.saveWeatherData(weatherData)
return weatherData
}
// 保存天气数据
private async saveWeatherData(data: WeatherData): Promise<void> {
try {
// 保存到 Preferences
const context = getContext(this) as Context
const prefs = await preferences.getPreferences(context, 'weather_prefs')
await prefs.put(KEY_WEATHER_DATA, JSON.stringify(data))
await prefs.flush()
// 同步到分布式数据库
if (this.kvStore) {
await this.kvStore.put(KEY_WEATHER_DATA, JSON.stringify(data))
}
} catch (err) {
console.error(`Failed to save weather data: ${err}`)
}
}
// 获取卡片数据
getCardData(data: WeatherData, dimension: string): CardData {
if (dimension === '2*2') {
return {
title: data.cityName,
detail: `${data.temperature}° ${data.condition}`,
cityName: data.cityName,
temperature: `${data.temperature}°`,
condition: data.condition,
humidity: `${data.humidity}%`,
windSpeed: data.windSpeed,
highTemp: `${data.highTemp}°`,
lowTemp: `${data.lowTemp}°`,
updateTime: data.updateTime,
forecast: data.forecast
}
} else {
return {
title: `${data.cityName} ${data.temperature}°`,
detail: data.condition,
cityName: data.cityName,
temperature: `${data.temperature}°`,
condition: data.condition,
humidity: `${data.humidity}%`,
windSpeed: data.windSpeed,
highTemp: `${data.highTemp}°`,
lowTemp: `${data.lowTemp}°`,
updateTime: data.updateTime,
forecast: data.forecast
}
}
}
// 格式化时间
private formatTime(date: Date): string {
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
return `${hours}:${minutes}`
}
}
四、FormExtensionAbility 实现
4.1 创建卡片扩展能力
创建 ets/weathercard/WeatherFormAbility.ets:
import { formBindingData, formInfo, formProvider } from '@kit.FormKit'
import { Want } from '@kit.AbilityKit'
import { WeatherService } from '../services/WeatherService'
import { WeatherData, CardData } from '../common/WeatherData'
const TAG = 'WeatherFormAbility'
export default class WeatherFormAbility extends FormExtensionAbility {
// 卡片被创建时调用
onCreate(want: Want): formBindingData.FormBindingData {
console.info(TAG, 'Form onCreate')
const formId = want.parameters?.[formInfo.FormParam.IDENTITY_KEY] as string
const formName = want.parameters?.[formInfo.FormParam.NAME_KEY] as string
const dimension = want.parameters?.[formInfo.FormParam.DIMENSION_KEY] as number
console.info(TAG, `Form created: ${formId}, name: ${formName}, dimension: ${dimension}`)
// 返回初始数据
return this.getDefaultFormData(formName, dimension)
}
// 卡片被销毁时调用
onDestroy(formId: string): void {
console.info(TAG, `Form onDestroy: ${formId}`)
}
// 卡片可见时调用
onVisibilityChange(newStatus: Record<string, number>): void {
console.info(TAG, `Form visibility changed`)
}
// 卡片事件被触发时调用
onFormEvent(formId: string, message: string): void {
console.info(TAG, `Form event: ${formId}, message: ${message}`)
// 处理卡片事件,如点击刷新按钮
if (message === 'refresh') {
this.refreshFormData(formId)
}
}
// 卡片更新时调用
onUpdate(formId: string): void {
console.info(TAG, `Form onUpdate: ${formId}`)
this.refreshFormData(formId)
}
// 获取默认表单数据
private getDefaultFormData(formName: string, dimension: number): formBindingData.FormBindingData {
const dimStr = dimension === 1 ? '2*2' : '2*4'
const data: CardData = {
title: '加载中...',
detail: '获取天气信息',
cityName: '加载中',
temperature: '--°',
condition: '获取中',
humidity: '--%',
windSpeed: '--',
highTemp: '--°',
lowTemp: '--°',
updateTime: '--:--',
forecast: []
}
return formBindingData.createFormBindingData(data)
}
// 刷新表单数据
private async refreshFormData(formId: string): Promise<void> {
try {
const weatherService = WeatherService.getInstance()
const weatherData = await weatherService.getWeatherData()
const formInfoObj = await formProvider.getFormInfo(formId)
const dimension = formInfoObj?.formConfigProperties?.supportDimensions?.[0] || '2*2'
const cardData = weatherService.getCardData(weatherData, dimension)
await formProvider.updateForm(formId, {
data: JSON.stringify(cardData)
})
console.info(TAG, `Form updated: ${formId}`)
} catch (err) {
console.error(TAG, `Failed to update form: ${err}`)
}
}
}
五、卡片 UI 实现
5.1 2×2 天气卡片
创建 ets/weathercard/WeatherCard2x2.ets:
import { formBindingData } from '@kit.FormKit'
import { CardData } from '../common/WeatherData'
@Entry
@Component
struct WeatherCard2x2 {
@State cityName: string = '加载中'
@State temperature: string = '--°'
@State condition: string = '获取中'
@State humidity: string = '--%'
@State highTemp: string = '--°'
@State lowTemp: string = '--°'
@State updateTime: string = '--:--'
@State icon: string = '☀️'
aboutToAppear() {
// 监听卡片数据变化
formBindingData.onFormDataChanged(this.onDataChanged.bind(this))
}
onDataChanged(data: CardData) {
if (data) {
this.cityName = data.cityName || '未知'
this.temperature = data.temperature || '--°'
this.condition = data.condition || '未知'
this.humidity = data.humidity || '--%'
this.highTemp = data.highTemp || '--°'
this.lowTemp = data.lowTemp || '--°'
this.updateTime = data.updateTime || '--:--'
this.icon = this.getWeatherIcon(data.condition)
}
}
getWeatherIcon(condition: string): string {
switch (condition) {
case '晴': return '☀️'
case '多云': return '⛅'
case '阴': return '☁️'
case '雨': return '🌧️'
case '雪': return '❄️'
default: return '🌤️'
}
}
build() {
Column() {
// 城市名称和温度
Row() {
Text(this.cityName)
.fontSize(14)
.fontColor('#FFFFFF')
.opacity(0.9)
Blank()
Text(this.updateTime)
.fontSize(10)
.fontColor('#FFFFFF')
.opacity(0.7)
}
.width('100%')
.padding({ left: 12, right: 12, top: 12 })
// 主要温度显示
Row() {
Text(this.icon)
.fontSize(36)
Column() {
Text(this.temperature)
.fontSize(42)
.fontWeight(FontWeight.Light)
.fontColor('#FFFFFF')
Text(this.condition)
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.9)
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 8 })
}
.width('100%')
.padding({ left: 12, right: 12, top: 8 })
// 湿度和高低温
Row() {
Text(`💧 ${this.humidity}`)
.fontSize(11)
.fontColor('#FFFFFF')
.opacity(0.8)
Blank()
Text(`${this.highTemp}/${this.lowTemp}`)
.fontSize(11)
.fontColor('#FFFFFF')
.opacity(0.8)
}
.width('100%')
.padding({ left: 12, right: 12, bottom: 12 })
}
.width('100%')
.height('100%')
.backgroundColor('#4FC3F7')
.borderRadius(16)
.alignItems(HorizontalAlign.Start)
}
}
5.2 2×4 天气卡片
创建 ets/weathercard/WeatherCard2x4.ets:
import { formBindingData } from '@kit.FormKit'
import { CardData, ForecastData } from '../common/WeatherData'
@Entry
@Component
struct WeatherCard2x4 {
@State cityName: string = '加载中'
@State temperature: string = '--°'
@State condition: string = '获取中'
@State humidity: string = '--%'
@State windSpeed: string = '--'
@State highTemp: string = '--°'
@State lowTemp: string = '--°'
@State updateTime: string = '--:--'
@State icon: string = '☀️'
@State forecast: ForecastData[] = []
aboutToAppear() {
formBindingData.onFormDataChanged(this.onDataChanged.bind(this))
}
onDataChanged(data: CardData) {
if (data) {
this.cityName = data.cityName || '未知'
this.temperature = data.temperature || '--°'
this.condition = data.condition || '未知'
this.humidity = data.humidity || '--%'
this.windSpeed = data.windSpeed || '--'
this.highTemp = data.highTemp || '--°'
this.lowTemp = data.lowTemp || '--°'
this.updateTime = data.updateTime || '--:--'
this.icon = this.getWeatherIcon(data.condition)
this.forecast = data.forecast || []
}
}
getWeatherIcon(condition: string): string {
switch (condition) {
case '晴': return '☀️'
case '多云': return '⛅'
case '阴': return '☁️'
case '雨': return '🌧️'
case '雪': return '❄️'
default: return '🌤️'
}
}
getForecastIcon(condition: string): string {
switch (condition) {
case 'sunny': return '☀️'
case 'cloudy': return '⛅'
case 'rainy': return '🌧️'
default: return '🌤️'
}
}
@Builder
ForecastItem(item: ForecastData) {
Column() {
Text(item.day)
.fontSize(11)
.fontColor('#FFFFFF')
.opacity(0.9)
Text(this.getForecastIcon(item.icon))
.fontSize(20)
.margin({ top: 4, bottom: 4 })
Text(`${item.highTemp}°`)
.fontSize(11)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
Text(`${item.lowTemp}°`)
.fontSize(11)
.fontColor('#FFFFFF')
.opacity(0.7)
}
.width('33%')
.alignItems(HorizontalAlign.Center)
}
build() {
Column() {
// 顶部区域:城市和更新时间
Row() {
Text(this.cityName)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
Blank()
Text(`更新于 ${this.updateTime}`)
.fontSize(10)
.fontColor('#FFFFFF')
.opacity(0.7)
}
.width('100%')
.padding({ left: 16, right: 16, top: 16 })
// 中部区域:天气详情
Row() {
Column() {
Text(this.icon)
.fontSize(48)
Text(this.condition)
.fontSize(12)
.fontColor('#FFFFFF')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Center)
.width('30%')
Column() {
Text(this.temperature)
.fontSize(56)
.fontWeight(FontWeight.Light)
.fontColor('#FFFFFF')
Row() {
Text(`💧 ${this.humidity}`)
.fontSize(11)
.fontColor('#FFFFFF')
.opacity(0.8)
Text(' ')
Text(`💨 ${this.windSpeed}`)
.fontSize(11)
.fontColor('#FFFFFF')
.opacity(0.8)
}
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.width('40%')
Column() {
Text(`↑ ${this.highTemp}`)
.fontSize(12)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
Text(`↓ ${this.lowTemp}`)
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.8)
.margin({ top: 8 })
}
.width('30%')
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding({ left: 16, right: 16, top: 12 })
// 分隔线
Divider()
.color('#FFFFFF')
.opacity(0.3)
.margin({ left: 16, right: 16, top: 12 })
// 底部区域:未来天气预报
Row() {
ForEach(this.forecast, (item: ForecastData) => {
this.ForecastItem(item)
}, (item: ForecastData) => item.day)
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 16 })
}
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['#4FC3F7', 0.0], ['#29B6F6', 1.0]]
})
.borderRadius(16)
.alignItems(HorizontalAlign.Start)
}
}
六、主页面与卡片管理
6.1 入口页面
创建 ets/pages/Index.ets:
import { formProvider } from '@kit.FormKit'
import { abilityDelegatorRegistry } from '@kit.TestKit'
import { promptAction } from '@kit.ArkUI'
@Entry
@Component
struct Index {
@State message: string = '天气服务卡片示例'
build() {
Column() {
Text(this.message)
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
// 添加2×2卡片按钮
Button('添加 2×2 天气卡片')
.width('80%')
.height(50)
.fontSize(16)
.margin({ bottom: 16 })
.onClick(() => {
this.addForm('weather_2x2')
})
// 添加2×4卡片按钮
Button('添加 2×4 天气卡片')
.width('80%')
.height(50)
.fontSize(16)
.margin({ bottom: 16 })
.onClick(() => {
this.addForm('weather_2x4')
})
// 刷新所有卡片按钮
Button('刷新所有卡片')
.width('80%')
.height(50)
.fontSize(16)
.backgroundColor('#4FC3F7')
.onClick(() => {
this.refreshAllForms()
})
Text('提示:点击按钮添加天气卡片到桌面')
.fontSize(12)
.fontColor('#666666')
.margin({ top: 30 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('#F5F5F5')
}
// 添加表单卡片
async addForm(formName: string) {
try {
const formId = await formProvider.requestForm({
want: {
bundleName: 'com.example.weathercard',
abilityName: 'WeatherFormAbility',
parameters: {
'ohos.extra.param.key.form_name': formName
}
}
})
promptAction.showToast({ message: '卡片已添加到桌面' })
} catch (err) {
console.error(`Failed to add form: ${err}`)
promptAction.showToast({ message: '添加卡片失败' })
}
}
// 刷新所有表单
async refreshAllForms() {
try {
// 获取所有表单ID
const formIds = await formProvider.getAllFormsInfo()
for (const formInfo of formIds) {
await formProvider.updateForm(formInfo.formId, {
data: JSON.stringify({})
})
}
promptAction.showToast({ message: '卡片已刷新' })
} catch (err) {
console.error(`Failed to refresh forms: ${err}`)
promptAction.showToast({ message: '刷新失败' })
}
}
}
七、定时刷新机制
7.1 自动刷新配置
HarmonyOS 提供了两种定时刷新方式:
方式一:固定间隔刷新
在 form_config.json 中配置 updateDuration(单位:分钟):
{
"updateDuration": 30
}
方式二:指定时间点刷新
在 form_config.json 中配置 scheduledUpdateTime:
{
"scheduledUpdateTime": "08:00"
}
7.2 手动刷新实现
在卡片页面中添加刷新按钮:
// 在卡片 UI 中添加刷新按钮
Row() {
Text(`更新于 ${this.updateTime}`)
.fontSize(10)
.fontColor('#FFFFFF')
.opacity(0.7)
// 刷新按钮
Text('🔄')
.fontSize(14)
.onClick(() => {
formBindingData.sendFormMessage(this.formId, 'refresh')
})
}
7.3 后台定时任务
创建后台服务定期更新天气数据:
// 在 WeatherFormAbility 中添加定时任务
import { backgroundTaskManager } from '@kit.BackgroundTasksKit'
// 启动后台任务
async startBackgroundTask() {
const wantAgentInfo: backgroundTaskManager.WantAgentInfo = {
wants: [{
bundleName: 'com.example.weathercard',
abilityName: 'WeatherFormAbility'
}],
actionType: backgroundTaskManager.OperationType.START_SERVICE,
requestCode: 0,
actionFlags: [backgroundTaskManager.WantAgentFlags.UPDATE_PRESENT_FLAG]
}
const wantAgent = await wantAgent.getWantAgent(wantAgentInfo)
const delayTime = 30 * 60 * 1000 // 30分钟
await backgroundTaskManager.startBackgroundRunning(this.context, {
title: '天气数据更新',
wantAgent: wantAgent
})
}
八、跨设备数据同步
8.1 分布式数据同步
利用分布式 KVStore 实现跨设备数据同步:
import { distributedKVStore } from '@kit.DistributedService'
// 监听数据变化
kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
console.info(`Data changed: ${JSON.stringify(data)}`)
// 更新卡片数据
this.refreshFormData(data)
})
// 跨设备同步数据
async syncDataToOtherDevices(data: WeatherData) {
if (this.kvStore) {
await this.kvStore.put(KEY_WEATHER_DATA, JSON.stringify(data))
// 数据会自动同步到其他设备
}
}
8.2 设备信息获取
import { deviceManager } from '@kit.DistributedService'
// 获取当前设备信息
const deviceInfos = await deviceManager.getAvailableDeviceListSync()
for (const device of deviceInfos) {
console.info(`Device: ${device.deviceName}, ID: ${device.networkId}`)
}
九、常见问题与解决方案
9.1 卡片不显示
问题:添加卡片后桌面上没有显示
解决方案:
-
检查
module.json5中 FormExtensionAbility 配置是否正确 -
确认
form_config.json路径和配置是否正确 -
检查卡片页面路径是否正确
9.2 数据不更新
问题:卡片数据不刷新
解决方案:
-
确认
updateDuration或scheduledUpdateTime已配置 -
检查网络权限
ohos.permission.INTERNET是否添加 -
验证
formProvider.updateForm是否正确调用
9.3 多尺寸适配问题
问题:不同尺寸卡片显示异常
解决方案:
-
使用百分比布局而非固定尺寸
-
使用
dimension参数判断当前卡片尺寸 -
针对不同尺寸编写独立的 UI 组件
十、总结
通过本教程,你已经掌握了:
-
卡片配置:
form_config.json的完整配置 -
生命周期管理:FormExtensionAbility 的各个回调
-
多尺寸适配:2×2 和 2×4 两种尺寸的实现
-
数据绑定:使用
formBindingData传递数据 -
定时刷新:自动和手动两种刷新方式
-
跨设备同步:利用分布式 KVStore 同步数据
项目源码:完整的天气卡片项目代码已在文中给出,可直接复用到你的项目中。
下一篇预告:我们将开发一个新闻资讯卡片,实现图文混排、滑动列表等高级功能。
系列文章推荐
标签:服务卡片 天气卡片 FormKit 鸿蒙开发 万能卡片 ArkUI 跨设备 分布式
💡 提示:如果你在开发过程中遇到问题,欢迎在评论区留言交流!
更多推荐



所有评论(0)