ArkUI CalendarPicker 组件完整知识库
版本: 1.0 更新时间: 2026-02-02 基于: OpenHarmony ace_engine (master 分支)
目录
1. 架构概述
1.1 系统架构
CalendarPicker 组件实现了一个日期选择入口,采用 4 层架构:
ArkTS 前端
↓
桥接层 (CalendarModifier) - 属性解析和应用
↓
模式层 (CalendarPickerPattern) - 业务逻辑和状态管理
↓
对话框层 (CalendarDialogPattern) - 日历弹窗
↓
日历组件 (Calendar) - 日历视图
设计原则:
- 入口模式: 轻量级入口,点击后弹出完整日历对话框
- 边框对齐: 支持对话框边缘对齐 (START/CENTER/END)
- 键盘支持: 完整的键盘导航和数字输入
- 日期范围: 灵活的起始/结束日期配置
1.2 组件结构
CalendarPicker (入口组件)
├── 文本区域 (年/月/日)
├── 加减按钮
└── 点击事件 → 弹出 CalendarDialog
2. 核心类详解
2.1 CalendarPickerPattern
职责:
- 管理日期数据状态
- 处理键盘输入
- 处理鼠标点击和悬停
- 管理对话框弹出
关键成员变量:
CalendarSettingData calendarData_; // 日历数据
CalendarPickerSelectedType selected_; // 当前选中类型 (年/月/日)
CalendarEdgeAlign align_; // 对齐方式
DimensionOffset offset_; // 对话框偏移
int32_t yearIndex_ = 0; // 文本索引
int32_t monthIndex_ = 2;
int32_t dayIndex_ = 4;
bool isDialogShow_ = false; // 对话框显示状态
bool isKeyWaiting_ = false; // 等待键盘输入状态
bool isMarkToday_ = false; // 是否标记今天
uint32_t yearEnterCount_ = 0; // 年份输入计数
uint32_t monthPrefixZeroCount_ = 0; // 月份前导零计数
uint32_t dayPrefixZeroCount_ = 0; // 日期前导零计数
关键方法:
-
OnModifyDone(): 初始化组件
void CalendarPickerPattern::OnModifyDone() { LinearLayoutPattern::OnModifyDone(); // 初始化日期索引 InitDateIndex(); // 初始化事件 InitClickEvent(); InitOnKeyEvent(); InitOnHoverEvent(); // 更新样式 FlushTextStyle(); UpdateEdgeAlign(); } -
HandleClickEvent(): 处理点击事件
void CalendarPickerPattern::HandleClickEvent(const Offset& globalLocation) { // 检查点击区域 CalendarPickerSelectedType type = CheckRegion(globalLocation); switch (type) { case CalendarPickerSelectedType::YEAR: case CalendarPickerSelectedType::MONTH: case CalendarPickerSelectedType::DAY: SetSelectedType(type); ShowDialog(); break; case CalendarPickerSelectedType::ADDBTN: HandleAddButtonClick(); break; case CalendarPickerSelectedType::SUBBTN: HandleSubButtonClick(); break; default: break; } } -
HandleNumberKeyEvent(): 处理数字键输入
bool CalendarPickerPattern::HandleNumberKeyEvent(const KeyEvent& event) { uint32_t number = event.GetKeyCode() - KeyCode::KEY_0; switch (selected_) { case CalendarPickerSelectedType::YEAR: return HandleYearKeyEvent(number); case CalendarPickerSelectedType::MONTH: return HandleMonthKeyEvent(number); case CalendarPickerSelectedType::DAY: return HandleDayKeyEvent(number); default: return false; } } -
HandleYearKeyEvent(): 年份输入处理
bool CalendarPickerPattern::HandleYearKeyEvent(uint32_t number) { auto& date = calendarData_.selectedDate; return HandleYearKeyWaitingEvent(number, [this, &date]() { // task yearEnterCount_++; auto year = date.GetYear(); // 构建新年份 if (yearEnterCount_ < 4) { year = year * 10 + number; } else { year = (year / 1000) * 1000 + number; } date.SetYear(year); UpdateEntryButtonColor(); }, [this, &date]() { // zeroStartTask yearPrefixZeroCount_++; if (yearPrefixZeroCount_ >= 2) { // 00xx → 2000 + xx yearEnterCount_ = 0; yearPrefixZeroCount_ = 0; isKeyWaiting_ = false; } } ); } -
HandleAddButtonClick(): 加按钮处理
void CalendarPickerPattern::HandleAddButtonClick() { auto& date = calendarData_.selectedDate; switch (selected_) { case CalendarPickerSelectedType::YEAR: date.AddYears(1); break; case CalendarPickerSelectedType::MONTH: date.AddMonths(1); // 检查是否超出范围 AdjustSolarDate(date, calendarData_.startDate, calendarData_.endDate); break; case CalendarPickerSelectedType::DAY: date.AddDays(1); AdjustSolarDate(date, calendarData_.startDate, calendarData_.endDate); break; default: break; } OnValueChange(); UpdateEntryButtonColor(); }
2.2 CalendarSettingData
struct CalendarSettingData {
PickerDate selectedDate; // 当前选中日期
PickerDate startDate; // 起始日期
PickerDate endDate; // 结束日期
std::optional<Dimension> dayRadius; // 日期圆角半径
RefPtr<ResourceObject> dayRadiusResObj;
WeakPtr<FrameNode> entryNode; // 入口节点
std::vector<std::pair<PickerDate, PickerDate>> disabledDateRange; // 禁用日期范围
bool markToday; // 是否标记今天
};
2.3 CalendarPickerSelectedType
enum class CalendarPickerSelectedType {
YEAR = 0, // 年份区域
MONTH, // 月份区域
DAY, // 日期区域
ADDBTN, // 加按钮
SUBBTN, // 减按钮
OTHER, // 其他区域
};
3. 完整交互流程
3.1 点击弹出对话框
用户点击年/月/日文本区域
↓
HandleClickEvent(globalLocation)
↓
CheckRegion(globalLocation) → 确定点击区域
↓
SetSelectedType(type) → 设置当前选中类型
↓
ShowDialog() → 弹出日历对话框
↓
创建 CalendarDialogPattern
↓
设置初始选中日期
↓
显示对话框
3.2 键盘数字输入
用户按数字键 (0-9)
↓
HandleNumberKeyEvent(event)
↓
根据 selected_ 分发:
- YEAR → HandleYearKeyEvent()
- MONTH → HandleMonthKeyEvent()
- DAY → HandleDayKeyEvent()
↓
HandleYearKeyWaitingEvent()
↓
累加数字构建年份/月份/日期
↓
达到长度后自动完成
↓
OnValueChange() → 触发 onChange 事件
3.3 加减按钮操作
用户点击加/减按钮
↓
HandleAddButtonClick() / HandleSubButtonClick()
↓
根据 selected_ 调整日期:
- YEAR: ±1 年
- MONTH: ±1 月 (带范围检查)
- DAY: ±1 日 (带范围检查)
↓
AdjustSolarDate() → 调整到有效范围
↓
OnValueChange() → 触发 onChange 事件
↓
UpdateEntryButtonColor() → 更新按钮状态
4. 关键技术点
4.1 日期范围限制
void CalendarPickerPattern::SetCalendarData(const CalendarSettingData& data) {
CalendarSettingData settingData = data;
// 自动调整选中日期到有效范围
settingData.selectedDate = PickerDate::AdjustDateToRange(
data.selectedDate,
data.startDate,
data.endDate
);
calendarData_ = settingData;
}
void AdjustSolarDate(PickerDate& date, const PickerDate& start, const PickerDate& end) {
if (SolarDateCompare(date, start) < 0) {
date = start; // 小于起始日期
return;
}
if (SolarDateCompare(date, end) > 0) {
date = end; // 大于结束日期
}
}
4.2 禁用日期范围
std::vector<std::pair<PickerDate, PickerDate>> disabledDateRange;
// 检查日期是否被禁用
bool IsDateDisabled(const PickerDate& date) {
for (const auto& range : disabledDateRange) {
if (SolarDateCompare(date, range.first) >= 0 &&
SolarDateCompare(date, range.second) <= 0) {
return true;
}
}
return false;
}
4.3 对话框边缘对齐
enum class CalendarEdgeAlign {
EDGE_ALIGN_START = 0, // 左对齐
EDGE_ALIGN_CENTER, // 居中
EDGE_ALIGN_END, // 右对齐
};
OffsetF CalendarPickerPattern::CalculateDialogOffset() {
auto host = GetHost();
auto geometryNode = host->GetGeometryNode();
auto offset = geometryNode->GetOffset();
// 根据对齐方式计算偏移
switch (align_) {
case CalendarEdgeAlign::EDGE_ALIGN_START:
// 左对齐,无额外偏移
break;
case CalendarEdgeAlign::EDGE_ALIGN_CENTER:
// 居中对齐
offset += DimensionOffset(dialogWidth / 2, 0);
break;
case CalendarEdgeAlign::EDGE_ALIGN_END:
// 右对齐
offset += DimensionOffset(dialogWidth, 0);
break;
}
return offset;
}
4.4 悬停效果
void CalendarPickerPattern::HandleTextHoverEvent(bool state, int32_t index) {
auto host = GetHost();
auto textNode = DynamicCast<FrameNode>(host->GetChildAtIndex(index));
// 更新悬停样式
if (state) {
// 悬停状态
textNode->GetRenderContext()->UpdateBackgroundColor(hoverColor);
} else {
// 恢复正常状态
textNode->GetRenderContext()->UpdateBackgroundColor(normalColor);
}
}
void CalendarPickerPattern::InitOnHoverEvent() {
auto hub = GetHost()->GetEventHub<EventHub>();
auto hoverListener = [weak = WeakClaim(this)](bool state, const Offset& globalLocation) {
auto pattern = weak.Upgrade();
CHECK_NULL_VOID(pattern);
// 判断悬停区域
auto type = pattern->CheckRegion(globalLocation);
if (type == CalendarPickerSelectedType::YEAR) {
pattern->HandleTextHoverEvent(state, pattern->yearIndex_);
}
// ... 其他区域
};
hub->SetOnHoverEvent(hoverListener);
}
4.5 按钮状态更新
void CalendarPickerPattern::UpdateEntryButtonColor() {
auto host = GetHost();
// 获取加/减按钮
auto addButton = DynamicCast<FrameNode>(host->GetChildAtIndex(addId_));
auto subButton = DynamicCast<FrameNode>(host->GetChildAtIndex(subId_));
// 检查是否可以继续加减
bool canAdd = IsAddOrSubButtonEnable(ADD_BUTTON_INDEX);
bool canSub = IsAddOrSubButtonEnable(SUB_BUTTON_INDEX);
// 更新按钮颜色和状态
if (!canAdd) {
addButton->GetRenderContext()->UpdateOpacity(disabledOpacity);
}
if (!canSub) {
subButton->GetRenderContext()->UpdateOpacity(disabledOpacity);
}
}
bool CalendarPickerPattern::IsAddOrSubButtonEnable(int32_t buttonIndex) {
auto& date = calendarData_.selectedDate;
auto& start = calendarData_.startDate;
auto& end = calendarData_.endDate;
if (buttonIndex == ADD_BUTTON_INDEX) {
// 检查是否可以增加
PickerDate testDate = date;
switch (selected_) {
case CalendarPickerSelectedType::YEAR:
testDate.AddYears(1);
break;
case CalendarPickerSelectedType::MONTH:
testDate.AddMonths(1);
break;
case CalendarPickerSelectedType::DAY:
testDate.AddDays(1);
break;
}
return SolarDateCompare(testDate, end) <= 0;
} else {
// 检查是否可以减少
PickerDate testDate = date;
switch (selected_) {
case CalendarPickerSelectedType::YEAR:
testDate.AddYears(-1);
break;
case CalendarPickerSelectedType::MONTH:
testDate.AddMonths(-1);
break;
case CalendarPickerSelectedType::DAY:
testDate.AddDays(-1);
break;
}
return SolarDateCompare(testDate, start) >= 0;
}
}
5. 属性系统
5.1 布局属性 (CalendarPickerLayoutProperty)
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| calendarData | CalendarSettingData | - | 日历数据 (选中日期、起始/结束日期等) |
| align | CalendarEdgeAlign | END | 对话框对齐方式 |
| offset | DimensionOffset | - | 对话框偏移量 |
| textStyle | PickerTextStyle | - | 文本样式 |
| markToday | bool | false | 是否标记今天 |
5.2 CalendarSettingData 属性
| 属性 | 类型 | 说明 |
|---|---|---|
| selectedDate | PickerDate | 当前选中日期 |
| startDate | PickerDate | 起始日期 |
| endDate | PickerDate | 结束日期 |
| dayRadius | Dimension | 日期圆角半径 |
| disabledDateRange | vector<pair> | 禁用日期范围列表 |
| markToday | bool | 是否标记今天 |
6. 事件系统
6.1 事件类型
onChange - 日期改变:
void CalendarPickerPattern::OnValueChange() {
auto eventHub = GetEventHub<CalendarPickerEventHub>();
auto onChangeEvent = eventHub->GetChangeEvent();
if (onChangeEvent) {
auto info = GetEntryDateInfo();
onChangeEvent(info); // 触发 ArkTS onChange 回调
}
}
6.2 事件数据格式
std::string CalendarPickerPattern::GetEntryDateInfo() {
// 格式: "year-month-day"
auto& date = calendarData_.selectedDate;
return std::to_string(date.GetYear()) + "-" +
std::to_string(date.GetMonth()) + "-" +
std::to_string(date.GetDay());
}
7. 对话框系统
7.1 对话框弹出
void CalendarPickerPattern::ShowDialog() {
if (isDialogShow_) {
return; // 已显示,不重复弹出
}
// 准备对话框属性
CalendarPickerOption option;
option.data = calendarData_;
option.alignType = align_;
option.offset = offset_;
option.textStyle = GetTextStyle();
// 创建并显示对话框
CalendarDialogView::Show(option);
isDialogShow_ = true;
}
7.2 对话框关闭
void CalendarPickerPattern::SetDialogShow(bool flag) {
isDialogShow_ = flag;
if (!flag) {
// 对话框关闭,可能日期已改变
OnValueChange();
UpdateEntryButtonColor();
}
}
8. 无障碍支持
8.1 焦点管理
FocusPattern CalendarPickerPattern::GetFocusPattern() const {
return {
FocusType::NODE, // 节点级焦点
true, // 可聚焦
FocusStyleType::CUSTOM_REGION // 自定义焦点区域
};
}
8.2 键盘导航
bool CalendarPickerPattern::HandleKeyEvent(const KeyEvent& event) {
switch (event.GetKeyCode()) {
case KeyCode::KEY_UP:
// 向上键 → 日期 +1
HandleAddButtonClick();
return true;
case KeyCode::KEY_DOWN:
// 向下键 → 日期 -1
HandleSubButtonClick();
return true;
case KeyCode::KEY_TAB:
// Tab 键 → 切换选中类型
SwitchSelectedType();
return true;
case KeyCode::KEY_ENTER:
// 回车键 → 弹出对话框
ShowDialog();
return true;
default:
// 数字键 → 输入数字
if (event.GetKeyCode() >= KeyCode::KEY_0 &&
event.GetKeyCode() <= KeyCode::KEY_9) {
return HandleNumberKeyEvent(event);
}
return false;
}
}
9. 最佳实践
9.1 基本使用
推荐做法:
// 1. 设置合理的日期范围
CalendarPicker({
start: new Date('1900-1-1'),
end: new Date('2100-12-31'),
selected: new Date('2024-1-1')
})
.onChange((date: string) => {
console.log('Selected:', date); // "2024-1-1"
})
// 2. 标记今天
CalendarPicker({
markToday: true
})
// 3. 禁用特定日期范围
CalendarPicker({
disabledDateRange: [
{ start: new Date('2024-1-1'), end: new Date('2024-1-7') }
]
})
9.2 对话框配置
// 设置对话框对齐
CalendarPicker()
.edgeAlign(CalendarEdgeAlign.START) // 左对齐
// .edgeAlign(CalendarEdgeAlign.CENTER) // 居中
// .edgeAlign(CalendarEdgeAlign.END) // 右对齐
// 设置对话框偏移
CalendarPicker()
.dialogOffset({ dx: 10, dy: 20 })
9.3 样式定制
// 自定义文本样式
CalendarPicker()
.textStyle({
color: Color.Blue,
fontSize: 16,
fontWeight: FontWeight.Bold
})
10. 问题排查
10.1 常见问题
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 日期无法修改 | 超出范围限制 | 检查 startDate/endDate 设置 |
| 按钮禁用 | 达到边界 | 调整日期范围 |
| 对话框位置错误 | 对齐方式设置错误 | 调整 edgeAlign |
| 输入无响应 | 未选中区域 | 点击年/月/日区域先选中 |
10.2 调试工具
日志输出:
# 查看日历相关日志
hilog -T ArkUI | grep -i calendar
状态检查:
ToJsonValue() // 输出当前状态
// selectedDate: "2024-1-1"
// startDate: "1900-1-31"
// endDate: "2100-12-31"
// align: EDGE_ALIGN_END
相关目录
frameworks/core/components_ng/pattern/calendar_picker/
├── calendar_picker_pattern.h/cpp # 主要逻辑
├── calendar_picker_layout_property.h # 布局属性
├── calendar_picker_layout_algorithm.h/cpp # 布局算法
├── calendar_picker_event_hub.h # 事件处理
├── calendar_dialog_pattern.h/cpp # 对话框模式
├── calendar_dialog_layout_algorithm.h/cpp # 对话框布局
├── calendar_type_define.h # 类型定义
└── bridge/
├── calendar_picker_static_modifier.cpp # 静态修饰符
└── calendar_picker_dynamic_module.cpp # 动态模块
关键要点总结
- 入口模式: 轻量级入口,点击后弹出完整日历对话框
- 日期范围: 支持灵活的起始/结束日期配置,自动调整
- 键盘支持: 完整的数字输入和方向键导航
- 边缘对齐: 支持对话框左/中/右对齐
- 禁用日期: 支持配置禁用日期范围
- 悬停效果: 支持鼠标悬停高亮显示
- 按钮状态: 根据日期范围自动启用/禁用加减按钮
- 无障碍: 完整的焦点管理和键盘导航支持