鸿蒙任务项设置案例实战

目录

案例效果

资源文件与初始化

string.json

color.json

CommonConstant

添加任务

首页组件

任务列表初始化

任务列表视图

任务编辑页

添加跳转

任务目标设置模型(formatParams)

编辑页面

详情页

任务编辑列表项

目标设置展示

引入目标设置

目标设置展示实现

[TaskInfo 枚举模型设置](#TaskInfo 枚举模型设置)

弹窗构造逻辑

定义单击事件

[定义 BroadCast](#定义 BroadCast)

定义弹窗视图

[定义弹窗 builder 组件](#定义弹窗 builder 组件)

目标设置弹窗实现

目标设置窗口逻辑

任务目标设置视图模型

频率设置视图模型

时间提醒弹窗实现

更新TaskDetail

实现编辑任务列表的开启提醒与提醒时间

实现时间提醒弹窗

频率弹窗和提交完成的实现

实现频率任务项视图

实现频率设置弹窗

定义频率设置视图模型


案例效果

资源文件与初始化

string.json

javascript 复制代码
{
  "string": [
    {
      "name": "entry_desc",
      "value": "description"
    },
    {
      "name": "entryAbility_desc",
      "value": "description"
    },
    {
      "name": "entryAbility_label",
      "value": "List_HDC"
    },
    {
      "name": "task_morning",
      "value": "早起"
    },
    {
      "name": "task_water",
      "value": "喝水"
    },
    {
      "name": "task_apple",
      "value": "吃苹果"
    },
    {
      "name": "task_smile",
      "value": "每日微笑"
    },
    {
      "name": "task_brush",
      "value": "每日刷牙"
    },
    {
      "name": "task_night",
      "value": "早睡"
    },
    {
      "name": "already_open",
      "value": "已开启"
    },
    {
      "name": "complete",
      "value": "完成"
    },
    {
      "name": "frequency",
      "value": "频率"
    },
    {
      "name": "remind_time",
      "value": "提醒时间"
    },
    {
      "name": "open_reminder",
      "value": "开启提醒"
    },
    {
      "name": "target_setting",
      "value": "目标设置"
    },
    {
      "name": "cancel",
      "value": "取消"
    },
    {
      "name": "confirm",
      "value": "确认"
    },
    {
      "name": "set_your_frequency",
      "value": "请设置您的频率"
    }
  ]
}

color.json

javascript 复制代码
{
  "color": [
    {
      "name": "white",
      "value": "#FFFFFF"
    },
    {
      "name": "primaryBgColor",
      "value": "#F1F3F5"
    },
    {
      "name": "titleColor",
      "value": "#182431"
    },
    {
      "name": "btnBgColor",
      "value": "#F2F2F2"
    },
    {
      "name": "statusTipColor",
      "value": "#989A9C"
    },
    {
      "name": "blueColor",
      "value": "#007DFF"
    },
    {
      "name": "black",
      "value": "#000000"
    },
    {
      "name": "primaryRed",
      "value": "#E92F4F"
    },
    {
      "name": "tabTitleColor",
      "value": "#999"
    },
    {
      "name": "signatureColor",
      "value": "#66686a"
    },
    {
      "name": "leveColor",
      "value": "#c99411"
    },
    {
      "name": "leveBgColor",
      "value": "#d4e6f1"
    },
    {
      "name": "borderColor",
      "value": "#cccccc"
    },
    {
      "name": "mineBgColor",
      "value": "#edf2f5"
    },
    {
      "name": "launcherBlueColor",
      "value": "#4694C2"
    },
    {
      "name": "disabledColor",
      "value": "#dddadc"
    }
  ]
}

CommonConstant

TypeScript 复制代码
// ets/common/contants/CommonConstant.ets

export const THOUSANDTH_80: string = '8%'
export const THOUSANDTH_100: string = '10%'
export const THOUSANDTH_400: string = '40%'
export const THOUSANDTH_500: string = '50%'
export const THOUSANDTH_560: string = '56%'
export const THOUSANDTH_800: string = '80%'
export const THOUSANDTH_900: string = '90%'
export const THOUSANDTH_940: string = '94%'
export const THOUSANDTH_1000: string = '100%'

export const DEFAULT_2: number = 2
export const DEFAULT_8: number = 8
export const DEFAULT_12: number = 12
export const DEFAULT_10: number = 10
export const DEFAULT_16: number = 16
export const DEFAULT_18: number = 18
export const DEFAULT_20: number = 20
export const DEFAULT_24: number = 24
export const DEFAULT_28: number = 28
export const DEFAULT_32: number = 32
export const DEFAULT_48: number = 48
export const DEFAULT_56: number = 56
export const DEFAULT_60: number = 60

export const LIST_ITEM_SPACE: number = 2

export const ADD_TASK_TITLE: string = '添加任务'
export const EDIT_TASK_TITLE: string = '编辑任务'

export const SETTING_FINISHED_MESSAGE = '设置完成!!!'
export const CHOOSE_TIME_OUT_RANGE: string = '选择时间超出范围'

export const TODAY: string = new Date().toDateString()

export const DEFAULT_TIME: string = '08:00'
export const GET_UP_TIME_RANGE: string = '(06:00 - 09:00)'
export const SLEEP_TIME_RANGE: string = '(20:00 - 23:00)'
export const GET_UP_EARLY_TIME: string = '06:00'
export const GET_UP_LATE_TIME: string = '09:00'
export const SLEEP_EARLY_TIME: string = '20:00'
export const SLEEP_LATE_TIME: string = '23:00'
export const DEFAULT_SELECTED_TIME: Date = new Date(`${TODAY} 8:00:00`)

export const EVERYDAY: string = '每天'
export const NO_LENGTH: number = 0
export const INIT_WEEK_IDS: string = '1, 2, 3, 4, 5, 6, 7'

export const PER_DAY: string = '/ 天'

export const ZERO: number = 0
export const MINUS_20: number = -20
export const HAS_NO_INDEX: number = -1

export const DRINK_STEP: number = 25
export const DRINK_MAX_RANGE: number = 500
export const TIMES_100: number = 100
export const EAT_APPLE_RANGE: number = 100
export const DEFAULT_TEXT: string = '0.25'
export const DEFAULT_APPLE: string = '1'

添加任务

首页组件

TypeScript 复制代码
// ets/pages/Index.ets

import TaskList from '../view/TaskList'
import { TaskListItem, TaskInitList } from '../model/TaskInitList'
import { THOUSANDTH_1000, ADD_TASK_TITLE } from '../common/constants/CommonConstant'

@Entry
@Component
struct Index {
  @Provide taskList: TaskListItem[] = TaskInitList

  build() {
    Row() {
      Navigation() {
        Column() {
          TaskList()
        }
        .width(THOUSANDTH_1000)
        .justifyContent(FlexAlign.Center)
      }
      .size({ width: THOUSANDTH_1000, height: THOUSANDTH_1000 })
      .title(ADD_TASK_TITLE)
      .titleMode(NavigationTitleMode.Mini)
    }
    .backgroundColor($r('app.color.primaryBgColor'))
    .height(THOUSANDTH_1000)
  }
}

任务列表初始化

TypeScript 复制代码
// ets/model/TaskInitList.ets

export interface TaskListItem {
  taskID: number
  taskName: Resource
  isOpen: boolean
  unit: string
  icon: Resource
  targetValue: string
  isAlarm: boolean
  startTime: string
  frequency: string
}

export interface FrequencyContentType {
  id: number,
  label: string,
  isChecked: boolean
}

export const TaskInitList: TaskListItem[] = [
  { // Get up early.
    taskID: 1,
    taskName: $r('app.string.task_morning'),
    icon: $r('app.media.morning'),
    targetValue: '08: 00',
    isOpen: true,
    unit: '',
    isAlarm: false,
    startTime: '08: 00',
    frequency: '1, 2, 3, 4, 5, 6, 7'
  },
  { // Drink water.
    taskID: 2,
    taskName: $r('app.string.task_water'),
    icon: $r('app.media.water'),
    targetValue: '0.25',
    isOpen: true,
    unit: 'L',
    isAlarm: false,
    startTime: '08: 00',
    frequency: '1, 2, 3, 4, 5, 6, 7'
  },
  { // Eat apples.
    taskID: 3,
    taskName: $r('app.string.task_apple'),
    icon: $r('app.media.apple'),
    targetValue: '1',
    isOpen: true,
    unit: '个',
    isAlarm: false,
    startTime: '08: 00',
    frequency: '1, 2, 3, 4, 5, 6, 7'
  },
  { // Smile every day.
    taskID: 4,
    taskName: $r('app.string.task_smile'),
    icon: $r('app.media.smile'),
    targetValue: '1',
    isOpen: false,
    unit: '次',
    isAlarm: false,
    startTime: '08: 00',
    frequency: '1, 2, 3, 4, 5, 6, 7'
  },
  { // Clean one's teeth.
    taskID: 5,
    taskName: $r('app.string.task_brush'),
    icon: $r('app.media.brush'),
    targetValue: '1',
    isOpen: false,
    unit: '次',
    isAlarm: false,
    startTime: '08: 00',
    frequency: '1, 2, 3, 4, 5, 6, 7'
  },
  { // Go to bed early.
    taskID: 6,
    taskName: $r('app.string.task_night'),
    icon: $r('app.media.night'),
    targetValue: '20: 00',
    isOpen: false,
    unit: '',
    isAlarm: false,
    startTime: '08: 00',
    frequency: '1, 2, 3, 4, 5, 6, 7'
  }
]

任务列表视图

TypeScript 复制代码
// ets/view/TaskList.ets

import * as commonConst from '../common/constants/CommonConstant'
import { TaskListItem } from '../model/TaskInitList'

@Component
export default struct TaskList {
  @Consume taskList: TaskListItem[]

  build() {
    List({ space: commonConst.LIST_ITEM_SPACE }) {
      ForEach(this.taskList, (item: TaskListItem) => {
        ListItem() {
          Row() {
            Row() {
              Image(item?.icon)
                .width(commonConst.DEFAULT_24)
                .height(commonConst.DEFAULT_24)
                .margin({ right: commonConst.DEFAULT_8 })
              Text(item?.taskName)
                .fontSize(commonConst.DEFAULT_20)
                .fontColor($r('app.color.titleColor'))
            }
            .width(commonConst.THOUSANDTH_500)

            Blank()
              .layoutWeight(1)

            if (item?.isOpen) {
              Text($r('app.string.already_open'))
                .fontSize(commonConst.DEFAULT_16)
                .flexGrow(1)
                .align(Alignment.End)
                .margin({ right: commonConst.DEFAULT_8 })
                .fontColor($r('app.color.titleColor'))
            }

            Image($r('app.media.right_grey'))
              .width(commonConst.DEFAULT_8)
              .height(commonConst.DEFAULT_16)
          }
          .width(commonConst.THOUSANDTH_1000)
          .justifyContent(FlexAlign.SpaceBetween)
          .padding({ left: commonConst.DEFAULT_12, right: commonConst.DEFAULT_12 })
        }
        .height(commonConst.THOUSANDTH_80)
        .borderRadius(commonConst.DEFAULT_12)
        .backgroundColor($r('app.color.white'))
      })
    }
    .height(commonConst.THOUSANDTH_1000)
    .width(commonConst.THOUSANDTH_940)
  }
}

任务编辑页

添加跳转

TypeScript 复制代码
// ets/view/TaskList.ets

import * as commonConst from '../common/constants/CommonConstant'
import { TaskListItem } from '../model/TaskInitList'
import { router } from '@kit.ArkUI'
import { formatParams } from '../viewModel/TaskTargetSetting'

@Component
export default struct TaskList {
  @Consume taskList: TaskListItem[]

  build() {
    List({ space: commonConst.LIST_ITEM_SPACE }) {
      ForEach(this.taskList, (item: TaskListItem) => {
        ListItem() {
          Row() {
            Row() {
              Image(item?.icon)
                .width(commonConst.DEFAULT_24)
                .height(commonConst.DEFAULT_24)
                .margin({ right: commonConst.DEFAULT_8 })
              Text(item?.taskName)
                .fontSize(commonConst.DEFAULT_20)
                .fontColor($r('app.color.titleColor'))
            }
            .width(commonConst.THOUSANDTH_500)

            Blank()
              .layoutWeight(1)

            if (item?.isOpen) {
              Text($r('app.string.already_open'))
                .fontSize(commonConst.DEFAULT_16)
                .flexGrow(1)
                .align(Alignment.End)
                .margin({ right: commonConst.DEFAULT_8 })
                .fontColor($r('app.color.titleColor'))
            }

            Image($r('app.media.right_grey'))
              .width(commonConst.DEFAULT_8)
              .height(commonConst.DEFAULT_16)
          }
          .width(commonConst.THOUSANDTH_1000)
          .justifyContent(FlexAlign.SpaceBetween)
          .padding({ left: commonConst.DEFAULT_12, right: commonConst.DEFAULT_12 })
        }
        .height(commonConst.THOUSANDTH_80)
        .borderRadius(commonConst.DEFAULT_12)
        .backgroundColor($r('app.color.white'))
        
        // 1. 添加链接
        .onClick(() => {
          router.pushUrl({
            url: 'pages/TaskEditPage',
            params: {
              params: formatParams(item)
            }
          })
          
        })
      })
    }
    .height(commonConst.THOUSANDTH_1000)
    .width(commonConst.THOUSANDTH_940)
  }
}

任务目标设置模型(formatParams)

TypeScript 复制代码
// ets/viewModel/TaskTargetSetting

import { TaskListItem } from '../model/TaskInitList'
export const formatParams = (params: TaskListItem) => {
  return JSON.stringify(params)
}

编辑页面

TypeScript 复制代码
// ets/pages/TaskEditPage.ets

import { THOUSANDTH_1000, EDIT_TASK_TITLE } from '../common/constants/CommonConstant'
import TaskDetail from '../view/TaskDetail'

@Entry
@Component
struct TaskEdit {
  build() {
    Row() {
      Navigation() {
        Column() {
          TaskDetail()
        }
        .width(THOUSANDTH_1000)
        .height(THOUSANDTH_1000)
      }
      .size({ width: THOUSANDTH_1000, height: THOUSANDTH_1000 })
      .title(EDIT_TASK_TITLE)
      .titleMode(NavigationTitleMode.Mini)
    }
    .height(THOUSANDTH_1000)
    .backgroundColor($r('app.color.primaryBgColor'))
  }
}

详情页

TypeScript 复制代码
// ets/view/TaskDetail.ets

import * as commonConst from '../common/constants/CommonConstant'
import { TaskListItem } from '../model/TaskInitList'
import {
  TaskChooseItem
} from './TaskEditListItem'
import { router } from '@kit.ArkUI'

@Styles
function listItemStyle() {
  .backgroundColor($r('app.color.white'))
  .height(commonConst.DEFAULT_56)
  .borderRadius(commonConst.DEFAULT_10)
  .padding({ left: commonConst.DEFAULT_12, right: commonConst.DEFAULT_12 })
}

@Component
export default struct TaskDetail {
  @Provide settingParams: TaskListItem = this.parseRouterParams()

  parseRouterParams() {
    let params = router.getParams() as Record<string, Object>
    const routerParams: TaskListItem = JSON.parse(params.params as string)
    return routerParams
  }

  build() {
    Column() {
      List({ space: commonConst.LIST_ITEM_SPACE }) {
        ListItem() {
          TaskChooseItem()
        }
        .listItemStyle()
      }
    }
    .width(commonConst.THOUSANDTH_1000)
  }
}

任务编辑列表项

TypeScript 复制代码
// ets/view/TaskEditListItem.ets

import { TaskListItem } from '../model/TaskInitList'
import {
  DEFAULT_20,
  DEFAULT_32,
  DEFAULT_56,
  THOUSANDTH_1000,
} from '../common/constants/CommonConstant'

@Component
export struct TaskChooseItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text(this.settingParams.taskName).fontSize(DEFAULT_20).fontWeight(FontWeight.Medium)
      Toggle({ type: ToggleType.Switch, isOn: this.settingParams.isOpen })
        .width(DEFAULT_56)
        .height(DEFAULT_32)
        .selectedColor($r('app.color.blueColor'))
        .onChange((isOn: boolean) => {
          this.settingParams.isOpen = isOn;
        })
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

目标设置展示

引入目标设置

TypeScript 复制代码
// ets/view/TaskDetail.ets

import * as commonConst from '../common/constants/CommonConstant'
import { TaskListItem } from '../model/TaskInitList'
import {
  TaskChooseItem,
  // 2. 引入TargetSetItem
  TargetSetItem
} from './TaskEditListItem'
import { router } from '@kit.ArkUI'
import { taskType } from '../model/TaskInfo'

@Styles
function listItemStyle() {
  .backgroundColor($r('app.color.white'))
  .height(commonConst.DEFAULT_56)
  .borderRadius(commonConst.DEFAULT_10)
  .padding({ left: commonConst.DEFAULT_12, right: commonConst.DEFAULT_12 })
}

@Component
export default struct TaskDetail {
  @Provide settingParams: TaskListItem = this.parseRouterParams()

  parseRouterParams() {
    let params = router.getParams() as Record<string, Object>
    const routerParams: TaskListItem = JSON.parse(params.params as string)
    return routerParams
  }

  build() {
    Column() {
      List({ space: commonConst.LIST_ITEM_SPACE }) {
        ListItem() {
          TaskChooseItem()
        }
        .listItemStyle()

        // 1. 目标设置入口
        ListItem() {
          TargetSetItem()
        }
        .listItemStyle()
        .enabled(
          this.settingParams?.isOpen &&
            (this.settingParams?.taskID !== taskType.smile) &&
            (this.settingParams?.taskID !== taskType.brushTeeth)
        )
      }
      .width(commonConst.THOUSANDTH_940)
    }
    .width(commonConst.THOUSANDTH_1000)
  }
}

目标设置展示实现

TypeScript 复制代码
// ets/view/TaskEditListItem.ets

import { TaskListItem } from '../model/TaskInitList'
import {
  DEFAULT_16,
  DEFAULT_20,
  DEFAULT_32,
  DEFAULT_56,
  DEFAULT_8,
  PER_DAY,
  THOUSANDTH_1000,
} from '../common/constants/CommonConstant'
import { taskType } from '../model/TaskInfo'

// 2.定义公共样式targetSetCommon
@Extend(Text)
function targetSetCommon() {
  .fontSize(DEFAULT_16)
  .flexGrow(1)
  .margin({ right: DEFAULT_8 })
  .align(Alignment.End)
}

// 2.定义公共样式targetSettingStyle
@Extend(Text)
function targetSettingStyle(isOpen: boolean, taskID: number) {
  .fontColor(isOpen && taskID !== taskType.smile && taskID !== taskType.brushTeeth ?
  $r('app.color.titleColor') :
  $r('app.color.disabledColor'))
}

@Component
export struct TaskChooseItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text(this.settingParams.taskName).fontSize(DEFAULT_20).fontWeight(FontWeight.Medium)
      Toggle({ type: ToggleType.Switch, isOn: this.settingParams.isOpen })
        .width(DEFAULT_56)
        .height(DEFAULT_32)
        .selectedColor($r('app.color.blueColor'))
        .onChange((isOn: boolean) => {
          this.settingParams.isOpen = isOn;
        })
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

// 1. 定义TargetSetItem组件
@Component
export struct TargetSetItem {
  @Consume settingParams: TaskListItem;

  build() {
    Row() {
      Text($r('app.string.target_setting'))
        .fontSize(DEFAULT_20)
        .fontWeight(FontWeight.Medium)

      Blank()
        .layoutWeight(1)

      if (this.settingParams?.unit === '') {
        Text(`${this.settingParams?.targetValue}`)
          .targetSetCommon()
          .targetSettingStyle(this.settingParams?.isOpen, this.settingParams?.taskID)
      } else {
        Text(`${this.settingParams?.targetValue} ${this.settingParams?.unit} ${PER_DAY}`)
          .targetSetCommon()
          .targetSettingStyle(this.settingParams?.isOpen, this.settingParams?.taskID)
      }
      Image($r('app.media.right_grey')).width(DEFAULT_8).height(DEFAULT_16);
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

TaskInfo 枚举模型设置

TypeScript 复制代码
// ets/model/TaskInfo.ets

export enum taskType {
  'getup' = 1,
  'drinkWater',
  'eatApple',
  'smile',
  'brushTeeth',
  'sleepEarly'
}

弹窗构造逻辑

定义单击事件

TypeScript 复制代码
import * as commonConst from '../common/constants/CommonConstant'
import { TaskListItem } from '../model/TaskInitList'
import {
  TaskChooseItem,
  TargetSetItem
} from './TaskEditListItem'
import { router } from '@kit.ArkUI'
import { taskType } from '../model/TaskInfo'

// 4. 引入BroadCast(先去创建)
import { BroadCast, BroadCastType } from '../common/utils/BroadCast'

import { CustomDialogView } from './CustomDialogView'

@Styles
function listItemStyle() {
  .backgroundColor($r('app.color.white'))
  .height(commonConst.DEFAULT_56)
  .borderRadius(commonConst.DEFAULT_10)
  .padding({ left: commonConst.DEFAULT_12, right: commonConst.DEFAULT_12 })
}

@Component
export default struct TaskDetail {
  @Provide settingParams: TaskListItem = this.parseRouterParams()

  // 5. 提供 broadCast
  @Provide broadCast: BroadCast = new BroadCast()

  parseRouterParams() {
    let params = router.getParams() as Record<string, Object>
    const routerParams: TaskListItem = JSON.parse(params.params as string)
    return routerParams
  }

  // 7. 先去定义弹窗和builder,注册(on)完,这里解绑
  aboutToAppear() {
    this.broadCast.off()
  }

  build() {
    Column() {
      List({ space: commonConst.LIST_ITEM_SPACE }) {
        ListItem() {
          TaskChooseItem()
        }
        .listItemStyle()

        ListItem() {
          TargetSetItem()
        }
        .listItemStyle()
          
        // 3. 设置 smile & brushTeeth 不可单击
        .enabled(
          this.settingParams?.isOpen &&
            (this.settingParams?.taskID !== taskType.smile) &&
            (this.settingParams?.taskID !== taskType.brushTeeth)
        )
        // 1. 定义单击事件
        .onClick(() => {
          // 2. 测试单击,目的是引出第 3 步
          // console.log('test')

          // 8. 最后再触发
          this.broadCast.emit(
            BroadCastType.SHOW_TARGET_SETTING_DIALOG)
        })

        // 9. 测试提醒时间设置
        ListItem() {
          Text('提醒时间')
        }
        .listItemStyle()
        .enabled(this.settingParams?.isOpen && this.settingParams?.isAlarm)
        .onClick(() => {
          this.broadCast.emit(
            BroadCastType.SHOW_REMIND_TIME_DIALOG
          )
        })

        // 9. 测试频率设置
        ListItem() {
          Text('频率')
        }
        .listItemStyle()
        .enabled(this.settingParams?.isOpen)
        .onClick(() => {
          this.broadCast.emit(
            BroadCastType.SHOW_FREQUENCY_DIALOG
          )
        })
      }

      // 6.定义弹框视图(先去创建)
      CustomDialogView()
    }
    .width(commonConst.THOUSANDTH_1000)
  }
}

定义 BroadCast

TypeScript 复制代码
// ets/common/util/BroadCast.ets

export class BroadCast {
  private callBackArray = []

  public on(event: string, callback: Function) {
    (this.callBackArray[event] || (this.callBackArray[event] = [])).push(callback)
  }

  public off() {
    this.callBackArray = []
  }

  public emit(event: string) {
    let _self = this
    if (!this.callBackArray[event]) {
      return
    }
    let cbs: Function[] = this.callBackArray[event]
    if (cbs) {
      let len = cbs.length;
      for (let i = 0; i < len; i++) {
        try {
          cbs[i](_self)
        } catch (e) {
          new Error(e)
        }
      }
    }
  }
}

export enum BroadCastType {
  SHOW_TARGET_SETTING_DIALOG = 'showTargetSettingDialog',
  SHOW_REMIND_TIME_DIALOG = 'showRemindTimeDialog',
  SHOW_FREQUENCY_DIALOG = 'showFrequencyDialog'
}

定义弹窗视图

TypeScript 复制代码
// ets/view/CustomDialogView.ets

import { TargetSettingDialog, RemindTimeDialog, FrequencyDialog } from './TaskSettingDialog'
import { BroadCast, BroadCastType } from '../common/utils/BroadCast'
import { ZERO, MINUS_20 } from '../common/constants/CommonConstant'

@Component
export struct CustomDialogView {
  @State isShow: boolean = false
  @Provide achievementLevel: number = 3
  @Consume broadCast: BroadCast

  targetSettingDialogController: CustomDialogController = new CustomDialogController({
    builder: TargetSettingDialog(),
    autoCancel: true,
    alignment: DialogAlignment.Bottom,
    offset: { dx: ZERO, dy: MINUS_20 }
  })

  RemindTimeDialogController: CustomDialogController = new CustomDialogController({
    builder: RemindTimeDialog(),
    autoCancel: true,
    alignment: DialogAlignment.Bottom,
    offset: { dx: ZERO, dy: MINUS_20 }
  });

  FrequencyDialogController: CustomDialogController = new CustomDialogController({
    builder: FrequencyDialog(),
    autoCancel: true,
    alignment: DialogAlignment.Bottom,
    offset: { dx: ZERO, dy: MINUS_20 }
  })

  aboutToAppear() {
    let self = this

    this.broadCast.on(
      BroadCastType.SHOW_TARGET_SETTING_DIALOG,
      () => {
        self.targetSettingDialogController.open()
      })

    this.broadCast.on(
      BroadCastType.SHOW_REMIND_TIME_DIALOG,
      () => {
        self.RemindTimeDialogController.open()
      })

    this.broadCast.on(
      BroadCastType.SHOW_FREQUENCY_DIALOG,
      () => {
        self.FrequencyDialogController.open()
      })
  }

  build() {
  }
}

定义弹窗 builder 组件

TypeScript 复制代码
// ets/view/TaskSettingDialog.ets

import * as commonConst from '../common/constants/CommonConstant'

@CustomDialog
export struct TargetSettingDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: TargetSettingDialog()
  })

  build() {
    Column() {
      Text('target setting dialog')
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

@CustomDialog
export struct RemindTimeDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: RemindTimeDialog()
  })

  build() {
    Column() {
      Text('remind time dialog')
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

@CustomDialog
export struct FrequencyDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: FrequencyDialog()
  })

  build() {
    Column() {
      Text('frequency dialog')
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

目标设置弹窗实现

目标设置窗口逻辑

TypeScript 复制代码
// ets/view/TaskSettingDialog.ets

import * as commonConst from '../common/constants/CommonConstant'
import { taskType } from '../model/TaskInfo'
import { TaskListItem } from '../model/TaskInitList'
import { createAppleRange, createDrinkRange, formatTime, returnTimeStamp } from '../viewModel/TaskTargetSetting'
import { promptAction } from '@kit.ArkUI'

@CustomDialog
export struct TargetSettingDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: TargetSettingDialog()
  })

  @Consume settingParams: TaskListItem

  currentTime: string = commonConst.DEFAULT_TIME
  currentValue: string = this.settingParams.taskID === taskType.drinkWater ? commonConst.DEFAULT_TEXT :
  commonConst.DEFAULT_APPLE

  drinkRange: string[] = createDrinkRange()
  appleRange: string[] = createAppleRange()

  createSubTitle() {
    if (this.settingParams.taskID === taskType.getup) {
      return commonConst.GET_UP_TIME_RANGE
    }
    if (this.settingParams.taskID === taskType.sleepEarly) {
      return commonConst.SLEEP_TIME_RANGE
    }
    return ''
  }

  setTargetValue() {
    if (this.settingParams.taskID === taskType.getup) {
      if (!this.compareTime(commonConst.GET_UP_EARLY_TIME, commonConst.GET_UP_LATE_TIME)) {
        return
      }
      this.settingParams.targetValue = this.currentTime
      return
    }

    if (this.settingParams.taskID === taskType.sleepEarly) {
      if (!this.compareTime(commonConst.SLEEP_EARLY_TIME, commonConst.SLEEP_LATE_TIME)) {
        return
      }
      this.settingParams.targetValue = this.currentTime
      return
    }
    this.settingParams.targetValue = this.currentValue
  }

  compareTime(startTime: string, endTime: string) {
    if (returnTimeStamp(this.currentTime) < returnTimeStamp(startTime) ||
      returnTimeStamp(this.currentTime) > returnTimeStamp(endTime)) {

      promptAction.showToast({
        message: commonConst.CHOOSE_TIME_OUT_RANGE
      })
      return false
    }
    return true
  }

  build() {
    Column() {
      Row() {
        Text($r('app.string.target_setting')).fontSize(commonConst.DEFAULT_20).margin({ right: commonConst.DEFAULT_12 })
        Text(this.createSubTitle())
          .fontSize(commonConst.DEFAULT_16)
      }
      .width(commonConst.THOUSANDTH_1000)
      .justifyContent(FlexAlign.Start)

      if ([taskType.getup, taskType.sleepEarly].indexOf(this.settingParams.taskID) > commonConst.HAS_NO_INDEX) {
        TimePicker({
          selected: commonConst.DEFAULT_SELECTED_TIME
        })
          .height(commonConst.THOUSANDTH_800)
          .useMilitaryTime(true)
          .onChange((value: TimePickerResult) => {
            this.currentTime = formatTime(value)
          })
      } else {
        TextPicker({ range: this.settingParams.taskID === taskType.drinkWater ? this.drinkRange : this.appleRange,
          value: this.settingParams.targetValue })
          .width(commonConst.THOUSANDTH_900)
          .height(commonConst.THOUSANDTH_800)
          .onChange((value: string | string[]) => {
            this.currentValue = (value as string)?.split(' ')[0]
          })
      }

      Row() {
        Text($r('app.string.cancel')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.currentTime = commonConst.DEFAULT_TIME
            this.currentValue = commonConst.DEFAULT_TEXT
            this.controller.close()
          })

        Text($r('app.string.confirm')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.setTargetValue()
            this.controller.close()
          })
      }
      .justifyContent(FlexAlign.SpaceAround)
      .width(commonConst.THOUSANDTH_1000)
      .height(commonConst.DEFAULT_28)
      .margin({ bottom: commonConst.DEFAULT_20 })
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

@CustomDialog
export struct RemindTimeDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: RemindTimeDialog()
  })

  build() {
    Column() {
      Text('remind time dialog')
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

@CustomDialog
export struct FrequencyDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: FrequencyDialog()
  })

  build() {
    Column() {
      Text('frequency dialog')
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

任务目标设置视图模型

TypeScript 复制代码
// ets/TaskTargetSetting.ets

import { DRINK_MAX_RANGE, DRINK_STEP, EAT_APPLE_RANGE, TIMES_100, TODAY } from '../common/constants/CommonConstant'
import { TaskListItem } from '../model/TaskInitList'
import { padTo2Digits } from './FrequencySetting'

export const formatParams = (params: TaskListItem) => {
  return JSON.stringify(params)
}

export const formatTime = (value: TimePickerResult) => {
  let hour: number = 0
  let minute: number = 0
  if (value.hour !== undefined && value.minute !== undefined) {
    hour = value.hour
    minute = value.minute
  }
  return `${padTo2Digits(hour)}:${padTo2Digits(minute)}`
}

export const createDrinkRange = () => {
  const drinkRangeArr: string[] = []
  for (let i = DRINK_STEP; i <= DRINK_MAX_RANGE; i += DRINK_STEP) {
    drinkRangeArr.push(`${i / TIMES_100} L`)
  }
  return drinkRangeArr
}

export const createAppleRange = () => {
  const appleRangeArr: string[] = []
  for (let i = 1; i <= EAT_APPLE_RANGE; i++) {
    appleRangeArr.push(`${i} 个`)
  }
  return appleRangeArr
}

export const returnTimeStamp = (currentTime: string) => {
  const timeString = `${TODAY} ${currentTime}`
  return new Date(timeString).getTime()
}

频率设置视图模型

TypeScript 复制代码
// ets/viewModel/FrequencySetting.ets

export function padTo2Digits(num: number) {
  return num.toString().padStart(2, '0')
}

时间提醒弹窗实现

更新TaskDetail

TypeScript 复制代码
// ets/view/TaskDetail.ets

import * as commonConst from '../common/constants/CommonConstant'
import { TaskListItem } from '../model/TaskInitList'
import {
  TaskChooseItem,
  TargetSetItem,
  // 3. 导入模块
  OpenRemindItem, 
  RemindTimeItem
} from './TaskEditListItem'
import { router } from '@kit.ArkUI'
import { taskType } from '../model/TaskInfo'
import { BroadCast, BroadCastType } from '../common/utils/BroadCast'
import { CustomDialogView } from './CustomDialogView'

@Styles
function listItemStyle() {
  .backgroundColor($r('app.color.white'))
  .height(commonConst.DEFAULT_56)
  .borderRadius(commonConst.DEFAULT_10)
  .padding({ left: commonConst.DEFAULT_12, right: commonConst.DEFAULT_12 })
}

@Component
export default struct TaskDetail {
  @Provide settingParams: TaskListItem = this.parseRouterParams()
  @Provide broadCast: BroadCast = new BroadCast()

  parseRouterParams() {
    let params = router.getParams() as Record<string, Object>
    const routerParams: TaskListItem = JSON.parse(params.params as string)
    return routerParams
  }

  aboutToAppear() {
    this.broadCast.off()
  }

  build() {
    Column() {
      List({ space: commonConst.LIST_ITEM_SPACE }) {
        ListItem() {
          TaskChooseItem()
        }
        .listItemStyle()

        ListItem() {
          TargetSetItem()
        }
        .listItemStyle()
        .enabled(
          this.settingParams?.isOpen &&
            (this.settingParams?.taskID !== taskType.smile) &&
            (this.settingParams?.taskID !== taskType.brushTeeth)
        )
        .onClick(() => {
          this.broadCast.emit(
            BroadCastType.SHOW_TARGET_SETTING_DIALOG)
        })

        // 1.构造编辑列表相应内容
        ListItem() {
          OpenRemindItem()
        }
        .listItemStyle()
        .enabled(this.settingParams.isOpen)

        
        ListItem() {
          // 2.构造编辑列表相应内容
          RemindTimeItem()
        }
        .listItemStyle()
        .onClick(() => {
          this.broadCast.emit(
            BroadCastType.SHOW_REMIND_TIME_DIALOG
          )
        })

        ListItem() {
          Text('频率')
        }
        .listItemStyle()
        .onClick(() => {
          this.broadCast.emit(
            BroadCastType.SHOW_FREQUENCY_DIALOG
          )
        })
      }

      CustomDialogView()
    }
    .width(commonConst.THOUSANDTH_1000)
  }
}

实现编辑任务列表的开启提醒与提醒时间

TypeScript 复制代码
import { TaskListItem } from '../model/TaskInitList'
import {
  DEFAULT_16,
  DEFAULT_20,
  DEFAULT_32,
  DEFAULT_56,
  DEFAULT_8,
  PER_DAY,
  THOUSANDTH_1000,
} from '../common/constants/CommonConstant'
import { taskType } from '../model/TaskInfo'

@Extend(Text)
function targetSetCommon() {
  .fontSize(DEFAULT_16)
  .flexGrow(1)
  .margin({ right: DEFAULT_8 })
  .align(Alignment.End)
}

@Extend(Text)
function targetSettingStyle(isOpen: boolean, taskID: number) {
  .fontColor(isOpen && taskID !== taskType.smile && taskID !== taskType.brushTeeth ?
  $r('app.color.titleColor') :
  $r('app.color.disabledColor'))
}

@Extend(Text)
function remindTimeStyle(isOpen: boolean, isAlarm: boolean) {
  .fontColor(isOpen && isAlarm ? $r('app.color.titleColor') : $r('app.color.disabledColor'))
}

@Component
export struct TaskChooseItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text(this.settingParams.taskName).fontSize(DEFAULT_20).fontWeight(FontWeight.Medium)
      Toggle({ type: ToggleType.Switch, isOn: this.settingParams.isOpen })
        .width(DEFAULT_56)
        .height(DEFAULT_32)
        .selectedColor($r('app.color.blueColor'))
        .onChange((isOn: boolean) => {
          this.settingParams.isOpen = isOn;
        })
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

@Component
export struct TargetSetItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text($r('app.string.target_setting'))
        .fontSize(DEFAULT_20)
        .fontWeight(FontWeight.Medium)

      Blank()
        .layoutWeight(1)

      if (this.settingParams?.unit === '') {
        Text(`${this.settingParams?.targetValue}`)
          .targetSetCommon()
          .targetSettingStyle(this.settingParams?.isOpen, this.settingParams?.taskID)
      } else {
        Text(`${this.settingParams?.targetValue} ${this.settingParams?.unit} ${PER_DAY}`)
          .targetSetCommon()
          .targetSettingStyle(this.settingParams?.isOpen, this.settingParams?.taskID)
      }
      Image($r('app.media.right_grey')).width(DEFAULT_8).height(DEFAULT_16);
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}


// 1.实现开启提醒
@Component
export struct OpenRemindItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text($r('app.string.open_reminder'))
        .fontSize(DEFAULT_20)
        .fontWeight(FontWeight.Medium)

      Blank()
        .layoutWeight(1)

      Toggle({ type: ToggleType.Switch, isOn: this.settingParams?.isAlarm })
        .width(DEFAULT_56)
        .height(DEFAULT_32)
        .selectedColor($r('app.color.blueColor'))
        .onChange((isOn: boolean) => {
          this.settingParams.isAlarm = isOn
        })
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

// 2.实现提醒时间
@Component
export struct RemindTimeItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text($r('app.string.remind_time')).fontSize(DEFAULT_20).fontWeight(FontWeight.Medium)
      Blank()
        .layoutWeight(1)
      Text(this.settingParams?.startTime)
        .targetSetCommon()
        .remindTimeStyle(this.settingParams.isOpen, this.settingParams.isAlarm)
      Image($r('app.media.right_grey')).width(DEFAULT_8).height(DEFAULT_16)
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

实现时间提醒弹窗

TypeScript 复制代码
import * as commonConst from '../common/constants/CommonConstant'
import { taskType } from '../model/TaskInfo'
import { TaskListItem } from '../model/TaskInitList'
import { createAppleRange, createDrinkRange, formatTime, returnTimeStamp } from '../viewModel/TaskTargetSetting'
import { promptAction } from '@kit.ArkUI'

@CustomDialog
export struct TargetSettingDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: TargetSettingDialog()
  })

  @Consume settingParams: TaskListItem

  currentTime: string = commonConst.DEFAULT_TIME
  currentValue: string = this.settingParams.taskID === taskType.drinkWater ? commonConst.DEFAULT_TEXT :
  commonConst.DEFAULT_APPLE

  drinkRange: string[] = createDrinkRange()
  appleRange: string[] = createAppleRange()

  createSubTitle() {
    if (this.settingParams.taskID === taskType.getup) {
      return commonConst.GET_UP_TIME_RANGE
    }
    if (this.settingParams.taskID === taskType.sleepEarly) {
      return commonConst.SLEEP_TIME_RANGE
    }
    return ''
  }

  setTargetValue() {
    if (this.settingParams.taskID === taskType.getup) {
      if (!this.compareTime(commonConst.GET_UP_EARLY_TIME, commonConst.GET_UP_LATE_TIME)) {
        return
      }
      this.settingParams.targetValue = this.currentTime
      return
    }

    if (this.settingParams?.taskID === taskType.sleepEarly) {
      if (!this.compareTime(commonConst.SLEEP_EARLY_TIME, commonConst.SLEEP_LATE_TIME)) {
        return
      }
      this.settingParams.targetValue = this.currentTime
      return
    }
    this.settingParams.targetValue = this.currentValue
  }

  compareTime(startTime: string, endTime: string) {
    if (returnTimeStamp(this.currentTime) < returnTimeStamp(startTime) ||
      returnTimeStamp(this.currentTime) > returnTimeStamp(endTime)) {

      promptAction.showToast({
        message: commonConst.CHOOSE_TIME_OUT_RANGE
      })
      return false
    }
    return true
  }

  build() {
    Column() {
      Row() {
        Text($r('app.string.target_setting')).fontSize(commonConst.DEFAULT_20).margin({ right: commonConst.DEFAULT_12 })
        Text(this.createSubTitle())
          .fontSize(commonConst.DEFAULT_16)
      }
      .width(commonConst.THOUSANDTH_1000)
      .justifyContent(FlexAlign.Start)

      if ([taskType.getup, taskType.sleepEarly].indexOf(this.settingParams.taskID) > commonConst.HAS_NO_INDEX) {
        TimePicker({
          selected: commonConst.DEFAULT_SELECTED_TIME
        })
          .height(commonConst.THOUSANDTH_800)
          .useMilitaryTime(true)
          .onChange((value: TimePickerResult) => {
            this.currentTime = formatTime(value)
          })
      } else {
        TextPicker({ range: this.settingParams?.taskID === taskType.drinkWater ? this.drinkRange : this.appleRange,
          value: this.settingParams.targetValue })
          .width(commonConst.THOUSANDTH_900)
          .height(commonConst.THOUSANDTH_800)
          .onChange((value: string | string[]) => {
            this.currentValue = (value as string)?.split(' ')[0]
          })
      }

      Row() {
        Text($r('app.string.cancel')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.currentTime = commonConst.DEFAULT_TIME;
            this.currentValue = commonConst.DEFAULT_TEXT;
            this.controller.close()
          })

        Text($r('app.string.confirm')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.setTargetValue()
            this.controller.close()
          })
      }
      .justifyContent(FlexAlign.SpaceAround)
      .width(commonConst.THOUSANDTH_1000)
      .height(commonConst.DEFAULT_28)
      .margin({ bottom: commonConst.DEFAULT_20 })
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

// 实现时间提醒弹窗
@CustomDialog
export struct RemindTimeDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: RemindTimeDialog()
  })

  currentTime: string = commonConst.DEFAULT_TIME

  @Consume settingParams: TaskListItem

  build() {
    Column() {
      Column() {
        Text($r('app.string.remind_time'))
          .fontSize(commonConst.DEFAULT_20)
          .margin({ top: commonConst.DEFAULT_10 })
          .width(commonConst.THOUSANDTH_1000)
          .textAlign(TextAlign.Start)
      }
      .width(commonConst.THOUSANDTH_900)

      TimePicker({
        selected: commonConst.DEFAULT_SELECTED_TIME
      })
        .height(commonConst.THOUSANDTH_800)
        .useMilitaryTime(true)
        .onChange((value: TimePickerResult) => {
          this.currentTime = formatTime(value)
        })

      Row() {
        Text($r('app.string.cancel')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.currentTime = commonConst.DEFAULT_TIME
            this.controller.close()
          })

        Text($r('app.string.confirm')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.settingParams.startTime = this.currentTime
            this.controller.close()
          })
      }
      .justifyContent(FlexAlign.SpaceAround)
      .width(commonConst.THOUSANDTH_1000)
      .height(commonConst.DEFAULT_28)
      .margin({ bottom: commonConst.DEFAULT_20 })
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

