鸿蒙自定义组件(自定义时间选择器)


前言

鸿蒙官方提供了大量组件,使用起来很方便,但是在开发时为了统一样式自定义样式是必不可少的,今天在开发时遇到了需要自定义组件的时间选择器,因为博主使用自定义组件比较少所以不太熟练,在查阅官方文档中废了不少时间,所有编写了这个博客来记录一下如何使用和使用中踩到的坑,希望可以帮助到你
实现效果图:

在这里插入图片描述

1.自定义弹窗逻辑梳理

Created with Raphaël 2.3.0 开始(进入TimeRangeDialog组件) 初始化数据与控制器 构建整体UI(Column布局) 构建底部按钮区域(Row布局) 构建“确定”按钮(复用black_button组件,宽度100%) 点击确定按钮→调用validateTimeRange()验证时间 验证时间范围(endTime > startTime?) 验证成功→调用confirm回调(传递startTime和endTime) 调用controller.close()关闭对话框 结束(关闭对话框) 验证失败→调用promptAction.showToast()提示“结束时间必须晚于开始时间”(时长2000ms) yes no

流程图讲解

一、初始化阶段:组件启动的基础准备

流程起点为 进入 TimeRangeDialog 组件,首先执行 数据与控制器初始化(init_data),这是组件功能正常运行的前提,具体包含 4 个关键子步骤:

  1. 控制器初始化(init_controller):创建 SwiperController,用于后续控制“开始时间/结束时间”滑动视图的切换(如 showNext()showPrevious() 方法)。
  2. 状态变量初始化(init_state):定义两个核心状态变量:
    • currentIndex:默认值 0,用于标记 Tabs 组件(日期选择)当前选中的索引(0 = 今天、1 = 明天、2 = 后天)。
    • currentnuber:默认值 0,用于标记 Swiper 组件(时间视图)当前显示的页面(0 = 开始时间视图、1 = 结束时间视图)。
  3. 时间状态初始化(init_time):预设 4 个时间变量,为用户提供合理初始值:
    • startTime:默认当前时间,记录用户最终选择的开始时间。
    • endTime:默认当前时间 + 1 小时,记录用户最终选择的结束时间。
    • oneTomorrow:当前时间 + 1 天,对应“明天” Tab 的日期。
    • dayAfterTomorrow:当前时间 + 2 天,对应“后天” Tab 的日期(显示为具体“X 月 X 日”文本)。
  4. 回调初始化(init_callback):定义 confirm 回调函数,用于将用户最终选择的 startTimeendTime 传递给父组件,实现数据回传。

二、UI 构建阶段:分层搭建交互界面

初始化完成后进入 整体 UI 构建(build_ui),采用鸿蒙常用的 Column 纵向布局,将界面分为“标题栏”“时间选择区域”“底部按钮区” 3 个核心模块:

1. 标题栏模块(build_title):基础导航与关闭功能

  • 标题显示(show_title_text):展示“选择预约时段”文本,配置字体大小 18、中等粗细、颜色 #18181B,并居中对齐。
  • 关闭按钮(build_close_btn):使用透明背景按钮包裹“删除图标”(homedelete),点击后调用 controller.close() 直接关闭对话框(close_dialog)。

2. 时间选择区域(build_time_area):核心交互区,分 3 层设计

该区域通过“按钮切换 + 滑动视图 + 日期 Tabs + 时间选择器”实现功能:

(1)顶层:时间视图切换按钮(build_time_btn_row)

采用 Row 均匀布局,包含 3 个元素:

  • 开始时间按钮(build_start_btn):显示“开始时间”标签(蓝色背景、圆角样式)和当前 startTime(如“3 月 20 日 14:30”),点击后触发 btn_click_start:将 currentnuber 设为 0,并调用 swiperController.showNext() 切换到开始时间视图。
  • 分隔箭头(show_arrow):使用“右半箭头图标”(homeRight_half_arrow)分隔两个按钮。
  • 结束时间按钮(build_end_btn):显示 endTime,点击后触发 btn_click_end:将 currentnuber 设为 1,调用 swiperController.showPrevious() 切换到结束时间视图。

(2)中层:Swiper 滑动视图(build_swiper)

绑定初始化的 SwiperController

  • 滑动页内容:两个页面均通过 timeSelection() 方法构建(参数 id 用于区分开始/结束)。
  • 滑动响应(swiper_change):用户手动滑动时触发 onChange 事件,更新 currentnuber 为当前索引。
  • 指示器(set_swiper_indicator):使用 DotIndicator 显示位置(选中/未选中状态均为白色)。
    滑动效果:
    在这里插入图片描述

(3)底层:日期 Tabs 与时间选择器(build_time_selection)

