Files
wheel_spinner_tutorial/lib/spinner.dart
2019-03-22 17:41:23 +02:00

176 lines
4.6 KiB
Dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class WheelSpinner extends StatefulWidget {
final double max;
final double min;
final double value;
final Function(double value) onSlideUpdate;
final Function(double value) onSlideDone;
WheelSpinner({
@required this.value,
this.max = double.infinity,
this.min = double.negativeInfinity,
this.onSlideDone,
this.onSlideUpdate,
});
@override
_WheelSpinnerState createState() => _WheelSpinnerState();
}
class _WheelSpinnerState extends State<WheelSpinner>
with SingleTickerProviderStateMixin {
double value;
Offset dragStartOffset;
double dragStartValue;
AnimationController flingController;
Animation<double> flingAnimation;
void Function() currentFlingListener;
@override
void initState() {
flingAnimation = AlwaysStoppedAnimation(0.0);
flingController = AnimationController(vsync: this);
value = widget.value;
super.initState();
}
@override
Widget build(BuildContext context) {
double shadowOffset = 0.2;
return GestureDetector(
onVerticalDragStart: onDragStart,
onVerticalDragUpdate: onDragUpdate,
onVerticalDragEnd: onDragDone,
child: Container(
width: 60,
height: 100,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: [0.0, shadowOffset, 1.0 - shadowOffset, 1.0],
colors: [
Colors.grey[350],
Colors.grey[50],
Colors.grey[50],
Colors.grey[350]
],
),
border: Border.all(
width: 1,
style: BorderStyle.solid,
color: Colors.grey[600],
),
borderRadius: BorderRadius.circular(3.5),
),
child: Container(
child: Stack(
children: List<Widget>.generate(
11,
(i) {
var top = lineTopPos(value, i);
return Positioned.fromRect(
rect: Rect.fromLTWH(
0.0,
top,
60,
0,
),
child: Divider(
color: Colors.grey[600],
),
);
},
).toList(),
),
),
),
);
}
double lineTopPos(double value, int i) {
double valueFraction = (value.ceil() - value) * 10.0;
double indexedTop = 10.0 * i;
double top = indexedTop + valueFraction;
return top;
}
void onDragStart(details) {
// flingController.stop();
// flingAnimation = AlwaysStoppedAnimation(0.0);
setState(() {
dragStartOffset = details.globalPosition;
dragStartValue = value;
});
}
void onDragUpdate(details) {
// flingController.stop();
// flingAnimation = AlwaysStoppedAnimation(0.0);
var newValue = clamp(
dragStartValue - (details.globalPosition - dragStartOffset).dy / 20.0,
widget.min,
widget.max);
setState(() {
value = newValue;
});
if (widget.onSlideUpdate != null) {
widget.onSlideUpdate(value);
}
}
void onDragDone(DragEndDetails details) {
setState(() {
dragStartOffset = null;
});
double velocity = details.primaryVelocity;
if (velocity.abs() == 0) {
if (widget.onSlideDone != null) {
widget.onSlideDone(value);
}
return;
}
double originalValue = value;
currentFlingListener = flingListener(originalValue);
flingController.duration = Duration(milliseconds: velocity.abs().toInt());
flingAnimation =
Tween(begin: 0.0, end: velocity / 100).animate(CurvedAnimation(
curve: Curves.decelerate,
parent: flingController,
))
..addListener(currentFlingListener);
flingController
..reset()
..forward();
}
flingListener(double originalValue) {
return () {
double newValue =
clamp(originalValue - flingAnimation.value, widget.min, widget.max);
if (newValue != value) {
setState(() {
value = newValue;
});
if (flingAnimation.value == flingController.upperBound) {
if (widget.onSlideDone != null) {
widget.onSlideDone(value);
}
} else {
if (widget.onSlideUpdate != null) {
widget.onSlideUpdate(value);
}
}
}
};
}
}
double clamp<T extends num>(T number, T low, T high) =>
max(low * 1.0, min(number * 1.0, high * 1.0));