@CustomDialog
export struct FrequencyDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: FrequencyDialog()
  })

  build() {
    Column() {
      Text('frequency dialog')
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

频率弹窗和提交完成的实现

TypeScript 复制代码
// ets/view/TaskDetail.ets

import * as commonConst from '../common/constants/CommonConstant'
import { TaskListItem } from '../model/TaskInitList'
import {
  TaskChooseItem,
  TargetSetItem,
  OpenRemindItem,
  RemindTimeItem,
  FrequencyItem
} from './TaskEditListItem'
import { promptAction, router } from '@kit.ArkUI'
import { taskType } from '../model/TaskInfo'
import { BroadCast, BroadCastType } from '../common/utils/BroadCast'
import { CustomDialogView } from './CustomDialogView'

@Styles
function listItemStyle() {
  .backgroundColor($r('app.color.white'))
  .height(commonConst.DEFAULT_56)
  .borderRadius(commonConst.DEFAULT_10)
  .padding({ left: commonConst.DEFAULT_12, right: commonConst.DEFAULT_12 })
}

@Component
export default struct TaskDetail {
  @Provide settingParams: TaskListItem = this.parseRouterParams()
  @Provide broadCast: BroadCast = new BroadCast()
  @Provide frequency: string = commonConst.EVERYDAY

  parseRouterParams() {
    let params = router.getParams() as Record<string, Object>
    const routerParams: TaskListItem = JSON.parse(params.params as string)
    return routerParams
  }

  aboutToAppear() {
    this.broadCast.off()
  }

  finishTaskEdit() {
    promptAction.showToast({
      message: commonConst.SETTING_FINISHED_MESSAGE
    })
  }

  build() {
    Column() {
      List({ space: commonConst.LIST_ITEM_SPACE }) {
        ListItem() {
          TaskChooseItem()
        }
        .listItemStyle()

        ListItem() {
          TargetSetItem()
        }
        .listItemStyle()
        .enabled(
          this.settingParams?.isOpen &&
            (this.settingParams?.taskID !== taskType.smile) &&
            (this.settingParams?.taskID !== taskType.brushTeeth)
        )
        .onClick(() => {
          this.broadCast.emit(
            BroadCastType.SHOW_TARGET_SETTING_DIALOG)
        })

        ListItem() {
          OpenRemindItem()
        }
        .listItemStyle()
        .enabled(this.settingParams?.isOpen)

        ListItem() {
          RemindTimeItem()
        }
        .listItemStyle()
        .onClick(() => {
          this.broadCast.emit(
            BroadCastType.SHOW_REMIND_TIME_DIALOG
          )
        })

        // 1. 引入FrequencyItem
        ListItem() {
          FrequencyItem()
        }
        .listItemStyle()
        .onClick(() => {
          this.broadCast.emit(
            BroadCastType.SHOW_FREQUENCY_DIALOG
          )
        })
      }
      .width(commonConst.THOUSANDTH_940)

      // x. 最后实现完成按钮提交
      Button() {
        Text($r('app.string.complete')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
      }
      .width(commonConst.THOUSANDTH_800)
      .height(commonConst.DEFAULT_48)
      .backgroundColor($r('app.color.borderColor'))
      .onClick(() => {
        this.finishTaskEdit()
      })
      .position({
        x: commonConst.THOUSANDTH_100,
        y: commonConst.THOUSANDTH_800
      })

      CustomDialogView()
    }
    .width(commonConst.THOUSANDTH_1000)
  }
}

实现频率任务项视图

TypeScript 复制代码
// ets/view/TaskEditListItem.ets

import { TaskListItem } from '../model/TaskInitList'
import {
  DEFAULT_12,
  DEFAULT_16,
  DEFAULT_20,
  DEFAULT_32,
  DEFAULT_56,
  DEFAULT_8,
  PER_DAY,
  THOUSANDTH_1000,
} from '../common/constants/CommonConstant'
import { taskType } from '../model/TaskInfo'

@Extend(Text)
function targetSetCommon() {
  .fontSize(DEFAULT_16)
  .flexGrow(1)
  .margin({ right: DEFAULT_8 })
  .align(Alignment.End)
}

@Extend(Text)
function targetSettingStyle(isOpen: boolean, taskID: number) {
  .fontColor(isOpen && taskID !== taskType.smile && taskID !== taskType.brushTeeth ?
  $r('app.color.titleColor') :
  $r('app.color.disabledColor'))
}

@Extend(Text)
function remindTimeStyle(isOpen: boolean, isAlarm: boolean) {
  .fontColor(isOpen && isAlarm ? $r('app.color.titleColor') : $r('app.color.disabledColor'))
}

@Extend(Text)
function frequencyStyle(isOpen: boolean) {
  .fontSize(DEFAULT_12)
  .flexGrow(1)
  .margin({ right: DEFAULT_8 })
  .textAlign(TextAlign.End)
  .fontColor(isOpen ? $r('app.color.titleColor') : $r('app.color.disabledColor'))
}

@Component
export struct TaskChooseItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text(this.settingParams.taskName).fontSize(DEFAULT_20).fontWeight(FontWeight.Medium)
      Toggle({ type: ToggleType.Switch, isOn: this.settingParams.isOpen })
        .width(DEFAULT_56)
        .height(DEFAULT_32)
        .selectedColor($r('app.color.blueColor'))
        .onChange((isOn: boolean) => {
          this.settingParams.isOpen = isOn;
        })
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

@Component
export struct TargetSetItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text($r('app.string.target_setting'))
        .fontSize(DEFAULT_20)
        .fontWeight(FontWeight.Medium)

      Blank()
        .layoutWeight(1)

      if (this.settingParams?.unit === '') {
        Text(`${this.settingParams?.targetValue}`)
          .targetSetCommon()
          .targetSettingStyle(this.settingParams?.isOpen, this.settingParams?.taskID)
      } else {
        Text(`${this.settingParams?.targetValue} ${this.settingParams?.unit} ${PER_DAY}`)
          .targetSetCommon()
          .targetSettingStyle(this.settingParams?.isOpen, this.settingParams?.taskID)
      }
      Image($r('app.media.right_grey')).width(DEFAULT_8).height(DEFAULT_16);
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

@Component
export struct OpenRemindItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text($r('app.string.open_reminder'))
        .fontSize(DEFAULT_20)
        .fontWeight(FontWeight.Medium)

      Blank()
        .layoutWeight(1)

      Toggle({ type: ToggleType.Switch, isOn: this.settingParams?.isAlarm })
        .width(DEFAULT_56)
        .height(DEFAULT_32)
        .selectedColor($r('app.color.blueColor'))
        .onChange((isOn: boolean) => {
          this.settingParams.isAlarm = isOn
        })
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

@Component
export struct RemindTimeItem {
  @Consume settingParams: TaskListItem

  build() {
    Row() {
      Text($r('app.string.remind_time')).fontSize(DEFAULT_20).fontWeight(FontWeight.Medium)
      Blank()
        .layoutWeight(1)
      Text(this.settingParams?.startTime)
        .targetSetCommon()
        .remindTimeStyle(this.settingParams.isOpen, this.settingParams.isAlarm)
      Image($r('app.media.right_grey')).width(DEFAULT_8).height(DEFAULT_16)
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

// 1. 实现频率任务项视图
@Component
export struct FrequencyItem {
  @Consume settingParams: TaskListItem
  @Consume frequency: string

  build() {
    Row() {
      Text($r('app.string.frequency')).fontSize(DEFAULT_20).fontWeight(FontWeight.Medium)
      Text(this.frequency)
        .targetSetCommon()
        .frequencyStyle(this.settingParams.isOpen)
      Image($r('app.media.right_grey')).width(DEFAULT_8).height(DEFAULT_16)
    }
    .width(THOUSANDTH_1000)
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

实现频率设置弹窗

TypeScript 复制代码
// ets/view/TaskSettingDialog.ets

import * as commonConst from '../common/constants/CommonConstant'
import { taskType } from '../model/TaskInfo'
import { FrequencyContentType, TaskListItem } from '../model/TaskInitList'
import { createAppleRange, createDrinkRange, formatTime, returnTimeStamp } from '../viewModel/TaskTargetSetting'
import { promptAction } from '@kit.ArkUI'
import { frequencyRange } from '../viewModel/FrequencySetting'

@CustomDialog
export struct TargetSettingDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: TargetSettingDialog()
  })

  @Consume settingParams: TaskListItem

  currentTime: string = commonConst.DEFAULT_TIME
  currentValue: string = this.settingParams.taskID === taskType.drinkWater ? commonConst.DEFAULT_TEXT :
  commonConst.DEFAULT_APPLE

  drinkRange: string[] = createDrinkRange()
  appleRange: string[] = createAppleRange()

  createSubTitle() {
    if (this.settingParams.taskID === taskType.getup) {
      return commonConst.GET_UP_TIME_RANGE
    }
    if (this.settingParams.taskID === taskType.sleepEarly) {
      return commonConst.SLEEP_TIME_RANGE
    }
    return ''
  }

  setTargetValue() {
    if (this.settingParams.taskID === taskType.getup) {
      if (!this.compareTime(commonConst.GET_UP_EARLY_TIME, commonConst.GET_UP_LATE_TIME)) {
        return
      }
      this.settingParams.targetValue = this.currentTime
      return
    }

    if (this.settingParams?.taskID === taskType.sleepEarly) {
      if (!this.compareTime(commonConst.SLEEP_EARLY_TIME, commonConst.SLEEP_LATE_TIME)) {
        return
      }
      this.settingParams.targetValue = this.currentTime
      return
    }
    this.settingParams.targetValue = this.currentValue
  }

  compareTime(startTime: string, endTime: string) {
    if (returnTimeStamp(this.currentTime) < returnTimeStamp(startTime) ||
      returnTimeStamp(this.currentTime) > returnTimeStamp(endTime)) {

      promptAction.showToast({
        message: commonConst.CHOOSE_TIME_OUT_RANGE
      })
      return false
    }
    return true
  }

  build() {
    Column() {
      Row() {
        Text($r('app.string.target_setting')).fontSize(commonConst.DEFAULT_20).margin({ right: commonConst.DEFAULT_12 })
        Text(this.createSubTitle())
          .fontSize(commonConst.DEFAULT_16)
      }
      .width(commonConst.THOUSANDTH_1000)
      .justifyContent(FlexAlign.Start)

      if ([taskType.getup, taskType.sleepEarly].indexOf(this.settingParams.taskID) > commonConst.HAS_NO_INDEX) {
        TimePicker({
          selected: commonConst.DEFAULT_SELECTED_TIME
        })
          .height(commonConst.THOUSANDTH_800)
          .useMilitaryTime(true)
          .onChange((value: TimePickerResult) => {
            this.currentTime = formatTime(value)
          })
      } else {
        TextPicker({ range: this.settingParams?.taskID === taskType.drinkWater ? this.drinkRange : this.appleRange,
          value: this.settingParams.targetValue })
          .width(commonConst.THOUSANDTH_900)
          .height(commonConst.THOUSANDTH_800)
          .onChange((value: string | string[]) => {
            this.currentValue = (value as string)?.split(' ')[0]
          })
      }

      Row() {
        Text($r('app.string.cancel')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.currentTime = commonConst.DEFAULT_TIME;
            this.currentValue = commonConst.DEFAULT_TEXT;
            this.controller.close()
          })

        Text($r('app.string.confirm')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.setTargetValue()
            this.controller.close()
          })
      }
      .justifyContent(FlexAlign.SpaceAround)
      .width(commonConst.THOUSANDTH_1000)
      .height(commonConst.DEFAULT_28)
      .margin({ bottom: commonConst.DEFAULT_20 })
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

@CustomDialog
export struct RemindTimeDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: RemindTimeDialog()
  })

  currentTime: string = commonConst.DEFAULT_TIME

  @Consume settingParams: TaskListItem

  build() {
    Column() {
      Column() {
        Text($r('app.string.remind_time'))
          .fontSize(commonConst.DEFAULT_20)
          .margin({ top: commonConst.DEFAULT_10 })
          .width(commonConst.THOUSANDTH_1000)
          .textAlign(TextAlign.Start)
      }
      .width(commonConst.THOUSANDTH_900)

      TimePicker({
        selected: commonConst.DEFAULT_SELECTED_TIME
      })
        .height(commonConst.THOUSANDTH_800)
        .useMilitaryTime(true)
        .onChange((value: TimePickerResult) => {
          this.currentTime = formatTime(value);
        })

      Row() {
        Text($r('app.string.cancel')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.currentTime = commonConst.DEFAULT_TIME
            this.controller.close()
          })

        Text($r('app.string.confirm')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.settingParams.startTime = this.currentTime
            this.controller.close()
          })
      }
      .justifyContent(FlexAlign.SpaceAround)
      .width(commonConst.THOUSANDTH_1000)
      .height(commonConst.DEFAULT_28)
      .margin({ bottom: commonConst.DEFAULT_20 })
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_560)
    .padding(commonConst.DEFAULT_12)
  }
}

// 1.实现频率设置弹窗
@CustomDialog
export struct FrequencyDialog {
  controller: CustomDialogController = new CustomDialogController({
    builder: FrequencyDialog()
  })

  private frequencyChooseRange: FrequencyContentType[] = frequencyRange()

  private currentFrequency: string = commonConst.EVERYDAY

  @Consume settingParams: TaskListItem

  @Consume frequency: string

  setFrequency() {
    const checkedArr = this.frequencyChooseRange.filter((item: FrequencyContentType) => item.isChecked)

    if (checkedArr.length === this.frequencyChooseRange.length || checkedArr.length === commonConst.NO_LENGTH) {
      this.currentFrequency = commonConst.EVERYDAY
      this.settingParams.frequency = commonConst.INIT_WEEK_IDS
      return
    }

    this.currentFrequency = checkedArr.reduce((sum: string, current: FrequencyContentType) => {
      return sum + ' ' + current.label
    }, '')

    this.settingParams.frequency = checkedArr.reduce((sum: string, current: FrequencyContentType) => {
      return sum === '' ? sum + current.id : sum + ',' + current.id
    }, '')
  }

  build() {
    Column() {
      Column() {
        Text($r('app.string.set_your_frequency'))
          .fontSize(commonConst.DEFAULT_20)
          .margin({ top: commonConst.DEFAULT_10 })
          .width(commonConst.THOUSANDTH_1000)
          .textAlign(TextAlign.Start)
      }
      .width(commonConst.THOUSANDTH_900)

      List() {
        ForEach(this.frequencyChooseRange, (item: FrequencyContentType) => {
          ListItem() {
            Row() {
              Text(item?.label).fontSize(commonConst.DEFAULT_20)
              Toggle({ type: ToggleType.Checkbox })
                .onChange((isOn: boolean) => {
                  item.isChecked = isOn
                })
            }
            .width(commonConst.THOUSANDTH_1000)
            .justifyContent(FlexAlign.SpaceBetween)
            .height(commonConst.DEFAULT_60)
          }
        })
      }
      .divider({
        strokeWidth: commonConst.DEFAULT_2,
        color: $r('app.color.btnBgColor')
      })
      .flexGrow(1)
      .padding(commonConst.DEFAULT_12)
      .width(commonConst.THOUSANDTH_1000)

      Row() {
        Text($r('app.string.cancel')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.controller.close()
          })

        Text($r('app.string.confirm')).fontSize(commonConst.DEFAULT_20).fontColor($r('app.color.blueColor'))
          .onClick(() => {
            this.setFrequency()
            this.frequency = this.currentFrequency
            this.controller.close()
          })
      }
      .justifyContent(FlexAlign.SpaceAround)
      .width(commonConst.THOUSANDTH_900)
      .height(commonConst.DEFAULT_28)
      .margin({ bottom: commonConst.DEFAULT_16 })
    }
    .justifyContent(FlexAlign.Center)
    .height(commonConst.THOUSANDTH_940)
    .padding(commonConst.DEFAULT_12)
  }
}

定义频率设置视图模型

TypeScript 复制代码
// ets/viewModel/FrequencySetting.ets

import { FrequencyContentType } from "../model/TaskInitList"

export function padTo2Digits(num: number) {
  return num.toString().padStart(2, '0')
}

const chineseNumOfWeek: string[] = ['一', '二', '三', '四', '五', '六', '日']
const WEEK: string = '星期'

export const frequencyRange = () => {
  const frequencyRangeArr: FrequencyContentType[] = []
  chineseNumOfWeek.forEach((item: string, index: number) => {
    frequencyRangeArr.push({
      id: (index + 1),
      label: `${WEEK}${item}`,
      isChecked: false
    })
  })
  return frequencyRangeArr
}

基础知识:切换按钮 (Toggle)

Toggle组件提供状态按钮样式、勾选框样式和开关样式,一般用于两种状态之间的切换。

创建切换按钮

Toggle通过调用接口来创建,接口调用形式如下:

TypeScript 复制代码
Toggle(options: { type: ToggleType, isOn?: boolean })

其中,ToggleType为开关类型,包括Button、Checkbox和Switch,isOn为切换按钮的状态。

API version 11开始,Checkbox默认样式由圆角方形变为圆形。

接口调用有以下两种形式:

  • 创建不包含子组件的Toggle。

