'What's the best way to make a spinning wheel?
I'm trying to make a wheel that can spin when a user drags up and down on a screen. It's essentially an infinite vertical scroll. So far I can make it turn while actually scrolling, but I'd like to incorporate physics to make it keep spinning when you let go. At the moment I'm using a GestureDetector
to put an angle into Provider
, which is used to transform some child widgets that make up the wheel, like so:
GestureDetector(
onVerticalDragUpdate: (offset) {
provider.wheelAngle += atan(offset.delta.dy / wheelRadius);
},
);
I'm sure I can do the physics part manually by handling the onVerticalDragEnd
, but given that this is essentially just scrolling, I was wondering if it would make more sense to somehow leverage Flutter's built in scrolling stuff - maybe ScrollPhysics
or one of the classes that derive from it. I don't want to reinvent the wheel (no pun intended), but I also don't want extra complexity by trying to force something else into doing what I need if it isn't a good fit. I can't quite wrap my head around ScrollPhysics
, so I feel like it might be going down the route of over-complicated. Any gut feelings on what the best technique would be?
Solution 1:[1]
As pskink mentioned in the comments, animateWith
is the key. In case it helps anyone in the future, here's an untested, slimmed-down version of what I ended up with. It switches between using FrictionSimulation
when spinning freely and SpringSimulation
when snapping to a particular angle.
class Wheel extends StatefulWidget {
const Wheel({Key? key}) : super(key: key);
@override
State<Wheel> createState() => _WheelState();
}
class _WheelState extends State<Wheel> with SingleTickerProviderStateMixin {
late AnimationController _wheelAnimationController;
bool _isSnapping = false;
double _radius = 0.0; // Probably set this in the constructor.
static const double velocitySnapThreshold = 1.0;
static const double distanceSnapThreshold = 0.25;
@override
Widget build(BuildContext context) {
var provider = context.read<WheelAngleProvider>();
_wheelAnimationController = AnimationController.unbounded(vsync: this, value: provider.wheelAngle);
_wheelAnimationController.addListener(() {
if (!_isSnapping) {
// Snap to an item if not spinning quickly.
var wheelAngle = _wheelAnimationController.value;
var velocity = _wheelAnimationController.velocity.abs();
var closestSnapAngle = getClosestSnapAngle(wheelAngle);
var distance = (closestSnapAngle - wheelAngle).abs();
if (velocity == 0 || (velocity < velocitySnapThreshold && distance < distanceSnapThreshold)) {
snapTo(closestSnapAngle);
}
}
provider.wheelAngle = _wheelAnimationController.value;
});
return Stack(
children: [
// ... <-- Visible things go here
// Vertical dragging anywhere on the screen rotates the wheel, hence the SafeArea.
SafeArea(
child: GestureDetector(
onVerticalDragDown: (details) {
_wheelAnimationController.stop();
_isSnapping = false;
},
onVerticalDragUpdate: (offset) =>
provider.wheelAngle = provider.wheelAngle + atan(offset.delta.dy / _radius),
onVerticalDragEnd: (details) => onRotationEnd(provider, details.primaryVelocity),
),
),
],
);
}
double getClosestSnapAngle(double currentAngle) {
// Do what you gotta do here.
return 0.0;
}
void snapTo(double snapAngle) {
var wheelAngle = _wheelAnimationController.value;
_wheelAnimationController.stop();
_isSnapping = true;
var springSimulation = SpringSimulation(
SpringDescription(mass: 20.0, stiffness: 10.0, damping: 1.0),
wheelAngle,
snapAngle,
_wheelAnimationController.velocity,
);
_wheelAnimationController.animateWith(springSimulation);
}
void onRotationEnd(WheelAngleProvider provider, double? velocity) {
// When velocity is not null, this is the result of a fling and it needs to spin freely.
if (velocity != null) {
_wheelAnimationController.stop();
var frictionSimulation = FrictionSimulation(0.5, provider.wheelAngle, velocity / 200);
_wheelAnimationController.animateWith(frictionSimulation);
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Mark R |