HarmonyOS实战开发-如何实现一个简单的健康生活应用

发布于:2024-04-18 ⋅ 阅读:(32) ⋅ 点赞:(0)

功能概述

成就页面展示用户可以获取的所有勋章,当用户满足一定的条件时,将点亮本页面对应的勋章,没有得到的成就勋章处于熄灭状态。共有六种勋章,当用户连续完成任务打卡3天、7天、30天、50天、73天、99天时,可以获得对应的“连续xx天达成”勋章。

页面布局与 ArkTS 代码对应关系

效果如图所示:

标题部分TitleBar是一个横向容器Row里包含一个子组件Text。

// TitleBarComponent.ets
@Component
export struct TitleBar {
  build() {
    Row() {
      Text($r('app.string.achievement'))
        .fontSize($r('app.float.default_24'))
        .fontColor($r('app.color.white'))
        .align(Alignment.Start)
        .padding({left: Const.ACHIEVE_TITLE_BAR_LEFT,top: Const.ACHIEVE_TITLE_BAR_TOP})
    }
    .width(Const.FULL_WIDTH)
  }
}

每个勋章卡片BadgeCard是由一个竖向容器Column、一个图片子组件Image和一个文字子组件Text组成。

// BadgeCardComponent.ets
@Component
export struct BadgeCard {
  @Prop content: string = '';
  imgSrc: Resource = $r('app.string.empty');

  build() {
    Column({space: Const.DEFAULT_18}) {
      Image(this.imgSrc)
        .width(Const.FULL_WIDTH)
        .height(Const.ACHIEVE_CARD_IMG_HEIGHT)
        .objectFit(ImageFit.Contain)
      Text($r('app.string.task_achievement_level', Number(this.content)))
        .lineHeight($r('app.float.default_16'))
        .fontSize($r('app.float.default_12'))
        .fontColor($r('app.color.white'))
    }
    .width(ratio2percent(Const.ACHIEVE_SPLIT_RATIO))
    .padding({top: Const.ACHIEVE_CARD_TOP, bottom: Const.ACHIEVE_CARD_BOTTOM})
  }
}

整体的勋章面板使用Flex一个组件即可以实现均分和换行的功能。

// BadgePanelComponent.ets
@Component
export struct BadgePanel {
  @StorageProp(ACHIEVEMENT_LEVEL_KEY) successiveDays: number = 0;

  aboutToAppear() {
    Logger.debug('BadgePanel','aboutToAppear');
    getAchievementLevel();
  }

build() {
  Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
    ForEach(getBadgeCardItems(this.successiveDays), (item: CardInfo) => {
      BadgeCard({ content: item.titleContent, imgSrc: item.achievement})
    })
  }
  .width(Const.FULL_WIDTH)
  }
}

获取数据

进入界面第一次获取数据在aboutToAppear()声明周期中从数据库GlobalInfo表中获取存储的勋章数据, 通过@StorageProp装饰器刷新界面,其他的地方只要通过AppStorage更新勋章数据即可。

// BadgePanelComponent.ets
aboutToAppear() {
  Logger.debug('BadgePanel','aboutToAppear');
  getAchievementLevel();
}

// AchieveModel.ets
export function getAchievementLevel() {
  GlobalInfoApi.query((res: GlobalInfo) => {
    let globalInfo: GlobalInfo = res;
    let achievementStr = globalInfo.achievements??'';
    let achievements = achievementStr.split(',');
    if (achievements.length > 0) {
      AppStorage.Set<Number>(ACHIEVEMENT_LEVEL_KEY, Number(achievements[achievements.length - 1]));
    }
  })
}

// BadgePanelComponent.ets
@StorageProp(ACHIEVEMENT_LEVEL_KEY) successiveDays: number = 0;

ForEach(getBadgeCardItems(this.successiveDays), (item: CardInfo) => {
  BadgeCard({ content: item.titleContent, imgSrc: item.achievement})
})