当ToggleType为Checkbox或者Switch时,用于创建不包含子组件的Toggle:

TypeScript 复制代码
Toggle({ type: ToggleType.Checkbox, isOn: false })
Toggle({ type: ToggleType.Checkbox, isOn: true })
TypeScript 复制代码
Toggle({ type: ToggleType.Switch, isOn: false })
Toggle({ type: ToggleType.Switch, isOn: true })
  • 创建包含子组件的Toggle。

当ToggleType为Button时,只能包含一个子组件,如果子组件有文本设置,则相应的文本内容会显示在按钮上。

TypeScript 复制代码
Toggle({ type: ToggleType.Button, isOn: false }) {
  Text('status button')
    .fontColor('#182431')
    .fontSize(12)
}.width(100)
Toggle({ type: ToggleType.Button, isOn: true }) {
  Text('status button')
    .fontColor('#182431')
    .fontSize(12)
}.width(100)

自定义样式

  • 通过selectedColor属性设置Toggle打开选中后的背景颜色。
TypeScript 复制代码
Toggle({ type: ToggleType.Button, isOn: true }) {
  Text('status button')
    .fontColor('#182431')
    .fontSize(12)
}.width(100).selectedColor(Color.Pink)
Toggle({ type: ToggleType.Checkbox, isOn: true })
  .selectedColor(Color.Pink)
