
鸿蒙跨端天气卡片:多设备实时同步天气信息组件 原创
鸿蒙跨端天气卡片:多设备实时同步天气信息组件
本文将基于HarmonyOS 5的ArkUI框架和分布式能力,实现一个支持多设备同步的动态天气卡片组件,能够在不同设备间实时同步天气数据并保持一致的UI展示。
技术架构
组件设计层:使用@Component构建可复用的天气卡片
状态管理层:通过@State和@Watch实现数据驱动UI更新
数据同步层:利用分布式数据管理实现多设备天气数据同步
UI动画层:添加天气状态变化的过渡动画
完整代码实现
天气数据模型定义
// model/WeatherData.ts
export class WeatherData {
city: string = ‘北京’; // 城市名称
temperature: number = 25; // 温度(℃)
weather: string = ‘晴’; // 天气状况
humidity: number = 60; // 湿度(%)
windSpeed: number = 10; // 风速(km/h)
airQuality: string = ‘良’; // 空气质量
updateTime: number = Date.now(); // 更新时间戳
deviceId: string = ‘’; // 最后更新的设备ID
// 天气类型对应的图标
get weatherIcon(): Resource {
switch (this.weather) {
case ‘晴’: return $r(‘app.media.ic_sunny’);
case ‘多云’: return $r(‘app.media.ic_cloudy’);
case ‘阴’: return $r(‘app.media.ic_overcast’);
case ‘雨’: return $r(‘app.media.ic_rain’);
case ‘雪’: return $r(‘app.media.ic_snow’);
case ‘雷阵雨’: return $r(‘app.media.ic_thunderstorm’);
default: return $r(‘app.media.ic_unknown’);
}
// 空气质量对应的颜色
get airQualityColor(): Color {
switch (this.airQuality) {
case ‘优’: return Color.Green;
case ‘良’: return Color.Blue;
case ‘轻度污染’: return Color.Orange;
case ‘中度污染’: return Color.Red;
case ‘重度污染’: return Color.Purple;
default: return Color.Gray;
}
分布式天气同步服务
// service/WeatherSyncService.ts
import distributedData from ‘@ohos.data.distributedData’;
import deviceInfo from ‘@ohos.deviceInfo’;
const STORE_ID = ‘weather_sync_store’;
const WEATHER_KEY = ‘current_weather’;
export class WeatherSyncService {
private kvManager: distributedData.KVManager;
private kvStore: distributedData.SingleKVStore;
private localDeviceId: string = deviceInfo.deviceId;
// 初始化分布式数据存储
async initialize() {
const config = {
bundleName: ‘com.example.weatherapp’,
userInfo: {
userId: ‘weather_user’,
userType: distributedData.UserType.SAME_USER_ID
};
this.kvManager = distributedData.createKVManager(config);
const options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedData.KVStoreType.SINGLE_VERSION
};
this.kvStore = await this.kvManager.getKVStore(STORE_ID, options);
// 订阅数据变更
this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
this.handleDataChange(data);
});
// 处理数据变更
private handleDataChange(data: distributedData.ChangeNotification) {
if (data.insertEntries.length > 0 && data.insertEntries[0].key === WEATHER_KEY) {
const newWeather = JSON.parse(data.insertEntries[0].value.value);
AppStorage.setOrCreate(‘currentWeather’, new WeatherData(newWeather));
}
// 同步天气数据到所有设备
async syncWeatherData(weather: WeatherData) {
weather.deviceId = this.localDeviceId;
await this.kvStore.put(WEATHER_KEY, JSON.stringify(weather));
// 获取当前设备ID
getLocalDeviceId(): string {
return this.localDeviceId;
}
天气卡片组件实现
// components/WeatherCard.ets
@Component
export struct WeatherCard {
@State private isExpanded: boolean = false;
@Link @Watch(‘onWeatherChange’) currentWeather: WeatherData;
private rotateAngle: number = 0;
// 天气变化时的处理
private onWeatherChange() {
// 触发刷新动画
this.rotateAngle = 360;
animateTo({ duration: 1000 }, () => {
this.rotateAngle = 0;
});
build() {
Column() {
// 城市和更新时间
Row() {
Text(this.currentWeather.city)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text(this.formatTime(this.currentWeather.updateTime))
.fontSize(12)
.fontColor('#888888')
.width(‘100%’)
.margin({ bottom: 12 })
// 主要天气信息
Row() {
// 天气图标
Image(this.currentWeather.weatherIcon)
.width(80)
.height(80)
.rotate({ angle: this.rotateAngle })
.margin({ right: 16 })
Column() {
// 温度
Row() {
Text(this.currentWeather.temperature.toString())
.fontSize(48)
.fontWeight(FontWeight.Bold)
Text('℃')
.fontSize(24)
.margin({ top: 12 })
// 天气状况
Text(this.currentWeather.weather)
.fontSize(18)
.margin({ top: 4 })
}
.width('100%')
.margin({ bottom: 16 })
// 扩展信息(点击展开)
if (this.isExpanded) {
Column() {
Divider()
.margin({ bottom: 12 })
// 湿度和风速
Row() {
WeatherDetailItem({
icon: $r('app.media.ic_humidity'),
label: '湿度',
value: ${this.currentWeather.humidity}%
})
WeatherDetailItem({
icon: $r('app.media.ic_wind'),
label: '风速',
value: ${this.currentWeather.windSpeed}km/h
})
.margin({ bottom: 12 })
// 空气质量
Row() {
Text('空气质量:')
.fontSize(14)
.fontColor('#666666')
Text(this.currentWeather.airQuality)
.fontSize(14)
.fontColor(this.currentWeather.airQualityColor)
.margin({ left: 8 })
// 更新设备信息
if (this.currentWeather.deviceId) {
Text(更新设备: ${this.getDeviceName(this.currentWeather.deviceId)})
.fontSize(12)
.fontColor('#888888')
.margin({ top: 8 })
}
.transition({ type: TransitionType.Insert, opacity: 0 })
.transition({ type: TransitionType.Delete, opacity: 0 })
// 展开/收起按钮
Row() {
Image($r('app.media.ic_more'))
.width(20)
.height(20)
.rotate({ angle: this.isExpanded ? 180 : 0 })
.margin({ right: 4 })
Text(this.isExpanded ? '收起' : '更多信息')
.fontSize(14)
.fontColor('#666666')
.margin({ top: 8 })
.onClick(() => {
animateTo({ duration: 300 }, () => {
this.isExpanded = !this.isExpanded;
});
})
.width(‘100%’)
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: '#20000000', offsetX: 0, offsetY: 2 })
// 格式化时间显示
private formatTime(timestamp: number): string {
const date = new Date(timestamp);
return {date.getHours().toString().padStart(2, ‘0’)}:{date.getMinutes().toString().padStart(2, ‘0’)} 更新;
// 获取设备名称(简化版)
private getDeviceName(deviceId: string): string {
return deviceId === this.syncService.getLocalDeviceId() ? ‘本设备’ : ‘其他设备’;
}
// 天气详情项组件
@Component
struct WeatherDetailItem {
private icon: Resource;
private label: string;
private value: string;
build() {
Row() {
Image(this.icon)
.width(16)
.height(16)
.margin({ right: 8 })
Column() {
Text(this.label)
.fontSize(12)
.fontColor('#888888')
Text(this.value)
.fontSize(16)
.fontColor('#333333')
}
.layoutWeight(1)
}
天气页面实现
// pages/WeatherPage.ets
import { WeatherSyncService } from ‘…/service/WeatherSyncService’;
import { WeatherCard } from ‘…/components/WeatherCard’;
@Entry
@Component
struct WeatherPage {
private syncService: WeatherSyncService = new WeatherSyncService();
@StorageLink(‘currentWeather’) currentWeather: WeatherData = new WeatherData();
@State isLoading: boolean = true;
@State cityInput: string = ‘’;
async aboutToAppear() {
await this.syncService.initialize();
// 初始加载天气数据
await this.loadWeatherData(this.currentWeather.city);
this.isLoading = false;
build() {
Column() {
// 城市搜索栏
Row() {
TextInput({ text: this.cityInput, placeholder: '输入城市名称' })
.layoutWeight(1)
.onSubmit((value: string) => {
if (value) {
this.loadWeatherData(value);
})
Button('搜索')
.margin({ left: 8 })
.onClick(() => {
if (this.cityInput) {
this.loadWeatherData(this.cityInput);
})
.width(‘90%’)
.margin({ top: 20, bottom: 16 })
// 加载状态
if (this.isLoading) {
LoadingProgress()
.width(50)
.height(50)
.margin({ top: 100 })
else {
// 天气卡片
WeatherCard({ currentWeather: $currentWeather })
.width('90%')
.margin({ bottom: 20 })
// 同步状态
Row() {
Circle()
.width(10)
.height(10)
.fill(this.syncService.isConnected ? '#4CAF50' : '#F44336')
.margin({ right: 5 })
Text(this.syncService.isConnected ? '已连接多设备' : '单机模式')
.fontSize(14)
.fontColor('#666666')
}
.width(‘100%’)
.height('100%')
// 加载天气数据
private async loadWeatherData(city: string) {
this.isLoading = true;
// 模拟网络请求获取天气数据
const mockWeatherData = await this.fetchWeatherFromAPI(city);
const weather = new WeatherData(mockWeatherData);
// 更新并同步数据
this.currentWeather = weather;
await this.syncService.syncWeatherData(weather);
this.isLoading = false;
// 模拟天气API请求
private async fetchWeatherFromAPI(city: string): Promise<any> {
// 实际项目中替换为真实API调用
return new Promise(resolve => {
setTimeout(() => {
const weatherTypes = [‘晴’, ‘多云’, ‘阴’, ‘雨’, ‘雪’];
const airQualities = [‘优’, ‘良’, ‘轻度污染’, ‘中度污染’, ‘重度污染’];
resolve({
city: city,
temperature: Math.floor(Math.random() * 30) + 10,
weather: weatherTypes[Math.floor(Math.random() * weatherTypes.length)],
humidity: Math.floor(Math.random() * 50) + 30,
windSpeed: Math.floor(Math.random() * 20) + 5,
airQuality: airQualities[Math.floor(Math.random() * airQualities.length)],
updateTime: Date.now()
});
}, 800);
});
}
实现原理详解
组件化设计:
使用@Component创建独立的天气卡片组件
通过@State和@Link管理组件内部和外部状态
封装天气图标、空气质量等子组件
数据同步机制:
主设备获取最新天气数据后同步到分布式数据库
其他设备通过dataChange事件监听更新
显示数据来源设备信息
动画效果:
天气变化时添加图标旋转动画
展开/收起详情时使用平滑过渡动画
使用animateTo控制动画过程
响应式UI:
根据天气类型显示不同图标
空气质量使用不同颜色标识
温度变化时自动更新显示
扩展功能建议
多城市管理:
// 添加多城市支持
async addFavoriteCity(city: string) {
await this.kvStore.put(city_${city}, JSON.stringify({
city,
lastUpdate: Date.now()
}));
天气预警通知:
// 检查极端天气条件
function checkWeatherAlert(weather: WeatherData) {
if (weather.weather = ‘暴雨’ || weather.airQuality = ‘重度污染’) {
showNotification(‘天气预警’, 当前{weather.city}有{weather.weather}天气,请注意安全);
}
天气趋势预测:
// 获取未来几天天气预报
async fetchWeatherForecast(city: string) {
const forecast = await weatherAPI.getForecast(city);
await this.syncService.syncForecastData(forecast);
总结
本文展示了如何利用ArkUI的组件化能力和HarmonyOS的分布式特性,构建一个支持多设备同步的动态天气卡片组件。通过合理的状态管理和数据同步机制,实现了跨设备的天气信息实时更新和一致的UI展示。
这种架构不仅适用于天气应用,也可以扩展到新闻推送等需要实时数据同步的场景。组件化的设计方式提高了代码的复用性,分布式能力则大大简化了多设备协同开发的复杂度。