通过 Tabs 纵向布局(vertical=true)实现:

  • Tabs 基础配置(tabs_vertical):设置 Tab 栏在左侧(barPosition=Start)、高度 250、宽度 80、背景色 #f0f3f8。

  • 3 个日期 Tab

    • tab_today(今天):文本为“今天”,内容调用 timeSelectionutensil() 构建时间选择器。
    • tab_tomorrow(明天):文本为“明天”,内容同上。
    • tab_day_after(后天):文本为 dayAfterTomorrow 的“X 月 X 日”格式,内容同上。
      在这里插入图片描述
  • Tab 样式(build_tab_name):根据 currentIndex 切换背景色(选中为 #ffffff,未选中为透明)。

  • Tab 切换响应(tabs_change):用户点击 Tab 时触发 onChange 事件,更新 currentIndex,并进入 update_time_by_tab 逻辑:

    • currentIndex=0(今天):调用 copyDateTime(startDate, 布尔值),赋值给 startTimeendTime(基于 currentnuber)。
    • currentIndex=1(明天):使用 oneTomorrow 赋值。
    • currentIndex=2(后天):使用 dayAfterTomorrow 赋值。
  • copyDateTime 方法(copy_date_time):复制源日期的年、月、日、时、分到目标时间,确保时间修改独立性。

(4)时间选择器(build_time_picker)

每个 Tab 内容区通过 timeSelectionutensil() 构建:

  • 基础配置(time_picker_config):默认选中时间为 startDate(当前时间),启用 24 小时制(useMilitaryTime=true)。
  • 选择响应(time_picker_change):用户调整时间时触发 onChange 事件:
    • currentnuber=0:更新 startTime 的小时(setHours)和分钟(setMinutes)。
    • currentnuber=1:更新 endTime 的小时和分钟。
      在这里插入图片描述

3. 底部按钮区(build_bottom_btn):确认选择与时间验证

采用 Row 布局:

  • 按钮构建(build_confirm_btn):复用自定义组件 black_button,设置宽度 100%、文本“确定”。
  • 点击响应(btn_click_confirm):点击后执行 validateTimeRange() 验证:
    • 验证失败:若 startTime 时间戳 ≥ endTime 时间戳,调用 promptAction.showToast() 弹出提示“结束时间必须晚于开始时间”(时长 2000ms)。
    • 验证成功:调用 confirm 回调函数传递数据,随后调用 controller.close() 关闭对话框(close_after_confirm)。
      验证成功:
      在这里插入图片描述
      验证失败:
      在这里插入图片描述

三、核心逻辑总结:状态同步与交互闭环

整个流程的核心是“状态变量驱动视图,视图交互更新状态”,关键闭环包括:

1.视图切换闭环currentnuber 关联 Swiper 视图,通过按钮点击或手动滑动更新状态。
2. 日期选择闭环currentIndex 关联 Tabs 选中项,切换时通过 copyDateTime 同步更新日期。
3. 时间验证闭环:确认前强制验证时间范围,失败时提示修正,成功后才回传数据并关闭组件。 1. 初始化 TimeRangeDialog

四、TimeRangeDialog(时间范围选择弹窗)使用步骤

一、前置准备:确认依赖与环境

在使用组件前,需确保项目环境和依赖满足以下条件,避免出现引用错误:

  • API 版本适配:组件基于鸿蒙 ArkTS 开发,需确保项目的 minSdkVersion 支持 @ohos.promptAction(弹窗提示)、CustomDialog(自定义弹窗)、Swiper(滑动容器)等组件,建议使用 API Version 9 及以上。
  • 依赖组件引入
    • 组件依赖 @ohos.promptAction(用于时间校验失败的 Toast 提示),无需额外安装,直接在页面头部导入即可。
    • 组件依赖自定义按钮组件 black_button(路径为 ./ButtonuUI),需确保项目中存在该组件文件,且组件接收 width1(宽度)和 text(按钮文本)两个参数。
  • 资源文件确认:组件中引用了两张图片资源(app.media.homedelete 关闭图标、app.media.homeRight_half_arrow 箭头图标),需在项目的 main_pages.json 对应的资源目录中添加这两张图片,或替换为项目中已有的同类资源。

二、步骤1:导入 TimeRangeDialog 组件

在需要使用“时间范围选择弹窗”的页面(如预约页面)中,首先导入 TimeRangeDialog 组件和 promptAction(若页面未导入),代码如下:

// 导入弹窗提示工具(用于时间校验失败提示)
import promptAction from '@ohos.promptAction';
// 导入自定义时间范围弹窗组件(路径需与组件实际存放路径一致)
import { TimeRangeDialog } from './TimeRangeDialog'; // 假设 TimeRangeDialog 存放在当前页面同级目录

三、步骤2:创建 CustomDialogController 实例

通过 CustomDialogController 控制 TimeRangeDialog 的显示/隐藏、弹窗样式(位置、背景、圆角等),并绑定“确认选择”后的回调函数。

核心代码示例:

在页面的结构体(struct)中定义 dialogController 变量,作为弹窗的控制器:

@Entry
@Component
struct ReservationPage { // 示例:预约页面
  // 1. 定义弹窗控制器,配置弹窗样式与回调
  dialogController: CustomDialogController = new CustomDialogController({
    // 绑定 TimeRangeDialog 组件,传入 confirm 回调(核心:接收选择的开始/结束时间)
    builder: TimeRangeDialog({
      confirm: (startTime: Date, endTime: Date) => {
        // 这里是“用户点击确定按钮后”的逻辑,startTime 和 endTime 为用户选择的时间
        this.handleConfirm(startTime, endTime); 
      }
    }),
    // 弹窗位置:底部弹出(符合常见选择器交互)
    alignment: DialogAlignment.Bottom,
    // 是否允许点击弹窗外部关闭:false(需用户主动点击关闭按钮或确定按钮)
    autoCancel: false,
    // 弹窗背景蒙版颜色:半透明黑色(遮罩页面,突出弹窗)
    maskColor: 'rgba(0, 0, 0, 0.3)',
    // 弹窗宽度:占满屏幕宽度
    width: '100%',
    // 弹窗圆角:仅顶部左右圆角(底部直角,贴合底部弹窗样式)
    cornerRadius: {
      topLeft: 10,
      topRight: 10,
      bottomLeft: 0,
      bottomRight: 0
    }
  });

  // 2. 页面构建(示例:添加“打开时间选择弹窗”的按钮)
  build() {
    Column({ space: 20 }) {
      // 触发弹窗的按钮(可根据实际页面样式调整)
      Button('选择预约时间')
        .width(200)
        .height(45)
        .backgroundColor('#3092f3')
        .fontColor(Color.White)
        .borderRadius(8)
        .onClick(() => {
          // 点击按钮,打开时间选择弹窗
          this.dialogController.open();
        });

      // 其他页面内容...
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  // 3. 定义“确认选择”的回调处理函数(用户点击弹窗“确定”后执行)
  private handleConfirm(startTime: Date, endTime: Date) {
    // 示例1:打印选择的时间(便于调试)
    console.log(`用户选择的开始时间:${startTime.toLocaleString()}`);
    console.log(`用户选择的结束时间:${endTime.toLocaleString()}`);

    // 示例2:后续业务逻辑(如调用“预约电桩”接口)
    // this.callReservationApi(startTime, endTime); // 需自行实现接口调用逻辑
  }
}

四、步骤3:使用弹窗进行时间选择(用户操作流程)

用户点击“选择预约时间”按钮后,弹窗会从页面底部弹出,用户可按以下流程完成时间选择:

1. 切换“开始时间/结束时间”选择页

弹窗中部通过 Swiper(滑动容器) 分为两个页面:

  • 左侧:开始时间选择页(默认显示)
  • 右侧:结束时间选择页
  • 切换方式:
    • 点击弹窗中部的“开始时间”或“结束时间”按钮,自动滑动到对应页面;
    • 直接用手指左右滑动弹窗中部区域,切换页面。

2. 选择日期(今天/明天/后天)

弹窗时间选择区顶部有 Tabs(标签页),提供3个日期选项:

  • Tab1:今天(默认选中)
  • Tab2:明天
  • Tab3:后天(显示为“X月X日”格式,自动计算当前日期后2天)
  • 操作:点击对应 Tab,即可切换到目标日期(切换后时间选择器会同步更新为该日期)。

3. 选择具体时间(小时/分钟)

日期切换后,下方会显示 TimePicker(时间选择器),支持:

  • 24小时制(组件已通过 useMilitaryTime(true) 开启);
  • 滑动选择“小时”和“分钟”(选择后实时更新弹窗中部的时间显示)。

4. 确认/取消选择

  • 确认选择:点击弹窗底部的“确定”按钮,组件会先校验时间有效性(结束时间必须晚于开始时间):
    • 校验通过:关闭弹窗,并通过 confirm 回调将选择的 startTime(开始时间)和 endTime(结束时间)传递给页面;
    • 校验失败:不关闭弹窗,弹出 Toast 提示“结束时间必须晚于开始时间”。
  • 取消选择:点击弹窗顶部右侧的“关闭图标”(homedelete 图片),直接关闭弹窗,不触发任何回调。

五、关键注意事项

  1. 时间格式处理confirm 回调返回的 startTimeendTimeDate 类型,若需传递给接口(如转为“yyyy-MM-dd HH:mm”字符串),需自行处理格式,示例代码:
    private formatDate(date: Date): string {
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,补0为两位数
      const day = String(date.getDate()).padStart(2, '0');
      const hour = String(date.getHours()).padStart(2, '0');
      const minute = String(date.getMinutes()).padStart(2, '0');
      return `${year}-${month}-${day} ${hour}:${minute}`;
    }
    
  2. 默认时间配置:组件默认“结束时间为当前时间+1小时”,若需修改默认时间(如默认结束时间为开始时间+2小时),可调整 TimeRangeDialogendDate 的初始化代码:
    // 原代码:默认结束时间=当前时间+1小时
    @State endDate: Date = new Date(Date.now() + 3600000); 
    // 修改为:默认结束时间=当前时间+2小时(7200000毫秒)
    @State endDate: Date = new Date(Date.now() + 7200000); 
    
  3. 样式自定义:若需修改弹窗颜色、字体大小、按钮样式等,可直接修改 TimeRangeDialog 组件内部的样式代码(如标题字体颜色 #18181B、按钮背景色 #3092f3 等)。

源码获取

gitee源码拉取下来即可使用

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