Flutter与OpenHarmony打卡时间选择器组件

前言

时间选择器是打卡工具类应用中的重要组件,用于设置打卡提醒时间、习惯执行时间等。一个设计良好的时间选择器应该提供直观的时间选择方式,支持多种时间格式,并且操作便捷。本文将详细介绍如何在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

相关推荐
金融RPA机器人丨实在智能7 小时前
Android Studio开发App项目进入AI深水区:实在智能Agent引领无代码交互革命
android·人工智能·ai·android studio
科技块儿7 小时前
利用IP查询在智慧城市交通信号系统中的应用探索
android·tcp/ip·智慧城市
独行soc8 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
冰暮流星8 小时前
javascript之二重循环练习
开发语言·javascript·数据库
ujainu8 小时前
Flutter + OpenHarmony 实现经典打砖块游戏开发实战—— 物理反弹、碰撞检测与关卡系统
flutter·游戏·openharmony·arkanoid·breakout
Mr Xu_8 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
微祎_8 小时前
构建一个 Flutter 点击速度测试器:深入解析实时交互、性能度量与响应式 UI 设计
flutter·ui·交互
王码码20358 小时前
Flutter for OpenHarmony 实战之基础组件:第二十七篇 BottomSheet — 动态底部弹窗与底部栏菜单
android·flutter·harmonyos
2501_915106328 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview
vistaup9 小时前
OKHTTP 默认构建包含 android 4.4 的TLS 1.2 以及设备时间不对兼容
android·okhttp