如何在Flutter中实现如图所示的可滑动时间范围选择器
自定义可滑动时间范围选择器实现方案(Flutter)
如果现有第三方包满足不了需求,完全可以自定义实现一个轻量的滑动时间范围选择器,核心依赖手势监听和布局计算,以下是具体实现步骤和代码示例:
核心实现思路
- 用
Stack层叠布局:底层放时间刻度、中间放选中范围的高亮背景、顶层放左右两个可拖动滑块 - 通过
GestureDetector监听拖动手势,实时更新滑块位置,并限制滑块边界(左滑块不能超过右滑块,且两者不能超出总范围) - 将滑块的像素偏移量映射为对应的时间值(比如总宽度对应24小时,按比例换算)
完整代码示例
import 'package:flutter/material.dart'; class SlidableTimeRangePicker extends StatefulWidget { final ValueChanged<TimeRange>? onRangeChanged; const SlidableTimeRangePicker({super.key, this.onRangeChanged}); @override State<SlidableTimeRangePicker> createState() => _SlidableTimeRangePickerState(); } class _SlidableTimeRangePickerState extends State<SlidableTimeRangePicker> { // 总宽度(初始化后由布局确定) double _totalWidth = 0; // 左右滑块的偏移量(单位:px) double _leftOffset = 40; double _rightOffset = 200; // 时间范围:0-24小时 final int _totalHours = 24; // 当前选中的时间范围 late TimeRange _selectedRange; @override void initState() { super.initState(); _updateSelectedRange(); } // 将偏移量转换为时间范围 void _updateSelectedRange() { final startHour = (_leftOffset / _totalWidth) * _totalHours; final endHour = (_rightOffset / _totalWidth) * _totalHours; _selectedRange = TimeRange( start: TimeOfDay(hour: startHour.floor(), minute: ((startHour - startHour.floor()) * 60).round()), end: TimeOfDay(hour: endHour.floor(), minute: ((endHour - endHour.floor()) * 60).round()), ); widget.onRangeChanged?.call(_selectedRange); } // 处理滑块拖动 void _handlePanUpdate(DragUpdateDetails details, bool isLeftSlider) { if (_totalWidth == 0) return; setState(() { if (isLeftSlider) { _leftOffset += details.delta.dx; // 限制左滑块边界:不能小于0,不能超过右滑块 _leftOffset = _leftOffset.clamp(0, _rightOffset - 20); } else { _rightOffset += details.delta.dx; // 限制右滑块边界:不能小于左滑块,不能超过总宽度 _rightOffset = _rightOffset.clamp(_leftOffset + 20, _totalWidth); } _updateSelectedRange(); }); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (_totalWidth == 0) { _totalWidth = constraints.maxWidth; // 初始化时确保滑块在合理范围 _leftOffset = _leftOffset.clamp(0, _totalWidth - 40); _rightOffset = _rightOffset.clamp(_leftOffset + 20, _totalWidth); _updateSelectedRange(); } return Column( children: [ // 显示当前选中的时间范围 Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( '${_selectedRange.start.format(context)} - ${_selectedRange.end.format(context)}', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), ), // 核心滑动选择区域 SizedBox( height: 60, child: Stack( alignment: Alignment.bottomCenter, children: [ // 时间刻度 Positioned( bottom: 0, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: List.generate(_totalHours + 1, (index) { return SizedBox( width: _totalWidth / _totalHours, child: Text( '$index', textAlign: TextAlign.center, style: const TextStyle(fontSize: 12), ), ); }), ), ), // 选中范围高亮背景 Positioned( bottom: 12, left: _leftOffset, right: _totalWidth - _rightOffset, height: 20, child: Container( decoration: BoxDecoration( color: Colors.blue.withOpacity(0.3), borderRadius: BorderRadius.circular(10), ), ), ), // 左滑块 Positioned( left: _leftOffset - 10, bottom: 8, child: GestureDetector( onPanUpdate: (details) => _handlePanUpdate(details, true), child: Container( width: 20, height: 28, decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(10), boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 2)], ), ), ), ), // 右滑块 Positioned( left: _rightOffset - 10, bottom: 8, child: GestureDetector( onPanUpdate: (details) => _handlePanUpdate(details, false), child: Container( width: 20, height: 28, decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(10), boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 2)], ), ), ), ), ], ), ), ], ); }, ); } } // 时间范围模型类 class TimeRange { final TimeOfDay start; final TimeOfDay end; TimeRange({required this.start, required this.end}); } // 使用示例 // SlidableTimeRangePicker( // onRangeChanged: (range) { // print('选中时间:${range.start.format(context)} - ${range.end.format(context)}'); // }, // )
可自定义扩展的点
- 修改滑块的样式(颜色、大小、阴影)
- 调整选中区域的背景样式(渐变、圆角)
- 支持分钟级精度的刻度显示
- 添加点击定位滑块的功能
- 限制可选择的最小时间跨度(比如最少1小时)
内容的提问来源于stack exchange,提问作者Deekshith Xetty
相关产品推荐
相关产品推荐

