Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Drawer animate using a spring force instead of a linear curve. #3

Merged
merged 3 commits into from
Jul 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ vars = {
'cassowary_dart_revision': '7e5afc5b3956a18636d5b37b1dcba1705865564b',
'collection_dart_revision': '79ebc6fc2dae581cb23ad50a5c600c1b7dd132f8',
'crypto_dart_revision': 'd4558dea1639e5ad2a41d045265b8ece270c2d90',
'newton_dart_revision': '26da04f0c441d005a6ecbf62ae047cd02ec9abc5',
'newton_dart_revision': '9fbe5fbac809246f7ace4176feca13bdf731e393',
'path_dart_revision': '2f3dcdec32011f1bc41194ae3640d6d9292a7096',
'quiver_dart_revision': '6bab7dec34189eee579178eb16d3063c8ae69031',
'source_span_dart_revision': '5c6c13f62fc111adaace3aeb4a38853d64481d06',
Expand Down
1 change: 1 addition & 0 deletions sky/sdk/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dart_pkg("sky") {
"lib/animation/animated_simulation.dart",
"lib/animation/animation_performance.dart",
"lib/animation/curves.dart",
"lib/animation/forces.dart",
"lib/animation/scroll_behavior.dart",
"lib/animation/timeline.dart",
"lib/assets/.gitignore",
Expand Down
32 changes: 16 additions & 16 deletions sky/sdk/lib/animation/animation_performance.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:newton/newton.dart';
import 'dart:async';

import 'package:sky/animation/timeline.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/animation/forces.dart';

abstract class AnimatedVariable {
void setFraction(double t);
Expand Down Expand Up @@ -99,23 +101,20 @@ class AnimationPerformance {
bool get isCompleted => progress == 1.0;
bool get isAnimating => timeline.isAnimating;

void play() {
_animateTo(1.0);
}
void reverse() {
_animateTo(0.0);
}
Future play() => _animateTo(1.0);
Future reverse() => _animateTo(0.0);

void stop() {
timeline.stop();
}

// Resume animating in a direction, with the given velocity.
// TODO(mpcomplete): Allow user to specify the Simulation.
void fling({double velocity: 1.0}) {
Simulation simulation =
timeline.defaultSpringSimulation(velocity: velocity);
timeline.fling(simulation);
// Flings the timeline with an optional force (defaults to a critically damped
// spring) and initial velocity. Negative velocity causes the timeline to go
// in reverse.
Future fling({double velocity: 1.0, Force force}) {
if (force == null)
force = kDefaultSpringForce;
return timeline.fling(force.release(progress, velocity));
}

final List<Function> _listeners = new List<Function>();
Expand All @@ -134,11 +133,12 @@ class AnimationPerformance {
listener();
}

void _animateTo(double target) {
Future _animateTo(double target) {
double remainingDistance = (target - timeline.value).abs();
timeline.stop();
if (remainingDistance != 0.0)
timeline.animateTo(target, duration: duration * remainingDistance);
if (remainingDistance == 0.0)
return new Future.value();
return timeline.animateTo(target, duration: duration * remainingDistance);
}

void _tick(double t) {
Expand Down
3 changes: 2 additions & 1 deletion sky/sdk/lib/animation/scroll_behavior.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
import 'dart:math' as math;

import 'package:newton/newton.dart';
import 'package:sky/animation/forces.dart';

const double _kSecondsPerMillisecond = 1000.0;

abstract class ScrollBehavior {
abstract class ScrollBehavior extends Force {
Simulation release(double position, double velocity) => null;

// Returns the new scroll offset.
Expand Down
13 changes: 0 additions & 13 deletions sky/sdk/lib/animation/timeline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import 'dart:async';
import 'package:newton/newton.dart';
import 'package:sky/animation/animated_simulation.dart';

const double _kEpsilon = 0.001;

// Simple simulation that linearly varies from |begin| to |end| over |duration|.
class TweenSimulation extends Simulation {
final double _durationInSeconds;
Expand Down Expand Up @@ -68,17 +66,6 @@ class Timeline {
_animation.stop();
}

static final SpringDescription _kDefaultSpringDesc =
new SpringDescription.withDampingRatio(
mass: 1.0, springConstant: 500.0, ratio: 1.0);

Simulation defaultSpringSimulation({double velocity: 0.0}) {
// Target just past the 0 or 1 endpoint, because the animation will stop
// once the Spring gets within the epsilon, and we want to stop at 0 or 1.
double target = velocity < 0.0 ? -_kEpsilon : 1.0 + _kEpsilon;
return new SpringSimulation(_kDefaultSpringDesc, value, target, velocity);
}

// Give |simulation| control over the timeline.
Future fling(Simulation simulation) {
stop();
Expand Down
3 changes: 2 additions & 1 deletion sky/sdk/lib/widgets/dismissable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:vector_math/vector_math.dart';
const Duration _kCardDismissFadeout = const Duration(milliseconds: 300);
const double _kMinFlingVelocity = 700.0;
const double _kMinFlingVelocityDelta = 400.0;
const double _kFlingVelocityScale = 1.0/300.0;
const double _kDismissCardThreshold = 0.6;

typedef void DismissedCallback();
Expand Down Expand Up @@ -129,7 +130,7 @@ class Dismissable extends AnimatedComponent {
_dragUnderway = false;
_dragX = event.velocityX.sign;
_position.end = _activeCardDragEndPoint;
_performance.fling(velocity: event.velocityX.abs() / _width);
_performance.fling(velocity: event.velocityX.abs() * _kFlingVelocityScale);
}
}

Expand Down
30 changes: 16 additions & 14 deletions sky/sdk/lib/widgets/drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import 'dart:sky' as sky;

import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/theme/shadows.dart';
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets/animated_component.dart';
Expand All @@ -30,14 +29,11 @@ import 'package:vector_math/vector_math.dart';
// The right nav can vary depending on content.

const double _kWidth = 304.0;
const double _kMinFlingVelocity = 1.2;
const double _kMinFlingVelocity = 365.0;
const double _kFlingVelocityScale = 1.0/300.0;
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
// TODO(mpcomplete): The curve must be linear if we want the drawer to track
// the user's finger. Odeon remedies this by attaching spring forces to the
// initial timeline when animating (so it doesn't look linear).
const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0);
const Curve _kAnimationCurve = linear;

typedef void DrawerStatusChangeHandler (bool showing);

Expand Down Expand Up @@ -69,7 +65,7 @@ class Drawer extends AnimatedComponent {
AnimationPerformance _performance;

void initState() {
_position = new AnimatedType<Point>(_kClosedPosition, end: _kOpenPosition, curve: _kAnimationCurve);
_position = new AnimatedType<Point>(_kClosedPosition, end: _kOpenPosition);
_maskColor = new AnimatedColor(colors.transparent, end: const Color(0x7F000000));
_performance = new AnimationPerformance()
..duration = _kBaseSettleDuration
Expand All @@ -95,11 +91,18 @@ class Drawer extends AnimatedComponent {
void _show() {
if (navigator != null)
navigator.pushState(this, (_) => _performance.reverse());
_performance.play();
_fling(1.0);
}

void _hide() {
_performance.reverse();
_fling(-1.0);
}

// We fling the performance timeline instead of animating it to give it a
// nice spring effect. We can't use curves for this because we need a linear
// curve in order to track the user's finger while dragging.
void _fling(double direction) {
_performance.fling(velocity: direction.sign);
}

Widget build() {
Expand Down Expand Up @@ -151,9 +154,9 @@ class Drawer extends AnimatedComponent {
DrawerStatus get _status => _performance.isDismissed ? DrawerStatus.inactive : DrawerStatus.active;
bool get _isMostlyClosed => xPosition <= -_kWidth/2;

void _settle() => _isMostlyClosed ? _performance.reverse() : _performance.play();
void _settle() => _fling(_isMostlyClosed ? -1.0 : 1.0);

void handleMaskTap(_) => _performance.reverse();
void handleMaskTap(_) => _fling(-1.0);

// TODO(mpcomplete): Figure out how to generalize these handlers on a
// "PannableThingy" interface.
Expand All @@ -176,8 +179,7 @@ class Drawer extends AnimatedComponent {
}

void handleFlingStart(event) {
double velocityX = event.velocityX / _kWidth;
if (velocityX.abs() >= _kMinFlingVelocity)
_performance.fling(velocity: velocityX);
if (event.velocityX.abs() >= _kMinFlingVelocity)
_performance.fling(velocity: event.velocityX * _kFlingVelocityScale);
}
}