// AchievementViewModel.ets
export function getBadgeCardItems(successiveDays: number): Array<CardInfo> {
  let badgeMileStones = ACHIEVEMENT_LEVEL_LIST;
  let cardItems: Array<CardInfo> = [];
  for (let i = 0; i < badgeMileStones.length; i++) {
    let onOrOff = successiveDays >= badgeMileStones[i] ? 'on' : 'off';
    let titleContent = String(badgeMileStones[i]);
    let cardInfo: CardInfo = new CardInfo();
    cardInfo.titleContent = titleContent;
    cardInfo.achievement = getAchievement(`${ onOrOff }_${ badgeMileStones[i] }`);
    cardItems.push(cardInfo);
  }
  return cardItems;
}

搭建关系型数据库

本节将介绍如何调用关系型数据库接口在本地搭建数据库,并读写相应的用户数据。

创建数据库

要使用关系型数据库存储用户数据,首先要进行数据库的创建,并提供基本的增、删、查、改接口。

导入关系型数据库模块:

import data_rdb from '@ohos.data.rdb';

关系型数据库提供以下两个基本功能:

获取RdbStore

首先要获取一个RdbStore来操作关系型数据库,代码如下:

// RdbHelperImp.ets
getRdb(context: Context): Promise<RdbHelper> {
  this.storeConfig = {
    name: this.mDatabaseName, securityLevel: dataRdb.SecurityLevel.S1
  };
  return new Promise<RdbHelper>((success, error) => {
    dataRdb.getRdbStore(context, this.storeConfig).then(dbStore => {
      this.rdbStore = dbStore;
      success(this);
    }).catch((err: Error) => {
      Logger.error(`initRdb err : ${JSON.stringify(err)}`);
      error(err);
    })
  })
}

封装增、删、改、查接口

关系型数据库接口提供的增、删、改、查操作均有callback和Promise两种异步回调方式,本Codelab使用了callback异步回调,其中插入数据使用了insert()接口,实现代码如下:

// RdbHelperImp.ets
insert(tableName: string, values: dataRdb.ValuesBucket | Array<dataRdb.ValuesBucket>): Promise<number> {
  return new Promise<number>((success, error) => {
    Logger.info(`insert tableName : ${tableName}, values : ${JSON.stringify(values)}`);
    if (!values) {
      Logger.info(`insert failed, values is undefined`);
      error(0);
      return;
    }
    if (values instanceof Array) {
      Logger.info(`insert values isArray = ${values.length}`);
      this.rdbStore.beginTransaction();
      this.saveArray(tableName, values).then(data => {
        Logger.info(`insert success, data : ${JSON.stringify(data)}`);
        success(data);
        this.rdbStore.commit();
      }).catch((err: Error) => {
        Logger.error(`insert failed, err : ${err}`);
        error(err);
        this.rdbStore.commit();
      })
    } else {
      this.rdbStore.insert(tableName, values).then(data => {
        Logger.info(`insert success id : ${data}`);
        success(data);
        his.rdbStore.commit();
      }).catch((err: Error) => {
        Logger.error(`insert failed, err : ${JSON.stringify(err)}`);
        error(err);
        this.rdbStore.commit();
      })
    }
  })
}

删除数据使用了delete()接口,实现代码如下:

// RdbHelperImp.ets
delete(rdbPredicates: dataRdb.RdbPredicates): Promise<number> {
  Logger.info(`delete rdbPredicates : ${JSON.stringify(rdbPredicates)}`);
  return this.rdbStore.delete(rdbPredicates);
}

更新数据使用了update()接口,实现代码如下:

// RdbHelperImp.ets
update(values: dataRdb.ValuesBucket, rdbPredicates: dataRdb.RdbPredicates): Promise<number> {
  return this.rdbStore.update(values, rdbPredicates);
}

查找数据使用了query()接口,实现代码如下:

// RdbHelperImp.ets
query(rdbPredicates: dataRdb.RdbPredicates, columns?: Array<string>): Promise<dataRdb.ResultSet> {
  Logger.info(`query rdbPredicates : ${JSON.stringify(rdbPredicates)}`);
  return this.rdbStore.query(rdbPredicates, columns);
}

数据库表结构

根据健康生活APP的使用场景和业务逻辑,定义了三个数据对象,并使用三张数据表来存储,分别是健康任务信息表、每日信息表和全局信息表。

健康任务信息表

目前健康生活应用提供了6个基本的健康任务,分别是早起、喝水、吃苹果、每日微笑、睡前刷牙和早睡。用户可以选择开启或关闭某个任务,开启的任务可以选择是否开启提醒,在指定的时间段内提醒用户进行打卡。任务也可以选择开启的频率,如只在周一到周五开启等。需要记录每项任务的目标值和实际完成值,在用户打卡后判断任务是否已经完成,并记录在数据库中。因此,需要创建一张存储每天的健康任务信息的表,表头如下:

每日信息表

在主页面,用户可以查看当天健康任务的完成进度,需要创建一张表记录当天开启的任务个数和已经完成的任务个数,表头如下:

全局信息表

用户连续多日打卡完成所有创建的任务可以获得相应的成就,因此,需要有一张表记录连续打卡天数和已达成的成就项。另外,考虑应用多日未打开的情况,需要记录应用第一次打开的日期和最后一次打开的日期以向数据库回填数据,表头如下:

创建数据表

根据6.2中设计的表结构,创建对应的数据表,实现对相应数据的读写操作。

健康任务信息数据表

在获取RdbStore后,需要使用executeSql接口执行SQL语句来创建相应的表结构和初始化数据,SQL语句如下:

CREATE TABLE IF NOT EXISTS taskInfo(
  id INTEGER PRIMARY KEY AUTOINCREMENT, 
  date TEXT NOT NULL, 
  taskID INTEGER, 
  targetValue TEXT NOT NULL, 
  isAlarm BOOLEAN, 
  startTime TEXT NOT NULL, 
  endTime TEXT NOT NULL, 
  frequency TEXT NOT NULL, 
  isDone BOOLEAN, 
  finValue TEXT NOT NULL, 
  isOpen BOOLEAN
)

健康任务信息数据表需要提供插入数据的接口,以在用户当天第一次打开应用时创建当天的健康任务信息,实现代码如下:

// TaskInfoApi.ets
insertData(taskInfo: TaskInfo, callback: Function): void {
  // 根据输入数据创建待插入的数据行
  const valueBucket = generateBucket(taskInfo);
  RdbUtils.insert('taskInfo', valueBucket).then(result => {
    callback(result);
  });
  Logger.info('TaskInfoTable', `Insert taskInfo {${taskInfo.date}:${taskInfo.taskID}} finished.`);
}

其中generateBucket()代码如下:

// TaskInfoApi.ets
function generateBucket(taskInfo: TaskInfo): dataRdb.ValuesBucket {
  let valueBucket = {} as dataRdb.ValuesBucket;
  Const.TASK_INFO.columns?.forEach((item: string) => {
    if (item !== 'id') {
      switch (item) {
        case 'date':
          valueBucket[item] = taskInfo.date;
          break;
        case 'taskID':
          valueBucket[item] = taskInfo.taskID;
          break;
        case 'targetValue':
          valueBucket[item] = taskInfo.targetValue;
          break;
        case 'isAlarm':
          valueBucket[item] = taskInfo.isAlarm;
          break;
        case 'startTime':
          valueBucket[item] = taskInfo.startTime;
          break;
        case 'endTime':
          valueBucket[item] = taskInfo.endTime;
          break;
        case 'frequency':
          valueBucket[item] = taskInfo.frequency;
          break;
        case 'isDone':
          valueBucket[item] = taskInfo.isDone;
          break;
        case 'finValue':
          valueBucket[item] = taskInfo.finValue;
          break;
        case 'isOpen':
          valueBucket[item] = taskInfo.isOpen;
          break;
        default:
          break;
      }
    }
  });
  return valueBucket;
}

