
前言
时间选择器是打卡工具类应用中的重要组件,用于设置打卡提醒时间、习惯执行时间等。一个设计良好的时间选择器应该提供直观的时间选择方式,支持多种时间格式,并且操作便捷。本文将详细介绍如何在Flutter和OpenHarmony平台上实现功能完善的时间选择器组件。
时间选择器的设计需要考虑选择方式、显示格式和交互体验。我们将实现滚轮式时间选择器和时间段选择器,满足不同场景的需求。
Flutter时间选择器实现
首先创建时间显示和选择组件:
dart
class TimePickerField extends StatelessWidget {
final TimeOfDay time;
final ValueChanged<TimeOfDay> onTimeChanged;
final String? label;
const TimePickerField({
Key? key,
required this.time,
required this.onTimeChanged,
this.label,
}) : super(key: key);
Future<void> _showPicker(BuildContext context) async {
final picked = await showTimePicker(
context: context,
initialTime: time,
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
timePickerTheme: TimePickerThemeData(
backgroundColor: Colors.white,
hourMinuteShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
child: child!,
);
},
);
if (picked != null) {
onTimeChanged(picked);
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (label != null)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(label!, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
),
InkWell(
onTap: () => _showPicker(context),
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
time.format(context),
style: const TextStyle(fontSize: 16),
),
Icon(Icons.access_time, color: Colors.grey.shade600),
],
),
),
),
],
);
}
}
TimePickerField封装了时间显示和选择功能。点击触发系统时间选择器,选择完成后通过回调更新时间。time.format根据系统设置自动选择12小时制或24小时制格式。Theme包装器自定义了时间选择器的样式,使其与应用风格一致。
实现自定义滚轮时间选择器:
dart
class WheelTimePicker extends StatefulWidget {
final TimeOfDay initialTime;
final ValueChanged<TimeOfDay> onTimeChanged;
const WheelTimePicker({
Key? key,
required this.initialTime,
required this.onTimeChanged,
}) : super(key: key);
@override
State<WheelTimePicker> createState() => _WheelTimePickerState();
}
class _WheelTimePickerState extends State<WheelTimePicker> {
late FixedExtentScrollController _hourController;
late FixedExtentScrollController _minuteController;
int _selectedHour = 0;
int _selectedMinute = 0;
@override
void initState() {
super.initState();
_selectedHour = widget.initialTime.hour;
_selectedMinute = widget.initialTime.minute;
_hourController = FixedExtentScrollController(initialItem: _selectedHour);
_minuteController = FixedExtentScrollController(initialItem: _selectedMinute);
}
void _onTimeChanged() {
widget.onTimeChanged(TimeOfDay(hour: _selectedHour, minute: _selectedMinute));
}
@override
Widget build(BuildContext context) {
return Container(
height: 200,
child: Row(
children: [
Expanded(child: _buildHourPicker()),
const Text(':', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Expanded(child: _buildMinutePicker()),
],
),
);
}
Widget _buildHourPicker() {
return ListWheelScrollView.useDelegate(
controller: _hourController,
itemExtent: 40,
physics: const FixedExtentScrollPhysics(),
onSelectedItemChanged: (index) {
setState(() => _selectedHour = index);
_onTimeChanged();
},
childDelegate: ListWheelChildBuilderDelegate(
childCount: 24,
builder: (context, index) {
final isSelected = index == _selectedHour;
return Center(
child: Text(
index.toString().padLeft(2, '0'),
style: TextStyle(
fontSize: isSelected ? 24 : 18,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? Colors.blue : Colors.grey,
),
),
);
},
),
);
}
Widget _buildMinutePicker() {
return ListWheelScrollView.useDelegate(
controller: _minuteController,
itemExtent: 40,
physics: const FixedExtentScrollPhysics(),
onSelectedItemChanged: (index) {
setState(() => _selectedMinute = index);
_onTimeChanged();
},
childDelegate: ListWheelChildBuilderDelegate(
childCount: 60,
builder: (context, index) {
final isSelected = index == _selectedMinute;
return Center(
child: Text(
index.toString().padLeft(2, '0'),
style: TextStyle(
fontSize: isSelected ? 24 : 18,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? Colors.blue : Colors.grey,
),
),
);
},
),
);
}
}
滚轮时间选择器使用ListWheelScrollView实现iOS风格的滚轮效果。FixedExtentScrollPhysics确保滚动停止时对齐到某一项。选中项使用更大的字体和蓝色,未选中项使用较小的灰色字体。小时和分钟分别使用独立的滚轮,中间用冒号分隔。
OpenHarmony时间选择器实现
在鸿蒙系统中创建时间选择器:
typescript
@Component
struct TimePickerField {
@Prop time: { hour: number, minute: number } = { hour: 8, minute: 0 }
@Prop label: string = ''
private onTimeChanged: (time: { hour: number, minute: number }) => void = () => {}
@State showPicker: boolean = false
formatTime(): string {
const hour = this.time.hour.toString().padStart(2, '0')
const minute = this.time.minute.toString().padStart(2, '0')
return `${hour}:${minute}`
}
build() {
Column() {
if (this.label) {
Text(this.label)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.width('100%')
.margin({ bottom: 8 })
}
Row() {
Text(this.formatTime())
.fontSize(16)
Blank()
Image($r('app.media.clock'))
.width(20)
.height(20)
.fillColor('#666666')
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.border({ width: 1, color: '#E0E0E0' })
.borderRadius(8)
.onClick(() => {
this.showPicker = true
})
}
.bindSheet($$this.showPicker, this.TimePickerSheet(), {
height: 300,
dragBar: true
})
}
@Builder
TimePickerSheet() {
Column() {
TimePicker({
selected: new Date(2024, 0, 1, this.time.hour, this.time.minute)
})
.useMilitaryTime(true)
.onChange((value: TimePickerResult) => {
this.onTimeChanged({ hour: value.hour, minute: value.minute })
})
Button('确定')
.width('100%')
.margin({ top: 16 })
.onClick(() => {
this.showPicker = false
})
}
.padding(16)
}
}
鸿蒙使用TimePicker组件实现时间选择。bindSheet将时间选择器放在底部弹出面板中,dragBar显示拖动条。useMilitaryTime设为true使用24小时制。onChange回调在时间变化时触发,返回TimePickerResult包含选中的小时和分钟。
时间段选择器
实现开始和结束时间的选择:
dart
class TimeRangePicker extends StatelessWidget {
final TimeOfDay startTime;
final TimeOfDay endTime;
final ValueChanged<TimeOfDay> onStartTimeChanged;
final ValueChanged<TimeOfDay> onEndTimeChanged;
const TimeRangePicker({
Key? key,
required this.startTime,
required this.endTime,
required this.onStartTimeChanged,
required this.onEndTimeChanged,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: TimePickerField(
label: '开始时间',
time: startTime,
onTimeChanged: onStartTimeChanged,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Icon(Icons.arrow_forward, color: Colors.grey),
),
Expanded(
child: TimePickerField(
label: '结束时间',
time: endTime,
onTimeChanged: onEndTimeChanged,
),
),
],
);
}
}
时间段选择器组合两个时间选择器,用于设置习惯的执行时间范围。中间的箭头图标表示时间流向。Expanded确保两个选择器等宽分布。这种设计适用于需要设置时间范围的场景,如"早起习惯:6:00 - 8:00"。
快捷时间选择
实现常用时间的快捷选择:
dart
class QuickTimePicker extends StatelessWidget {
final TimeOfDay selectedTime;
final ValueChanged<TimeOfDay> onTimeChanged;
static const quickTimes = [
{'label': '早晨', 'time': TimeOfDay(hour: 7, minute: 0)},
{'label': '上午', 'time': TimeOfDay(hour: 9, minute: 0)},
{'label': '中午', 'time': TimeOfDay(hour: 12, minute: 0)},
{'label': '下午', 'time': TimeOfDay(hour: 15, minute: 0)},
{'label': '傍晚', 'time': TimeOfDay(hour: 18, minute: 0)},
{'label': '晚上', 'time': TimeOfDay(hour: 21, minute: 0)},
];
const QuickTimePicker({
Key? key,
required this.selectedTime,
required this.onTimeChanged,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('快捷选择', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: quickTimes.map((item) {
final time = item['time'] as TimeOfDay;
final isSelected = time.hour == selectedTime.hour && time.minute == selectedTime.minute;
return ChoiceChip(
label: Text('${item['label']} ${time.format(context)}'),
selected: isSelected,
onSelected: (_) => onTimeChanged(time),
);
}).toList(),
),
const SizedBox(height: 16),
TimePickerField(
label: '自定义时间',
time: selectedTime,
onTimeChanged: onTimeChanged,
),
],
);
}
}
快捷时间选择器提供常用时间的快捷按钮,同时保留自定义选择功能。ChoiceChip显示时间段名称和具体时间,选中状态有明显的视觉区分。这种设计减少了用户的操作步骤,提升了设置效率。
总结
本文详细介绍了在Flutter和OpenHarmony平台上实现时间选择器组件的完整方案。时间选择器通过系统选择器、滚轮选择器和快捷选择等多种方式,为用户提供了灵活的时间选择体验。时间段选择器满足了范围选择的需求,快捷选择提升了操作效率。两个平台的实现都注重用户体验,确保时间选择操作简单直观。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net