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

相关推荐
戌中横1 天前
JavaScript——Web APIs DOM
前端·javascript·html
Beginner x_u1 天前
如何解释JavaScript 中 this 的值?
开发语言·前端·javascript·this 指针
HWL56791 天前
获取网页首屏加载时间
前端·javascript·vue.js
速易达网络1 天前
基于RuoYi-Vue 框架美妆系统
前端·javascript·vue.js
yinmaisoft1 天前
JNPF 表单模板实操:高效复用表单设计指南
前端·javascript·html
37方寸1 天前
前端基础知识(JavaScript)
开发语言·前端·javascript
zilikew1 天前
Flutter框架跨平台鸿蒙开发——今日吃啥APP的开发流程
flutter·华为·harmonyos·鸿蒙
Whisper_Sy1 天前
Flutter for OpenHarmony移动数据使用监管助手App实战 - 应用列表实现
android·开发语言·javascript·flutter·php
血色橄榄枝1 天前
03 基于Flutter集成网络请求On OpenHarmony
网络·flutter
json{shen:"jing"}1 天前
1. 两数之和
前端·javascript·数据库