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:
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:
@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:
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:
@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:
child: Align( alignment: _dragAlignment,
child: Card(
child: widget.child,
),
), |
Finally manage the Animation using the GestureDetector as follows:
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:
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:
onPanEnd: (details) { _runAnimation(details.velocity.pixelsPerSecond, size);
}, |
Complete Source Code:
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: