From b51d75966a0ae22b51cb5b38fa2fb402a8a28586 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Thu, 8 Aug 2019 10:10:57 -0700 Subject: [PATCH] Add scroll physics demo (#123) * add spring physics card demo * fix button colors in animations project * code review updates * refactor method into runAnimation * combine updateAnimation and runAnimation * update comment --- animations/lib/main.dart | 3 + .../lib/src/basics/02_page_route_builder.dart | 1 - .../lib/src/basics/05_custom_tween.dart | 3 +- .../lib/src/misc/physics_card_drag.dart | 117 ++++++++++++++++++ 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 animations/lib/src/misc/physics_card_drag.dart diff --git a/animations/lib/main.dart b/animations/lib/main.dart index dceb55403..4087e8638 100644 --- a/animations/lib/main.dart +++ b/animations/lib/main.dart @@ -14,6 +14,7 @@ import 'src/misc/card_swipe.dart'; import 'src/misc/carousel.dart'; import 'src/misc/expand_card.dart'; import 'src/misc/focus_image.dart'; +import 'src/misc/physics_card_drag.dart'; import 'src/misc/repeating_animation.dart'; void main() => runApp(AnimationSamples()); @@ -48,6 +49,8 @@ final miscDemos = [ Demo('Card Swipe', CardSwipeDemo.routeName, (context) => CardSwipeDemo()), Demo('Repeating Animation', RepeatingAnimationDemo.routeName, (context) => RepeatingAnimationDemo()), + Demo('Spring Physics', PhysicsCardDragDemo.routeName, + (context) => PhysicsCardDragDemo()), ]; final basicDemoRoutes = diff --git a/animations/lib/src/basics/02_page_route_builder.dart b/animations/lib/src/basics/02_page_route_builder.dart index 1ee16a16a..1180114ab 100644 --- a/animations/lib/src/basics/02_page_route_builder.dart +++ b/animations/lib/src/basics/02_page_route_builder.dart @@ -9,7 +9,6 @@ class PageRouteBuilderDemo extends StatelessWidget { body: Center( child: RaisedButton( child: Text('Go!'), - color: Theme.of(context).primaryColor, onPressed: () { Navigator.of(context).push(_createRoute()); }, diff --git a/animations/lib/src/basics/05_custom_tween.dart b/animations/lib/src/basics/05_custom_tween.dart index 4e40c2589..5785f7be8 100644 --- a/animations/lib/src/basics/05_custom_tween.dart +++ b/animations/lib/src/basics/05_custom_tween.dart @@ -43,12 +43,13 @@ class _CustomTweenDemoState extends State return Scaffold( appBar: AppBar( actions: [ - RaisedButton( + MaterialButton( child: Text( controller.status == AnimationStatus.completed ? 'Delete Essay' : 'Write Essay', ), + textColor: Colors.white, onPressed: () { if (controller.status == AnimationStatus.completed) { controller.reverse(); diff --git a/animations/lib/src/misc/physics_card_drag.dart b/animations/lib/src/misc/physics_card_drag.dart new file mode 100644 index 000000000..c41c47f25 --- /dev/null +++ b/animations/lib/src/misc/physics_card_drag.dart @@ -0,0 +1,117 @@ +// Copyright 2019 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; + +class PhysicsCardDragDemo extends StatelessWidget { + static const String routeName = '/misc/physics_card'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: DraggableCard( + child: FlutterLogo( + size: 128, + ), + ), + ); + } +} + +/// A draggable card that moves back to [Alignment.center] when it's +/// released. +class DraggableCard extends StatefulWidget { + final Widget child; + DraggableCard({this.child}); + + @override + _DraggableCardState createState() => _DraggableCardState(); +} + +class _DraggableCardState extends State + with SingleTickerProviderStateMixin { + AnimationController _controller; + + /// The alignment of the card as it is dragged or being animated. + /// + /// While the card is being dragged, this value is set to the values computed + /// in the GestureDetector onPanUpdate callback. If the animation is running, + /// this value is set to the value of the [_animation]. + Alignment _dragAlignment = Alignment.center; + + Animation _animation; + + /// Calculates and runs a [SpringSimulation] + void _runAnimation(Offset pixelsPerSecond, Size size) { + _animation = _controller.drive( + AlignmentTween( + begin: _dragAlignment, + end: Alignment.center, + ), + ); + // Calculate the velocity relative to the unit interval, [0,1], + // used by the animation controller. + 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, + ), + ), + ); + } +}