You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter实现取消日期选择器时自动关闭联动时间选择器

Flutter 日期时间联合选择器联动取消问题修复

问题现象

实现日期+时间联合选择功能时,基础流程可正常运行:点击按钮后先弹出日期选择弹窗,用户选完日期自动弹出时间选择弹窗,两次选择完成后可合并为完整DateTime对象展示。但存在交互缺陷:如果用户在日期选择弹窗点击取消按钮,后续时间选择弹窗仍会自动弹出,需要用户手动二次取消,不符合交互预期。
问题复现动图

问题根因

现有代码存在两处核心逻辑错误:

  • 返回值逻辑错误:_selectDate、_selectTime两个方法的返回值处理有误,当用户点击取消按钮时,系统选择器会返回null,但原代码无论选择是否有效,最终都会返回全局缓存的旧选择值,永远不会返回null,导致后续空判断完全失效。
  • 执行顺序错误:联合选择方法_selectDateTime中,无条件先调用了时间选择器,之后才判断日期选择结果是否为空,哪怕日期选择已经被取消,时间选择器也已经被触发弹出。

修复方案

  1. 修正单值选择方法的返回逻辑:用户取消选择时直接返回null,仅当选择有效时更新全局状态并返回新选择值,同时将方法返回值改为可空类型匹配逻辑。
  2. 调整联合选择方法的执行顺序:拿到日期选择结果后立刻做空判断,如果用户取消了日期选择,直接终止流程,不再弹出时间选择器;仅当日期选择有效时,再拉起时间选择器。

修正后完整代码

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

void main() {
  runApp(const DateTimePickerApp());
}

class DateTimePickerApp extends StatelessWidget {
  const DateTimePickerApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: TextScreen(),
    );
  }
}

class TextScreen extends StatefulWidget {
  const TextScreen({
    Key? key,
  }) : super(key: key);

  @override
  State<TextScreen> createState() => _TextScreenState();
}

class _TextScreenState extends State<TextScreen> {
  DateTime selectedDate = DateTime.now();
  TimeOfDay selectedTime = TimeOfDay.now();
  DateTime dateTime = DateTime.now();
  bool showDate = true;
  bool showTime = true;
  bool showDateTime = true;

  // 选择日期
  Future<DateTime?> _selectDate(BuildContext context) async {
    final selected = await showDatePicker(
      context: context,
      initialDate: selectedDate,
      firstDate: DateTime(2000),
      lastDate: DateTime(2025),
    );
    if (selected != null && selected != selectedDate) {
      setState(() {
        selectedDate = selected;
      });
    }
    // 用户取消时返回null,选中时返回新选择的日期
    return selected;
  }

  // 选择时间
  Future<TimeOfDay?> _selectTime(BuildContext context) async {
    final selected = await showTimePicker(
      context: context,
      initialTime: selectedTime,
    );
    if (selected != null && selected != selectedTime) {
      setState(() {
        selectedTime = selected;
      });
    }
    // 用户取消时返回null,选中时返回新选择的时间
    return selected;
  }

  // 联合选择日期时间
  Future _selectDateTime(BuildContext context) async {
    final date = await _selectDate(context);
    // 日期选择被取消,直接终止流程,不弹出时间选择器
    if (date == null) return;

    final time = await _selectTime(context);
    // 时间选择被取消,不更新最终结果
    if (time == null) return;

    setState(() {
      dateTime = DateTime(
        date.year,
        date.month,
        date.day,
        time.hour,
        time.minute,
      );
    });
  }

  String getDate() {
    return DateFormat('MMM d, yyyy').format(selectedDate);
  }

  String getDateTime() {
    return DateFormat('yyyy-MM-dd HH:mm a').format(dateTime);
  }

  String getTime(TimeOfDay tod) {
    final now = DateTime.now();
    final dt = DateTime(now.year, now.month, now.day, tod.hour, tod.minute);
    final format = DateFormat.jm();
    return format.format(dt);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GeeksforGeeks'),
        centerTitle: true,
        backgroundColor: Colors.green,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            showDate ? Center(child: Text(getDate())) : const SizedBox(),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 15),
              child: ElevatedButton(
                onPressed: () {
                  _selectDate(context);
                  showDate = true;
                },
                child: const Text('Date Picker'),
              ),
            ),
            showTime
                ? Center(child: Text(getTime(selectedTime)))
                : const SizedBox(),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 15),
              child: ElevatedButton(
                onPressed: () {
                  _selectTime(context);
                  showTime = true;
                },
                child: const Text('Time Picker'),
              ),
            ),
            showDateTime
                ? Center(child: Text(getDateTime()))
                : const SizedBox(),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 15),
              child: ElevatedButton(
                onPressed: () {
                  _selectDateTime(context);
                  showDateTime = true;
                },
                child: const Text(' Date & Time '),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Ankit Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 23:09:15