I am working on using the RangeSlider to navigate a user selected range of WMS layers which can be animated to a map based on the current value which is between the range. The current value needs to be draggable, and of course set from the animation sequence (not shown here). I have created a sample while trying to get this working that just includes the slider and hard coded date values. In doing so I add a Positioned > GestureDetector which holds a draggable icon and the current date. This almost works, however the current indicator i put on there starts positioned just to the right of the beginning (i can move it over by adding Left: -10) but the most troubling issue i am facing is that the indicator can be dragged past the timeline approximately the amount of the "padding" between the slider and the edge of the screen. I can move the end range in to see that this amount that it goes past the allowable range is variable and decreased slightly as the end limit narrows the range.
I am attaching an animated gif at the bottom that show this happening. Notice it starts indented to the right, then slides way past the end, when i slide the limit in the ratio that it goes past gets less and less as i move the range limit in.
I have tried a lot of things to get this to work properly and i have a ton of debug print statements to try to figure out why this is happening and how to fix it. I believe the problem may be due to the width of the RangeSlider not actually being used, but rather the entire screen. I can't get it to tell me the width of the RangeSlider.
Any assistance would be appreciated.
Here is the code :
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'WMS Time Slider Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'WMS Time Slider Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[TimeSlider()],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class TimeSlider extends StatefulWidget {
const TimeSlider({Key? key}) : super(key: key);
@override
_TimeSliderState createState() => _TimeSliderState();
}
class _TimeSliderState extends State<TimeSlider> {
static const int MIN_DATE = 3848; // July 15, 1980
static const int MAX_DATE = 14974; // December 31, 2010
final GlobalKey<ScaffoldState> _sliderKey = GlobalKey();
int _startValue = MIN_DATE;
int _endValue = MAX_DATE;
int _currentValue = MIN_DATE;
double _sliderWidth = 0;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext ctx, BoxConstraints constraints) {
_sliderWidth = constraints.maxWidth;
print("slider width: " + _sliderWidth.toString());
print("Position: " + _getPosition().toString());
return Stack(clipBehavior: Clip.none, children: [
RangeSlider(
key: _sliderKey,
values: RangeValues(_startValue.toDouble(), _endValue.toDouble()),
min: MIN_DATE.toDouble(),
max: MAX_DATE.toDouble(),
divisions: ((MAX_DATE - MIN_DATE) / 365)
.round()
.toInt(), // one division per day
onChanged: (RangeValues values) {
setState(() {
_startValue = values.start.toInt();
_endValue = values.end.toInt();
if (_currentValue < _startValue) {
_currentValue = _startValue;
} else if (_currentValue > _endValue) {
_currentValue = _endValue;
}
});
},
onChangeEnd: (RangeValues values) {
setState(() {
// _currentValue = values.start.toInt();
});
print(formatDate(_currentValue));
// Update the WMS layer animation with the current value
// ...
},
),
Positioned(
top: 0,
left: _getPosition(),
child: GestureDetector(
onHorizontalDragUpdate: (details) {
print("dragging");
setState(() {
final range = MAX_DATE - MIN_DATE;
print("got range");
final dx = details.delta.dx;
final position = _getPosition() + dx;
print("got position");
final DateTime startDate = DateTime(1970, 1, 1);
// Calculate the number of days between the current date and the start date
_currentValue = _getDate(position).difference(startDate).inDays;
// ((position / MediaQuery.of(context).size.width) * range)
// .round() +
// MIN_DATE;
print("start: " + formatDate(_startValue));
print("Current: " + formatDate(_currentValue));
print("End: " + formatDate(_endValue));
// Prevent _currentValue from going outside the range
if (_currentValue < _startValue) {
print("fixed less");
_currentValue = _startValue;
print("Current: " + formatDate(_currentValue));
} else if (_currentValue > _endValue) {
_currentValue = _endValue;
}
});
},
child: Column(
children: [
Icon(Icons.circle, color: Colors.deepPurple),
Text(
formatDate(_currentValue),
style: TextStyle(color: Colors.purple),
),
],
),
),
),
]);
});
}
double _getPosition() {
final date = DateTime.fromMillisecondsSinceEpoch(
_currentValue * 24 * 60 * 60 * 1000);
final min =
DateTime.fromMillisecondsSinceEpoch(MIN_DATE * 24 * 60 * 60 * 1000);
final max =
DateTime.fromMillisecondsSinceEpoch(MAX_DATE * 24 * 60 * 60 * 1000)
.subtract(Duration(days: 32));
final double ratio =
date.difference(min).inDays / max.difference(min).inDays;
print("ratio: " + ratio.toString());
// return ratio * MediaQuery.of(context).size.width;
print("MAX_DATE: " + MAX_DATE.toString());
print("MIN_DATE: " + MIN_DATE.toString());
print("_currentValue: " + _currentValue.toString());
final range = MAX_DATE - MIN_DATE;
final value = _currentValue - MIN_DATE;
final sliderWidth = (MediaQuery.of(context).size.width);
print("range: " + range.toString());
print("Width buffered: " +
(MediaQuery.of(context).size.width - 64).toString());
print("Width: " + MediaQuery.of(context).size.width.toString());
double position = (value / range) * (sliderWidth);
return position;
}
DateTime _getDate(double position) {
final min =
DateTime.fromMillisecondsSinceEpoch(MIN_DATE * 24 * 60 * 60 * 1000);
final max =
DateTime.fromMillisecondsSinceEpoch(MAX_DATE * 24 * 60 * 60 * 1000);
final double ratio = position / (MediaQuery.of(context).size.width);
final int days = (ratio * max.difference(min).inDays).round();
final date = min.add(Duration(days: days));
// Make sure the date doesn't go past the max date
if (date.isAfter(max)) {
return max;
} else {
return date;
}
}
String formatDate(int daysSinceEpoch) {
final dateTime = DateTime.fromMillisecondsSinceEpoch(
daysSinceEpoch * 24 * 60 * 60 * 1000);
return '${dateTime.year}-${_padZero(dateTime.month)}-${_padZero(dateTime.day)}';
}
String _padZero(int value) {
return value.toString().padLeft(2, '0');
}
}