Toggle({ type: ToggleType.Switch, isOn: true })
  .selectedColor(Color.Pink)
  • 通过switchPointColor属性设置Switch类型的圆形滑块颜色,仅对type为ToggleType.Switch生效。
TypeScript 复制代码
Toggle({ type: ToggleType.Switch, isOn: false })
  .switchPointColor(Color.Pink)
Toggle({ type: ToggleType.Switch, isOn: true })
  .switchPointColor(Color.Pink)

添加事件

除支持通用事件外,Toggle还用于选中和取消选中后触发某些操作,可以绑定onChange事件来响应操作后的自定义行为。

TypeScript 复制代码
Toggle({ type: ToggleType.Switch, isOn: false })
  .onChange((isOn: boolean) => {
    if(isOn) {
      // 需要执行的操作
    }
  })

案例整理

TypeScript 复制代码
// ets/pages/toggle/usagePage.ets

@Entry
@Component
struct usagePage {
  @State isOn: boolean = true

  build() {
    Column({ space: 20 }) {
      Row() {
        Toggle({ type: ToggleType.Checkbox, isOn: false })
        Toggle({ type: ToggleType.Checkbox, isOn: true })
      }

      Row() {
        Toggle({ type: ToggleType.Switch, isOn: false })
        Toggle({ type: ToggleType.Switch, isOn: true })
      }

      Row() {
        Toggle({ type: ToggleType.Button, isOn: false }) {
          Text('status button')
            .fontColor('#182431')
            .fontSize(12)
        }.width(100)
        Toggle({ type: ToggleType.Button, isOn: true }) {
          Text('status button')
            .fontColor('#182431')
            .fontSize(12)
        }.width(100)
      }

      Row() {
        Toggle({ type: ToggleType.Button, isOn: true }) {
          Text('status button')
            .fontColor('#182431')
            .fontSize(12)
        }.width(100).selectedColor(Color.Orange)

        Toggle({ type: ToggleType.Checkbox, isOn: true })
          .selectedColor(Color.Orange)

        Toggle({ type: ToggleType.Switch, isOn: true })
          .selectedColor(Color.Orange)
      }

      Row() {
        Toggle({ type: ToggleType.Switch, isOn: false })
          .switchPointColor(Color.Orange)
        Toggle({ type: ToggleType.Switch, isOn: true })
          .switchPointColor(Color.Orange)
      }

      Row() {
        Toggle({ type: ToggleType.Switch, isOn: this.isOn })
          .onChange((isOn: boolean) => {
              this.isOn = !this.isOn
          })
        Text(`${this.isOn}`)
      }

    }
    .width('100%')
  }
}

