Open In App

Flutter – Physics Simulation in Animation

Improve
Improve
Like Article
Like
Save
Share
Report

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:

 

 



Last Updated : 18 Aug, 2021
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads