自定义时间选择器

import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';

import '../../../../utils/utils.dart';
import '../../../components/operate_botton.dart';
import '../config/config.dart';

class TimeData {
  final int hour;
  final int minute;
  final int second;

  TimeData(this.hour, [this.minute = 0, this.second = 0])
      : assert(hour >= 0 && hour <= 23),
        assert(minute >= 0 && minute <= 59),
        assert(second >= 0 && second <= 59);

  factory TimeData.now() {
    var now = DateTime.now();
    return TimeData(now.hour, now.minute, now.second);
  }

  //判断时间先后
  bool precede(TimeData other) {
    return (hour < other.hour) ||
        (hour == other.hour && minute < other.minute) ||
        (hour == other.hour && minute == other.minute && second < other.second);
  }

  //转字符串
  @override
  String toString() {
    return '$hour:$minute:$second';
  }
}

class MyTimePicker extends StatefulWidget {
  const MyTimePicker({
    super.key,
    this.width,
    this.height,
    this.endTime,
    this.startTime,
    this.isDialy = false,
    this.selectedEndTime,
    this.selectedStartTime,
    required this.onSelected,
    required this.disableperiod,
    required this.timeLimited,
    required this.editDate,
  });

  final double? width;
  final double? height;
  final bool? isDialy;
  final bool timeLimited;
  final TimeData? endTime;
  final TimeData? startTime;
  final TimeData? selectedEndTime;
  final List<String> disableperiod;
  final TimeData? selectedStartTime;
  final DateTime editDate;

  final Function(
      {required String startAt,
      required String endAt,
      required String period}) onSelected;

  @override
  State<MyTimePicker> createState() => _MyTimePickerState();
}

class _MyTimePickerState extends State<MyTimePicker> {
  late FixedExtentScrollController startHourScrollController;
  late FixedExtentScrollController startMinuteScrollController;
  late FixedExtentScrollController endHourScrollController;
  late FixedExtentScrollController endMinuteScrollController;

  List<int> hourList = [];
  List<int> minuteList = [];
  int startHourIndex = 0;
  int startMinuteIndex = 0;
  int endHourIndex = 0;
  int endMinuteIndex = 0;

  late TimeData startTime;
  late TimeData endTime;
  late TimeData selectedStartTime;
  late TimeData selectedEndTime;

  final double itemExtent = 44;

  bool isOverlap(List<String> disablePeriod, String newPeriod) {
    bool isOverlap = false;

    for (String period in disablePeriod) {
      List<String> parts = period.split(' - ');
      List<String> newParts = newPeriod.split(' - ');

      DateTime start1 = DateTime.parse('2024-01-01 ${parts[0]}');
      DateTime end1 = DateTime.parse('2024-01-01 ${parts[1]}');

      DateTime start2 = DateTime.parse('2024-01-01 ${newParts[0]}');
      DateTime end2 = DateTime.parse('2024-01-01 ${newParts[1]}');

      if (start1.isBefore(end2) && end1.isAfter(start2)) {
        isOverlap = true;
        break;
      }
    }

    return isOverlap;
  }

  void initData() {
    // 初始化时
    for (int i = startTime.hour; i <= endTime.hour; i++) {
      hourList.add(i);
    }

    int selectStarHour = selectedStartTime.hour;
    // 初始化分

    minuteList = getMinuteList(selectStarHour);
    // 初始化时间索引
    final List uniqueHourList = Set.from(hourList).toList();
    final List uniqueMinuteList = Set.from(minuteList).toList();
    // 获取索引
    setState(() {
      startHourIndex = uniqueHourList.indexOf(selectedStartTime.hour);
      startMinuteIndex = uniqueMinuteList.indexOf(selectedStartTime.minute);
      endHourIndex = uniqueHourList.indexOf(selectedEndTime.hour);
      endMinuteIndex = uniqueMinuteList.indexOf(selectedEndTime.minute);
    });
    // 设置Picker初始值
    startHourScrollController =
        FixedExtentScrollController(initialItem: startHourIndex);
    startMinuteScrollController =
        FixedExtentScrollController(initialItem: startMinuteIndex);
    endHourScrollController =
        FixedExtentScrollController(initialItem: endHourIndex);
    endMinuteScrollController =
        FixedExtentScrollController(initialItem: endMinuteIndex);
  }

  List<int> getMinuteList(int selectHour) {
    List<int> list = [];
    if (selectHour == startTime.hour) {
      for (int i = startTime.minute; i <= 59; i++) {
        list.add(i);
      }
    } else if (selectHour == endTime.hour) {
      for (int i = 0; i <= endTime.minute; i++) {
        list.add(i);
      }
    } else {
      for (int i = 0; i <= 59; i++) {
        list.add(i);
      }
    }
    return list;
  }

  void updateMinuteList() {
    int selectHour = hourList[startHourIndex];
    setState(() {
      startMinuteIndex = 0;
      minuteList = getMinuteList(selectHour);
      if (startHourScrollController.positions.isNotEmpty) {
        startMinuteScrollController.jumpTo(0);
      }
    });
  }

  @override
  void initState() {
    super.initState();
    startTime = widget.startTime ?? TimeData(0, 0, 0);
    endTime = widget.endTime ?? TimeData(23, 59, 59);
    selectedStartTime = widget.selectedStartTime ??
        TimeData(startTime.hour, startTime.minute, startTime.second);
    selectedEndTime = widget.selectedEndTime ??
        TimeData(endTime.hour, endTime.minute, endTime.second);
    if (endTime.precede(startTime)) {
      endTime = startTime;
    }
    if (selectedEndTime.precede(selectedStartTime)) {
      selectedEndTime = selectedStartTime;
    }
    if (selectedStartTime.precede(startTime)) {
      selectedStartTime = startTime;
    }
    if (endTime.precede(selectedStartTime)) {
      selectedStartTime = endTime;
    }
    if (selectedEndTime.precede(startTime)) {
      selectedEndTime = startTime;
    }
    if (endTime.precede(selectedEndTime)) {
      selectedEndTime = endTime;
    }
    initData();
  }