场景示例

Toggle用于切换蓝牙开关状态。

TypeScript 复制代码
// ets/pages/toggle/CasePage.ets

import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct CasePage {
  @State BOnSt: promptAction.ShowToastOptions = { 'message': 'Bluetooth is on.' }
  @State BOffSt: promptAction.ShowToastOptions = { 'message': 'Bluetooth is off.' }

  build() {
    Column() {
      Row() {
        Text("Bluetooth Mode")
          .height(50)
          .fontSize(16)
      }

      Row() {
        Text("Bluetooth")
          .height(50)
          .padding({ left: 10 })
          .fontSize(16)
          .textAlign(TextAlign.Start)
          .backgroundColor(0xFFFFFF)
        
        Toggle({ type: ToggleType.Switch })
          .margin({ left: 200, right: 10 })
          .onChange((isOn: boolean) => {
            if (isOn) {
              promptAction.showToast(this.BOnSt)
            } else {
              promptAction.showToast(this.BOffSt)
            }
          })
      }
      .backgroundColor(0xFFFFFF)
    }
    .padding(10)
    .backgroundColor(0xDCDCDC)
    .width('100%')
    .height('100%')
  }
}

基础知识:学习Picker选择器

CalendarPicker

示例

复制代码
// ets/pages/picker/CalendarPickerPage.ets