用户开启和关闭任务,改变任务的目标值、提醒时间、频率等,用户打卡后修改任务的实际完成值都是通过更新数据接口来实现的,代码如下:

// TaskInfoApi.ets
updateDataByDate(taskInfo: TaskInfo, callback: Function): void {
  const valueBucket = generateBucket(taskInfo);
  let tableName = Const.TASK_INFO.tableName;
  if (!tableName) {
    return;
  }
  let predicates = new dataRdb.RdbPredicates(tableName);
  // 根据date和taskID匹配要更新的数据行
  predicates.equalTo('date', taskInfo.date).and().equalTo('taskID', taskInfo.taskID);
  RdbUtils.update(valueBucket, predicates).then((result: number) => {
    callback(result);
  });
  Logger.info('TaskInfoTable', `Update data {${taskInfo.date}:${taskInfo.taskID}} finished.`);
}

用户可以查看当天和以前某日的健康任务信息,需要提供查找数据接口,实现代码如下:

// TaskInfoApi.ets
query(date: string, isOpen: boolean = true, callback: Function): void {
  let tableName = Const.TASK_INFO.tableName;
  if (!tableName) {
    return;
  }
  let predicates = new dataRdb.RdbPredicates(tableName);
  predicates.equalTo('date', date);
  // 如果isOpen为true,则只查找开启的任务 
  if (isOpen) {
    predicates.equalTo('isOpen', true);
  }
  predicates.orderByAsc('taskID');  // 查找结果按taskID排序
  RdbUtils.query(predicates).then(resultSet => {
    let count = resultSet.rowCount;
    // 查找结果为空则返回空数组,否则返回查找结果数组
    if (count === 0 || typeof count === 'string') {
      Logger.error('TaskInfoTable', `${date} query no results!`);
      const result: TaskInfo[] = [];
      callback(result);
    } else {
      resultSet.goToFirstRow();
      const result: TaskInfo[] = [];
      for (let i = 0; i < count; i++) {
        let tmp = new TaskInfo(0, '', 0, '', false, '', '', '', false, '');
        tmp.isOpen = resultSet.getDouble(resultSet.getColumnIndex('isOpen')) ? true : false;
        tmp.id = resultSet.getDouble(resultSet.getColumnIndex('id'));
        tmp.date = resultSet.getString(resultSet.getColumnIndex('date'));
        tmp.taskID = resultSet.getDouble(resultSet.getColumnIndex('taskID'));
        tmp.targetValue = resultSet.getString(resultSet.getColumnIndex('targetValue'));
        tmp.isAlarm = resultSet.getDouble(resultSet.getColumnIndex('isAlarm')) ? true : false;
        tmp.startTime = resultSet.getString(resultSet.getColumnIndex('startTime'));
        tmp.endTime = resultSet.getString(resultSet.getColumnIndex('endTime'));
        tmp.frequency = resultSet.getString(resultSet.getColumnIndex('frequency'));
        tmp.isDone = resultSet.getDouble(resultSet.getColumnIndex('isDone')) ? true : false;
        tmp.finValue = resultSet.getString(resultSet.getColumnIndex('finValue'));
        result[i] = tmp;
        resultSet.goToNextRow();
      }
      callback(result);
    }
  });
}

每日信息数据表

创建每日信息数据表的SQL语句如下:

CREATE TABLE IF NOT EXISTS dayInfo(
  date TEXT NOT NULL PRIMARY KEY, 
  targetTaskNum INTEGER, 
  finTaskNum INTEGER
)

在当天第一次打开应用时需要初始化每日信息数据,页面需要根据用户编辑任务和打卡的情况来更新当天目标任务个数和完成任务个数,所以需要提供插入数据和更新数据的接口,写法与上一条中相应接口类似,不再赘述。

页面需要查找对应日期的目标任务个数和完成任务个数用以在页面显示任务进度,因此需要查找数据的接口。且页面在打开时需要显示当周每天任务的完成情况,因此需要允许一次调用查找一周的每日任务信息。实现代码如下:

