Flutter – Physics Simulation in Animation
Physics simulation in Flutter is a beautiful way to Animate components of the flutter app to make it look more realistic and interactive. These can be used to create a range of animations like falling objects due to gravity to making a container seem attached to a spring. In this article, we will explore the same by building a simple application.
Follow the below steps to create a simple physics simulation in a Widget:
- Develop an Animation controller.
- Use gestures for movement.
- Display the animation.
- Use velocity to simulate the springing motion.
Let’s discuss them in detail:
Developing an Animation Controller:
To create the Animation controller make a StatefulWidget called DraggableCard as shown below:
Dart
import 'package:flutter/material.dart' ; main() { runApp(MaterialApp(home: PhysicsCardDragDemo())); } class PhysicsCardDragDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: DraggableCard( child: FlutterLogo( size: 128, ), ), ); } } class DraggableCard extends StatefulWidget { final Widget child; DraggableCard({ this .child}); @override _DraggableCardState createState() => _DraggableCardState(); } class _DraggableCardState extends State<DraggableCard> { @override void initState() { super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Align( child: Card( child: widget.child, ), ); } } |
Using gestures for movement:
Here we will make the widget move when dragged in any direction. The movement can be mapped using a GestureDetector that handles onPanEnd, onPanUpdate, and onPanDown as shown below:
Dart
@override Widget build(BuildContext context) { var size = MediaQuery.of(context).size; return GestureDetector( onPanDown: (details) {}, onPanUpdate: (details) { setState(() { _dragAlignment += Alignment( details.delta.dx / (size.width / 2), details.delta.dy / (size.height / 2), ); }); }, onPanEnd: (details) {}, child: Align( alignment: _dragAlignment, child: Card( child: widget.child, ), ), ); } |
Display Animation:
Use the Animation<Alignment> field and the _runAnimation method to produce a spring like spring-like effect as shown below to display the Animation:
Dart
Animation<Alignment> _animation; void _runAnimation() { _animation = _controller.drive( AlignmentTween( begin: _dragAlignment, end: Alignment.center, ), ); _controller.reset(); _controller.forward(); } |
Now, whenever the Animation Controller produces a value update the _dragAlignment field as shown below:
Dart
@override void initState() { super.initState(); _controller = AnimationController(vsync: this , duration: Duration(seconds: 1)); _controller.addListener(() { setState(() { _dragAlignment = _animation.value; }); }); } |
Now, use the _dragAlignment field to Align the widget as shown below:
Dart
child: Align( alignment: _dragAlignment, child: Card( child: widget.child, ), ), |
Finally manage the Animation using the GestureDetector as follows:
Dart
onPanDown: (details) { _controller.stop(); }, onPanUpdate: (details) { setState(() { _dragAlignment += Alignment( details.delta.dx / (size.width / 2), details.delta.dy / (size.height / 2), ); }); }, onPanEnd: (details) { _runAnimation(); }, |
Using velocity to simulate the springing motion:
First, import the Physics package as below:
import 'package:flutter/physics.dart';
Now use the animateWith() method of the AnimationController to create a spring-like effect as shown below:
Dart
void _runAnimation(Offset pixelsPerSecond, Size size) { _animation = _controller.drive( AlignmentTween( begin: _dragAlignment, end: Alignment.center, ), ); final unitsPerSecondX = pixelsPerSecond.dx / size.width; final unitsPerSecondY = pixelsPerSecond.dy / size.height; final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY); final unitVelocity = unitsPerSecond.distance; const spring = SpringDescription( mass: 30, stiffness: 1, damping: 1, ); final simulation = SpringSimulation(spring, 0, 1, -unitVelocity); _controller.animateWith(simulation); } |
Finally, make a call to _runAnimation() method with velocity and size as a parameter as shown below:
Dart
onPanEnd: (details) { _runAnimation(details.velocity.pixelsPerSecond, size); }, |
Complete Source Code:
Dart
import 'package:flutter/material.dart' ; import 'package:flutter/physics.dart' ; main() { runApp(MaterialApp(home: PhysicsCardDragDemo())); } class PhysicsCardDragDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( 'GeeksForGeeks' ), backgroundColor: Colors.green, ), body: DraggableCard( child: Container( width: 140, height: 140, decoration: BoxDecoration( color: Colors.green ), ) ), ); } } class DraggableCard extends StatefulWidget { final Widget child; DraggableCard({ this .child}); @override _DraggableCardState createState() => _DraggableCardState(); } class _DraggableCardState extends State<DraggableCard> with SingleTickerProviderStateMixin { AnimationController _controller; Alignment _dragAlignment = Alignment.center; Animation<Alignment> _animation; void _runAnimation(Offset pixelsPerSecond, Size size) { _animation = _controller.drive( AlignmentTween( begin: _dragAlignment, end: Alignment.center, ), ); // evaluating velocity final unitsPerSecondX = pixelsPerSecond.dx / size.width; final unitsPerSecondY = pixelsPerSecond.dy / size.height; final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY); final unitVelocity = unitsPerSecond.distance; const spring = SpringDescription( mass: 30, stiffness: 1, damping: 1, ); final simulation = SpringSimulation(spring, 0, 1, -unitVelocity); _controller.animateWith(simulation); } @override void initState() { super.initState(); _controller = AnimationController(vsync: this ); _controller.addListener(() { setState(() { _dragAlignment = _animation.value; }); }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; return GestureDetector( onPanDown: (details) { _controller.stop(); }, onPanUpdate: (details) { setState(() { _dragAlignment += Alignment( details.delta.dx / (size.width / 2), details.delta.dy / (size.height / 2), ); }); }, onPanEnd: (details) { _runAnimation(details.velocity.pixelsPerSecond, size); }, child: Align( alignment: _dragAlignment, child: Card( child: widget.child, ), ), ); } } |
Output:
Please Login to comment...