@Entry
@Component
struct CalendarPickerPage {
  private selectedDate: Date = new Date('2025-03-05')

  build() {
    Column() {
      Text('月历日期选择器').fontSize(30)
      Column() {
        CalendarPicker({ hintRadius: 10, selected: this.selectedDate })
          .edgeAlign(CalendarAlign.END)
          .textStyle({ color: "#182431", font: { size: 20, weight: FontWeight.Normal } })
          .margin(10)
          .onChange((value) => {
            console.info("CalendarPicker onChange:" + JSON.stringify(value))
          })
      }
      .alignItems(HorizontalAlign.End)
      .width("100%")
    }.width('100%')
  }
}

DatePicker

示例

复制代码
// ets/pages/picker/DatePickerPage.ets

@Entry
@Component
struct DatePickerPage {
  @State isLunar: boolean = false
  private selectedDate: Date = new Date('2025-08-08')

  build() {
    Column() {
      Button('切换公历农历')
        .margin({ top: 30, bottom: 30 })
        .onClick(() => {
          this.isLunar = !this.isLunar
        })

      DatePicker({
        start: new Date('1970-1-1'),
        end: new Date('2100-1-1'),
        selected: this.selectedDate
      })
        .disappearTextStyle({ color: Color.Gray, font: { size: '16fp', weight: FontWeight.Bold } })
        .textStyle({ color: '#182431', font: { size: '18fp', weight: FontWeight.Normal } })
        .selectedTextStyle({ color: '#0000FF', font: { size: '26fp', weight: FontWeight.Regular } })
        .lunar(this.isLunar)
        .onDateChange((value: Date) => {
          this.selectedDate = value
          console.info('select current date is: ' + value.toString())
        })
    }.width('100%')
  }
}