// DayInfoApi.ets
queryList(dates: string[], callback: Function): void {
  let predicates: dataRdb.RdbPredicates = new dataRdb.RdbPredicates(Const.DAY_INFO.tableName ? Const.DAY_INFO.tableName : '');
  predicates.in('date', dates);  // 匹配日期数组内的所有日期
  RdbUtils.query(predicates).then(resultSet => {
    let count = resultSet.rowCount;
    if (count === 0) {
      Logger.info('DayInfoTable', 'query no results.');
      let result: DayInfo[] = [];
      callback(result);
    } else {
      resultSet.goToFirstRow();
      let result: DayInfo[] = [];
      for (let i = 0; i < count; i++) {
        let tmp = new DayInfo('', 0, 0);
        tmp.date = resultSet.getString(resultSet.getColumnIndex('date'));
        tmp.targetTaskNum = resultSet.getDouble(resultSet.getColumnIndex('targetTaskNum'));
        tmp.finTaskNum = resultSet.getDouble(resultSet.getColumnIndex('finTaskNum'));
        result[i] = tmp;
        resultSet.goToNextRow();
      }
      callback(result);
    }
  });
}

全局信息数据表

创建全局信息数据表的SQL语句如下:

CREATE TABLE IF NOT EXISTS globalInfo(
  id INTEGER PRIMARY KEY, 
  firstDate TEXT NOT NULL, 
  lastDate TEXT NOT NULL, 
  checkInDays INTEGER, achievements TEXT NOT NULL
)

全局信息数据表同样需要提供插入数据、更新数据和查找数据的接口,写法与本节前两条中相应接口类似,不再赘述。

数据库初始化

应用首次打开时,数据库中没有数据,要做数据库的初始化,写入一组空数据。另外,如果用户连续几天没有打开APP,再次打开时需要将数据回写至数据库。因此需要实现一个数据库接口,在应用打开时调用,进行上述操作。代码如下:

// DatabaseModel.ets
query(date: string, callback: Function) {
  let result: TaskInfo[] = [];
  let self = this;
  GlobalInfoApi.query((globalResult: GlobalInfo) => {
    if (!globalResult.firstDate) { // 如果找不到全局信息,则写入
      let globalInfo: GlobalInfo = new GlobalInfo(date, date, 0, '');
      GlobalInfoApi.insertData(globalInfo, (isDone: number) => {
        if (isDone) {
          Logger.info('AppStart', 'Insert globalInfo success: ' + JSON.stringify(globalInfo));
        }
      });
      self.insertGlobalTask();
      let dayInfo: DayInfo = new DayInfo(date, 0, 0);
      DayInfoApi.insertData(dayInfo, (isDone: number) => {
        if (isDone) {
          Logger.info('AppStart', 'Insert dayInfo success: ' + JSON.stringify(dayInfo));
        }
      })
      self.insertTask(date);
      callback(result, dayInfo);
    } else { // 如果找到全局信息,则查询当天的任务信息
      let newGlobalInfo = globalResult;
      let preDate = globalResult.lastDate;
      newGlobalInfo.lastDate = date;
      GlobalInfoApi.updateData(newGlobalInfo, (isDone: number) => {
        if (isDone) {
          Logger.info('AppStart', 'update globalInfo success: ' + JSON.stringify(newGlobalInfo));
        }
      });
      self.queryPreInfo(date, preDate, result, callback);
    }
  });
}

编写通用工具类

本节将介绍日志打印、时间换算等通用工具类的编写和使用,工具类可以简化应用代码编写和业务流程处理。

日志类

日志类Logger旨在提供一个全局的日志打印、日志管理的地方,既可以规范整个应用的日志打印,也方便日后对日志工具类进行修改,而不需要去改动代码中每一个调用日志的地方,如切换具体的日志实现类(比如不使用Console而是HiLog),将日志记录到本地文件等。

