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

Flutter如何自定义横向时间选择器 实现带FROM/TO标注的黑色圆角样式

Flutter横向时间选择器实现方案

方案1:基于现有time_range: ^1.0.1插件调整

你可以通过调整插件参数和外层嵌套装饰组件接近目标效果,修改要点如下:

  • 恢复fromTitle、toTitle的文本配置,不要替换为分割线
  • 给TimeRange组件外层嵌套ClipRRect和Container实现全局圆角,调整选中状态的背景样式
  • 如对圆角要求高,可复制插件源码到项目内自定义,给时间块增加圆角属性

调整后的核心代码片段:

// 外层嵌套实现圆角
ClipRRect(
  borderRadius: BorderRadius.circular(16),
  child: TimeRange(
    fromTitle: Text(
      'FROM',
      style: TextStyle(
        fontSize: 14,
        color: dark,
        fontWeight: FontWeight.w600,
      ),
    ),
    toTitle: Text(
      'TO',
      style: TextStyle(
        fontSize: 14,
        color: dark,
        fontWeight: FontWeight.w600,
      ),
    ),
    // 其余原有参数保持不变
    titlePadding: leftPadding,
    textStyle: TextStyle(
      fontWeight: FontWeight.normal,
      color: dark,
    ),
    activeTextStyle: TextStyle(
      fontWeight: FontWeight.bold,
      color: Colors.white,
    ),
    borderColor: Colors.transparent,
    activeBorderColor: Colors.transparent,
    backgroundColor: Colors.transparent,
    activeBackgroundColor: dark,
    firstTime: TimeOfDay(hour: 8, minute: 00),
    lastTime: TimeOfDay(hour: 20, minute: 00),
    initialRange: _timeRange,
    timeStep: 10,
    timeBlock: 30,
    onRangeCompleted: (range) => setState(() => _timeRange = range),
  ),
)

如果调整后仍无法完全匹配设计,建议使用自定义实现方案,灵活度更高。


方案2:自定义横向时间选择器(推荐,完全匹配样式)

完全自主实现组件,可100%还原设计的圆角、标注、样式效果,实现逻辑如下:

  • 按指定步长生成所有可选时间点列表
  • 用横向ListView渲染时间块,未选中状态为透明背景黑色文字,选中状态为黑色圆角背景白色文字
  • 通过手势监听处理点击/拖拽选择起始、结束时间
  • 在选中区间的首尾位置上方添加FROM、TO标注文本

核心示例代码:

class CustomTimeRangePicker extends StatefulWidget {
  final TimeOfDay startTime;
  final TimeOfDay endTime;
  final int stepMinutes;
  final Function(TimeOfDay start, TimeOfDay end) onRangeSelected;

  const CustomTimeRangePicker({
    super.key,
    required this.startTime,
    required this.endTime,
    required this.stepMinutes,
    required this.onRangeSelected,
  });

  @override
  State<CustomTimeRangePicker> createState() => _CustomTimeRangePickerState();
}

class _CustomTimeRangePickerState extends State<CustomTimeRangePicker> {
  List<TimeOfDay> timePoints = [];
  int? startIndex;
  int? endIndex;

  @override
  void initState() {
    super.initState();
    _generateTimePoints();
  }

  void _generateTimePoints() {
    TimeOfDay current = widget.startTime;
    while (current.hour < widget.endTime.hour || (current.hour == widget.endTime.hour && current.minute <= widget.endTime.minute)) {
      timePoints.add(current);
      current = TimeOfDay(hour: current.hour + (current.minute + widget.stepMinutes) ~/ 60, minute: (current.minute + widget.stepMinutes) % 60);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 标注行
        if (startIndex != null && endIndex != null)
          Row(
            children: [
              SizedBox(width: startIndex! * 60 + 20),
              const Text('FROM', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
              SizedBox(width: (endIndex! - startIndex!) * 60 - 40),
              const Text('TO', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
            ],
          ),
        const SizedBox(height: 8),
        // 时间块横向列表
        SizedBox(
          height: 50,
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemCount: timePoints.length,
            itemBuilder: (context, index) {
              bool isSelected = startIndex != null && endIndex != null && index >= startIndex! && index <= endIndex!;
              return GestureDetector(
                onTap: () {
                  if (startIndex == null || endIndex != null) {
                    startIndex = index;
                    endIndex = null;
                  } else {
                    endIndex = index > startIndex! ? index : startIndex;
                    startIndex = index < startIndex! ? index : startIndex;
                    widget.onRangeSelected(timePoints[startIndex!], timePoints[endIndex!]);
                  }
                  setState(() {});
                },
                child: Container(
                  width: 60,
                  margin: const EdgeInsets.symmetric(horizontal: 2),
                  decoration: BoxDecoration(
                    color: isSelected ? const Color(0xFF333A47) : Colors.transparent,
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Center(
                    child: Text(
                      timePoints[index].format(context),
                      style: TextStyle(
                        color: isSelected ? Colors.white : const Color(0xFF333A47),
                        fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

使用时直接替换原有TimeRange组件即可,可根据设计需求任意调整圆角、颜色、间距、标注位置等样式。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 14:24:03