鸿蒙跨端天气卡片:多设备实时同步天气信息组件 原创

进修的泡芙
发布于 2025-6-18 21:07
浏览
0收藏

鸿蒙跨端天气卡片:多设备实时同步天气信息组件

本文将基于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展示。

这种架构不仅适用于天气应用,也可以扩展到新闻推送等需要实时数据同步的场景。组件化的设计方式提高了代码的复用性,分布式能力则大大简化了多设备协同开发的复杂度。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2025-6-18 21:08:38修改
收藏
回复
举报
回复
    相关推荐