Logger对外的日志API全部使用静态方法,方便调用者使用,目前分verbose,debug,info,warn,error五个级别。

使用方法如下:

  1. import Logger日志类:
import { Logger } from '../utils/log/Logger';

2.调用对应级别的静态方法:

Logger.debug('MyAbilityStage', 'onCreate');

3.Logger类中包括debug、info、warn、error,具体内容如下:

// Logger.ets  
import hilog from '@ohos.hilog';

const LOGGER_PREFIX: string = 'Healthy_life';

class Logger {
  private domain: number;
  private prefix: string;

  ...
  constructor(prefix: string = '', domain: number = 0xFF00) {
    this.prefix = prefix;
    this.domain = domain;
  }

  debug(...args: string[]): void {
    hilog.debug(this.domain, this.prefix, this.format, args);
  }

  info(...args: string[]): void {
    hilog.info(this.domain, this.prefix, this.format, args);
  }

  warn(...args: string[]): void {
    hilog.warn(this.domain, this.prefix, this.format, args);
  }

  error(...args: string[]): void {
    hilog.error(this.domain, this.prefix, this.format, args);
  }
}

export default new Logger(LOGGER_PREFIX, 0xFF02);

时间工具

为全局提供时间工具,避免重复定义。

  1. 常用时间相关常量:
  // Utils.ets
  const CHINESE_OF_WEEK: string[] = ['一', '二', '三', '四', '五', '六', '日'];
  const YEAR: string = '年';
  const MONTH: string = '月';
  const DAY: string = '日';
  const WEEK: string = '星期';
  DAYS_OF_WEEK: number = 7;
  const SUNDAY_FIRST_SHIFT: number = 6;
  1. 时间函数示例(由时间常量衍生出星期一到星期日和数字 1-7 的字典映射):
// Utils.ets
export const oneWeekDictFunc = () => {
  const oneWeekDict: Array<string> = [];
  for (let index = 0;index < CHINESE_OF_WEEK.length; index++) {
    oneWeekDict[index] = `${WEEK}${CHINESE_OF_WEEK[index]}`;
  }
  return oneWeekDict;
}

单位转换工具

把比例等分浮点数转换为百分比字符串。

例如成就页面,每一行平均分布三个徽章,可以先定义一个浮点数代表等分比例,再转换为百分比字符串。

// Utils.ets
export function ratio2percent(ratio: number): string {
  return `${ratio * 100}%`;
}

使用方法如下:

  1. import 工具方法:
import { ratio2percent } from '../common/utils/Utils'

2.引用工具方法 ( 例如成就页面,每个徽章占据屏幕宽度的三分之一 ) :

// BadgeCardComponent.ets
Column({space: commonConst.DEFAULT_18}) { 
  ...  // 省略徽章卡片的 UI 布局细节
}
.width(ratio2percent(Const.ACHIEVE_SPLIT_RATIO)) // achieveConst.ACHIEVE_SPLIT_RATIO = 1 / 3

事件分发类

事件分发类提供应用全局的事件注册,分发,接受,可以实现组件之间的解耦。

事件分发类全局共享一个实例, 将事件处理统一管理(HealthDataSrcMgr是单例):

获取事件分发实例

// HomeComponent.ets
@Provide broadCast: BroadCast = HealthDataSrcMgr.getInstance().getBroadCast();

// HealthDataSrcMgr.ets
public getBroadCast(): BroadCast {
  return this.broadCast;
}

事件注册:

// CustomDialogView.ets
aboutToAppear() {
  Logger.debug('CustomDialogView', 'aboutToAppear'); 
  // 成就对话
  this.broadCast.on(BroadCastType.SHOW_ACHIEVEMENT_DIALOG, (achievementLevel: number) => {
    Logger.debug('CustomDialogView', 'SHOW_ACHIEVEMENT_DIALOG');
    this.achievementLevel = achievementLevel;
    this.achievementDialog.open();
  });

  // 任务时钟对话框
  this.broadCast.on(BroadCastType.SHOW_TASK_DETAIL_DIALOG,
    (currentTask: TaskInfo, dialogCallBack: CustomDialogCallback) => {
      Logger.debug('CustomDialogView', 'SHOW_TASK_DETAIL_DIALOG');
      this.currentTask = currentTask || TaskItem;
      this.dialogCallBack = dialogCallBack;
      this.taskDialog.open();
    });
}
  