  @override
  void dispose() {
    startHourScrollController.dispose();
    startMinuteScrollController.dispose();
    endHourScrollController.dispose();
    endMinuteScrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: widget.width ?? 280,
      height: widget.height ?? 280,
      padding: const EdgeInsets.all(10),
      child: Column(
        children: [
          SizedBox(
            width: double.infinity,
            height: widget.height != null ? widget.height! - 50 : 230,
            child: Row(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Expanded(child: Center(child: startHourPickerView())),
                Text(':',
                    style: TextStyle(
                      color: textColor(context.isDarkMode),
                      fontSize: 16,
                      height: 1,
                      fontWeight: FontWeight.bold,
                    )),
                Expanded(child: Center(child: startMinutePickerView())),
                Text('至',
                    style: TextStyle(
                      color: textColor(context.isDarkMode),
                      fontSize: 16,
                      height: 1,
                      fontWeight: FontWeight.bold,
                    )),
                Expanded(child: Center(child: endHourPickerView())),
                Text(':',
                    style: TextStyle(
                      color: textColor(context.isDarkMode),
                      fontSize: 16,
                      height: 1,
                      fontWeight: FontWeight.bold,
                    )),
                Expanded(child: Center(child: endMinutePickerView())),
              ],
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              creatCancelButton(() {
                SmartDialog.dismiss();
              }),
              const SizedBox(width: 10),
              creatOkButton(
                () async {
                  String startAt =
                      '${hourList[startHourIndex] > 9 ? hourList[startHourIndex] : '0${hourList[startHourIndex]}'}:${minuteList[startMinuteIndex] > 9 ? minuteList[startMinuteIndex] : '0${minuteList[startMinuteIndex]}'}';
                  String endAt =
                      '${hourList[endHourIndex] > 9 ? hourList[endHourIndex] : '0${hourList[endHourIndex]}'}:${minuteList[endMinuteIndex] > 9 ? minuteList[endMinuteIndex] : '0${minuteList[endMinuteIndex]}'}';

                  if (widget.isDialy!) {
                    DateTime now = DateTime.now();
                    if (widget.editDate.year == now.year &&
                        widget.editDate.month == now.month &&
                        widget.editDate.day == now.day &&
                        (hourList[startHourIndex] < now.hour ||
                            (hourList[startHourIndex] == now.hour &&
                                minuteList[startMinuteIndex] < now.minute))) {
                      throw '开始时间不可早于当前时间';
                    }
                  }

                  if (endHourIndex < startHourIndex ||
                      (endHourIndex == startHourIndex &&
                          endMinuteIndex < startMinuteIndex)) {
                    throw '结束时间不能早于开始时间';
                  }
                  String timeSlot = '$startAt - $endAt';
                  if (isOverlap(widget.disableperiod, timeSlot)) {
                    throw '时间段重复';
                  }

                  widget.onSelected(
                      startAt: startAt, endAt: endAt, period: timeSlot);
                  SmartDialog.dismiss();
                },
                snackbarOffset: Offset(0, Adaptive.getHeight(context) * .45),
              )
            ],
          ),
        ],
      ),
    );
  }

  Widget startHourPickerView() {
    return ListWheelScrollView(
      controller: startHourScrollController,
      itemExtent: itemExtent,
      physics: const FixedExtentScrollPhysics(
        parent: BouncingScrollPhysics(),
      ),
      diameterRatio: 1.2,
      children: [
        for (int i = 0; i < hourList.length; i++)
          InkWell(
              onTap: () {
                startHourScrollController.jumpToItem(i);
              },
              child: Container(
                height: itemExtent,
                alignment: Alignment.center,
                child: Text(
                  '${hourList[i]}时',
                  style: getTextStyle(i == startHourIndex),
                  maxLines: 1,
                ),
              ))
      ],
      onSelectedItemChanged: (index) {
        setState(() {
          startHourIndex = index;
        });
      },
    );
  }

  Widget startMinutePickerView() {
    return ListWheelScrollView(
      controller: startMinuteScrollController,
      itemExtent: itemExtent,
      physics: const FixedExtentScrollPhysics(
        parent: BouncingScrollPhysics(),
      ),
      diameterRatio: 1.2,
      children: [
        for (int i = 0; i < minuteList.length; i++)
          InkWell(
              onTap: () {
                startMinuteScrollController.jumpToItem(i);
              },
              child: Container(
                height: itemExtent,
                alignment: Alignment.center,
                child: Text(
                  '${minuteList[i]}分',
                  style: getTextStyle(i == startMinuteIndex),
                  maxLines: 1,
                ),
              ))
      ],
      onSelectedItemChanged: (index) {
        setState(() {
          startMinuteIndex = index;
        });
      },
    );
  }

  Widget endHourPickerView() {
    return ListWheelScrollView(
      controller: endHourScrollController,
      itemExtent: itemExtent,
      physics: const FixedExtentScrollPhysics(
        parent: BouncingScrollPhysics(),
      ),
      diameterRatio: 1.2,
      children: [
        for (int i = 0; i < hourList.length; i++)
          InkWell(
              onTap: () {
                endHourScrollController.jumpToItem(i);
              },
              child: Container(
                height: itemExtent,
                alignment: Alignment.center,
                child: Text(
                  '${hourList[i]}时',
                  style: getTextStyle(i == endHourIndex),
                  maxLines: 1,
                ),
              ))
      ],
      onSelectedItemChanged: (index) {
        setState(() {
          endHourIndex = index;
        });
      },
    );
  }

  Widget endMinutePickerView() {
    return ListWheelScrollView(
      controller: endMinuteScrollController,
      itemExtent: itemExtent,
      physics: const FixedExtentScrollPhysics(
        parent: BouncingScrollPhysics(),
      ),
      diameterRatio: 1.2,
      children: [
        for (int i = 0; i < minuteList.length; i++)
          InkWell(
              onTap: () {
                endMinuteScrollController.jumpToItem(i);
              },
              child: Container(
                height: itemExtent,
                alignment: Alignment.center,
                child: Text(
                  '${minuteList[i]}分',
                  style: getTextStyle(i == endMinuteIndex),
                  maxLines: 1,
                ),
              ))
      ],
      onSelectedItemChanged: (index) {
        setState(() {
          endMinuteIndex = index;
        });
        // updateDayList();
      },
    );
  }

  TextStyle getTextStyle(bool sel) {
    return TextStyle(
      color: textColor(context.isDarkMode),
      fontSize: textSizeConfig.contentTextSize,
      height: 1,
      fontWeight: sel ? FontWeight.bold : FontWeight.normal,
    );
  }
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值