TextPicker

示例1(设置选择器列数)

该示例通过配置range实现单列或多列文本选择器。

复制代码
// ets/pages/picker/TextPicker01Page.ets

class bottom {
  bottom: number = 50
}

let bott: bottom = new bottom()

@Entry
@Component
struct TextPicker01Page {
  private select: number = 1
  private apFruits: string[] = ['apple1', 'apple2', 'apple3', 'apple4']
  private orFruits: string[] = ['orange1', 'orange2', 'orange3', 'orange4']
  private peFruits: string[] = ['peach1', 'peach2', 'peach3', 'peach4']
  private multi: string[][] = [this.apFruits, this.orFruits, this.peFruits]
  private cascade: TextCascadePickerRangeContent[] = [
    {
      text: '辽宁省',
      children: [{ text: '沈阳市', children: [{ text: '沈河区' }, { text: '和平区' }, { text: '浑南区' }] },
        { text: '大连市', children: [{ text: '中山区' }, { text: '金州区' }, { text: '长海县' }] }]
    },
    {
      text: '吉林省',
      children: [{ text: '长春市', children: [{ text: '南关区' }, { text: '宽城区' }, { text: '朝阳区' }] },
        { text: '四平市', children: [{ text: '铁西区' }, { text: '铁东区' }, { text: '梨树县' }] }]
    },
    {
      text: '黑龙江省',
      children: [{ text: '哈尔滨市', children: [{ text: '道里区' }, { text: '道外区' }, { text: '南岗区' }] },
        { text: '牡丹江市', children: [{ text: '东安区' }, { text: '西安区' }, { text: '爱民区' }] }]
    }
  ]

  build() {
    Column() {
      TextPicker({ range: this.apFruits, selected: this.select })
        .onChange((value: string | string[], index: number | number[]) => {
          console.info('Picker item changed, value: ' + value + ', index: ' + index)
        }).margin(bott)
      TextPicker({ range: this.multi })
        .onChange((value: string | string[], index: number | number[]) => {
          console.info('TextPicker 多列:onChange ' + JSON.stringify(value) + ', ' + 'index: ' + JSON.stringify(index))
        }).margin(bott)
      TextPicker({ range: this.cascade })
        .onChange((value: string | string[], index: number | number[]) => {
          console.info('TextPicker 多列联动:onChange ' + JSON.stringify(value) + ', ' + 'index: ' +
          JSON.stringify(index))
        })
    }
  }
}

示例2(设置文本样式)

该示例通过配置disappearTextStyle、textStyle、selectedTextStyle实现文本选择器中的文本样式。

复制代码
// ets/pages/picker/TextPicker02Page.ets

@Entry
@Component
struct TextPicker02Page {
  private select: number = 1
  private fruits: string[] = ['apple1', 'orange2', 'peach3', 'grape4']

  build() {
    Column() {
      TextPicker({ range: this.fruits, selected: this.select })
        .onChange((value: string | string[], index: number | number[]) => {
          console.info('Picker item changed, value: ' + value + ', index: ' + index)
        })
        .disappearTextStyle({ color: Color.Red, font: { size: 15, weight: FontWeight.Lighter } })
        .textStyle({ color: Color.Black, font: { size: 20, weight: FontWeight.Normal } })
        .selectedTextStyle({ color: Color.Blue, font: { size: 30, weight: FontWeight.Bolder } })
    }
    .width('100%')
    .height('100%')
  }
}

示例3(设置无分割线样式)

该示例通过配置divider为null实现无分割线样式的文本选择器。

复制代码
// ets/pages/picker/TextPicker03Page.ets

@Entry
@Component
struct TextPicker03Page {
  private select: number = 1
  private fruits: string[] = ['apple1', 'orange2', 'peach3', 'grape4']

  build() {
    Column() {
      TextPicker({ range: this.fruits, selected: this.select })
        .onChange((value: string | string[], index: number | number[]) => {
          console.info('Picker item changed, value: ' + value + ', index: ' + index)
        })
        .disappearTextStyle({color: Color.Red, font: {size: 15, weight: FontWeight.Lighter}})
        .textStyle({color: Color.Black, font: {size: 20, weight: FontWeight.Normal}})
        .selectedTextStyle({color: Color.Blue, font: {size: 30, weight: FontWeight.Bolder}})
        .divider(null)
    }.width('100%').height('100%')
  }
}

示例4(设置分割线样式)

该示例通过配置divider的DividerOptions类型实现分割线样式的文本选择器。

复制代码
// ets/pages/picker/TextPicker04Page.ets

@Entry
@Component
struct TextPicker04Page {
  private select: number = 1
  private fruits: string[] = ['apple1', 'orange2', 'peach3', 'grape4']

  build() {
    Column() {
      TextPicker({ range: this.fruits, selected: this.select })
        .onChange((value: string | string[], index: number | number[]) => {
          console.info('Picker item changed, value: ' + value + ', index: ' + index)
        })
        .disappearTextStyle({ color: Color.Red, font: { size: 15, weight: FontWeight.Lighter } })
        .textStyle({ color: Color.Black, font: { size: 20, weight: FontWeight.Normal } })
        .selectedTextStyle({ color: Color.Blue, font: { size: 30, weight: FontWeight.Bolder } })
        .divider({
          strokeWidth: 10,
          color: Color.Red,
          startMargin: 10,
          endMargin: 20
        } as DividerOptions)
    }
    .width('100%')
    .height('100%')
  }
}

示例5(设置渐隐效果)

该示例通过gradientHeight自定义TextPicker的渐隐效果高度。

复制代码
// ets/pages/picker/TextPicker05Page.ets

@Entry
@Component
struct TextPicker05Page {
  private select: number = 1
  private fruits: string[] = ['apple1', 'orange2', 'peach3', 'grape4']

  build() {
    Column() {
      TextPicker({ range: this.fruits, selected: this.select })
        .onChange((value: string | string[], index: number | number[]) => {
          console.info('Picker item changed, value: ' + value + ', index: ' + index)
        })
        .disappearTextStyle({ color: Color.Red, font: { size: 15, weight: FontWeight.Lighter } })
        .textStyle({ color: Color.Black, font: { size: 20, weight: FontWeight.Normal } })
        .selectedTextStyle({ color: Color.Blue, font: { size: 30, weight: FontWeight.Bolder } })
        .gradientHeight(100)
    }.width('100%').height('100%')
  }
}

TimePicker

示例

复制代码
// ets/pages/picker/TimePicker.ets

@Entry
@Component
struct TimePickerExample {
  @State isMilitaryTime: boolean = false
  private selectedTime: Date = new Date('2025-07-22T08:00:00')

  build() {
    Column() {
      Button('切换12小时制/24小时制')
        .margin(30)
        .onClick(() => {
          this.isMilitaryTime = !this.isMilitaryTime
        })

      TimePicker({
        selected: this.selectedTime,
        format: TimePickerFormat.HOUR_MINUTE_SECOND
      })
        .useMilitaryTime(this.isMilitaryTime)
        .onChange((value: TimePickerResult) => {
          if(value.hour >= 0) {
            this.selectedTime.setHours(value.hour, value.minute)
            console.info(`${value}`)
          }
        })
        .disappearTextStyle({color: Color.Red, font: {size: 15, weight: FontWeight.Lighter}})
        .textStyle({color: Color.Black, font: {size: 20, weight: FontWeight.Normal}})
        .selectedTextStyle({color: Color.Blue, font: {size: 30, weight: FontWeight.Bolder}})

    }.width('100%')
  }
}
相关推荐
小草帽学编程18 分钟前
鸿蒙Next开发真机调试签名申请流程
android·华为·harmonyos
陈奕昆21 分钟前
5.2 HarmonyOS NEXT应用性能诊断与优化:工具链、启动速度与功耗管理实战
华为·harmonyos
哼唧唧_5 小时前
React Native开发鸿蒙运动健康类应用的项目实践记录
react native·harmonyos·harmony os5·运动健康
二流小码农12 小时前
鸿蒙开发:实现一个标题栏吸顶
android·ios·harmonyos
坚果的博客12 小时前
uniappx插件nutpi-idcard 开发与使用指南(适配鸿蒙)
华为·harmonyos
程序员小刘12 小时前
【HarmonyOS 5】 社交行业详解以及 开发案例
华为·harmonyos
软件测试小仙女12 小时前
鸿蒙APP测试实战:从HDC命令到专项测试
大数据·软件测试·数据库·人工智能·测试工具·华为·harmonyos
程序员小刘12 小时前
【HarmonyOS 5】 影视与直播详以及 开发案例
华为·harmonyos
程序员小刘13 小时前
鸿蒙【HarmonyOS 5】 (React Native)的实战教程
react native·华为·harmonyos