// BroadCast.ets
public on(event: string, callback: Function) {
  Logger.info(FILE_TAG, 'register broadcast with type '+ event);
  switch (event) {
    case BroadCastType.SHOW_ACHIEVEMENT_DIALOG:
      this.callBackArray.showAchievementDialog = callback;
      break;
    case BroadCastType.SHOW_TASK_DETAIL_DIALOG:
      this.callBackArray.showTaskDetailDialog = callback;
      break;
    case BroadCastType.SHOW_TARGET_SETTING_DIALOG:
      this.callBackArray.showTargetSettingDialog = callback;
      break;
    case BroadCastType.SHOW_REMIND_TIME_DIALOG:
      this.callBackArray.showRemindTimeDialog = callback;
      break;
    case BroadCastType.SHOW_FREQUENCY_DIALOG:
      this.callBackArray.showFrequencyDialog = callback;
      break;
    default:
      break;
  }
}

取消事件注册:

// TaskDetailComponent.ets
aboutToAppear() {
  this.broadCast.off(BroadCastType.SHOW_TARGET_SETTING_DIALOG, () => {});
  this.broadCast.off(BroadCastType.SHOW_REMIND_TIME_DIALOG, () => {});
  this.broadCast.off(BroadCastType.SHOW_FREQUENCY_DIALOG, () => {});
}

// BroadCast.ets
public off(event: string, callback: Function) {
  if (event === null) {
    Logger.info(FILE_TAG, 'cancel all broadcast');
    this.callBackArray = callBackArrayTemp;
  }
  Logger.info(FILE_TAG, 'cancel broadcast with type '+ event);
  const cbs = this.callBackArray;
  if (!cbs) {
    return;
  }
  if (callback === null) {
    switch (event) {
      case BroadCastType.SHOW_ACHIEVEMENT_DIALOG:
        this.callBackArray.showAchievementDialog = () => {};
        break;
      case BroadCastType.SHOW_TASK_DETAIL_DIALOG:
        this.callBackArray.showTaskDetailDialog = () => {}; 
        break;
      case BroadCastType.SHOW_TARGET_SETTING_DIALOG:
        this.callBackArray.showTargetSettingDialog = () => {};
        break;
      case BroadCastType.SHOW_REMIND_TIME_DIALOG: 
        this.callBackArray.showRemindTimeDialog = () => {};
        break;
      case BroadCastType.SHOW_FREQUENCY_DIALOG:
        this.callBackArray.showFrequencyDialog = () => {};
        break;
      default:
        break;
    }
  }
}

发送事件:

// HomeComponent.ets
taskItemAction(item: TaskInfo, isClick: boolean): void {
  if (!this.homeStore.checkCurrentDay()) {
    return;
  }
  if (isClick) {
    // 点击时钟
    let callback: CustomDialogCallback = { confirmCallback: (taskTemp: TaskInfo) => {
      this.onConfirm(taskTemp)
      }, cancelCallback: () => {
      } };
    this.broadCast.emit(BroadCastType.SHOW_TASK_DETAIL_DIALOG, [item, callback]);
  } else {
    // 编辑任务
    let editTaskStr: string = JSON.stringify(TaskMapById[item.taskID - 1]);
    let editTask: ITaskItem = JSON.parse(editTaskStr);
    editTask.targetValue = item?.targetValue;
    editTask.isAlarm = item.isAlarm;
    editTask.startTime = item.startTime;
    editTask.frequency = item.frequency;
    editTask.isOpen = item.isOpen;
    router.pushUrl({ url: 'pages/TaskEditPage', params: { params: JSON.stringify(editTask) } });
  }
}