提交 7a386db1 编写于 作者: M mpcomplete

Merge pull request #352 from mpcomplete/generic.slidein

Use SnackBar's SlideInIntention for Drawer animation.
......@@ -69,8 +69,8 @@ class AnimatedList extends AnimatedVariable {
String toString() => 'AnimatedList([$variables])';
}
class AnimatedColor extends AnimatedValue<Color> {
AnimatedColor(Color begin, { Color end, Curve curve: linear })
class AnimatedColorValue extends AnimatedValue<Color> {
AnimatedColorValue(Color begin, { Color end, Curve curve: linear })
: super(begin, end: end, curve: curve);
void setProgress(double t) {
......
......@@ -8,10 +8,7 @@ import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/forces.dart';
import 'package:sky/animation/timeline.dart';
enum AnimationDirection {
forward,
reverse
}
export 'package:sky/animation/forces.dart' show Direction;
enum AnimationStatus {
dismissed, // stoped at 0
......@@ -39,8 +36,21 @@ class AnimationPerformance {
Timeline _timeline;
Timeline get timeline => _timeline;
AnimationDirection _direction;
AnimationDirection get direction => _direction;
Direction _direction;
Direction get direction => _direction;
// If non-null, animate with this force instead of a tween animation.
Force attachedForce;
void addVariable(AnimatedVariable newVariable) {
if (variable == null) {
variable = newVariable;
} else if (variable is AnimatedList) {
(variable as AnimatedList).variables.add(newVariable);
} else {
variable = new AnimatedList([variable, newVariable]);
}
}
double get progress => timeline.value;
void set progress(double t) {
......@@ -59,33 +69,34 @@ class AnimationPerformance {
return AnimationStatus.completed;
if (!isAnimating && progress == 0.0)
return AnimationStatus.dismissed;
return direction == AnimationDirection.forward ?
return direction == Direction.forward ?
AnimationStatus.forward :
AnimationStatus.reverse;
}
Future play([AnimationDirection direction = AnimationDirection.forward]) {
Future play([Direction direction = Direction.forward]) {
_direction = direction;
return resume();
}
Future forward() => play(AnimationDirection.forward);
Future reverse() => play(AnimationDirection.reverse);
Future resume() => _animateTo(direction == AnimationDirection.forward ? 1.0 : 0.0);
Future forward() => play(Direction.forward);
Future reverse() => play(Direction.reverse);
Future resume() {
if (attachedForce != null)
return fling(_direction, force: attachedForce);
return _animateTo(direction == Direction.forward ? 1.0 : 0.0);
}
void stop() {
timeline.stop();
}
// 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}) {
// Flings the timeline in the given direction with an optional force
// (defaults to a critically damped spring) and initial velocity.
Future fling(Direction direction, {double velocity: 0.0, Force force}) {
if (force == null)
force = kDefaultSpringForce;
// This is an approximation - the force may not necessarily result in
// animating the same direction as the initial velocity.
_direction = velocity >= 0.0 ? AnimationDirection.forward : AnimationDirection.reverse;
return timeline.fling(force.release(progress, velocity));
_direction = direction;
return timeline.fling(force.release(progress, velocity, _direction));
}
final List<Function> _listeners = new List<Function>();
......
......@@ -4,9 +4,15 @@
import 'package:newton/newton.dart';
// TODO(mpcomplete): This doesn't belong here.
enum Direction {
forward,
reverse
}
// Base class for creating Simulations for the animation Timeline.
abstract class Force {
Simulation release(double position, double velocity);
Simulation release(double position, double velocity, Direction direction);
}
class SpringForce extends Force {
......@@ -17,18 +23,17 @@ class SpringForce extends Force {
// respectively.
final double left, right;
Simulation release(double position, double velocity) {
Simulation release(double position, double velocity, Direction direction) {
// Target just past the endpoint, because the animation will stop once the
// Spring gets within the epsilon, and we want to stop at the endpoint.
double target = velocity < 0.0 ?
double target = direction == Direction.reverse ?
this.left - _kEpsilon : this.right + _kEpsilon;
return new SpringSimulation(spring, position, target, velocity);
}
}
final SpringDescription _kDefaultSpringDesc =
new SpringDescription.withDampingRatio(
mass: 1.0, springConstant: 500.0, ratio: 1.0);
new SpringDescription.withDampingRatio(mass: 1.0, springConstant: 500.0, ratio: 1.0);
final SpringForce kDefaultSpringForce = new SpringForce(_kDefaultSpringDesc);
const double _kEpsilon = 0.001;
......@@ -5,11 +5,10 @@
import 'dart:math' as math;
import 'package:newton/newton.dart';
import 'package:sky/animation/forces.dart';
const double _kSecondsPerMillisecond = 1000.0;
abstract class ScrollBehavior extends Force {
abstract class ScrollBehavior {
Simulation release(double position, double velocity) => null;
// Returns the new scroll offset.
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file contains a set of common intentions to use with AnimationContainer
// for describing how to animate certain properties.
import 'dart:sky';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/basic.dart';
import 'package:vector_math/vector_math.dart';
// Slides a container in from |start| to |end| when the container's |tag| is
// true (reverses if false).
class SlideInIntention extends AnimationIntention {
SlideInIntention({Duration duration, this.performance, Point start, Point end}) {
if (performance == null) {
assert(duration != null);
performance = new AnimationPerformance(duration: duration);
}
_position = new AnimatedValue<Point>(start, end: end);
performance.addVariable(_position);
}
AnimatedValue<Point> _position;
AnimationPerformance performance;
void initFields(AnimatedContainer container) {
performance.addListener(() { _updateProgress(container); });
performance.progress = 0.0;
if (container.tag)
performance.play();
}
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
if (original.tag != updated.tag) {
original.tag = updated.tag;
performance.play(original.tag ? Direction.forward : Direction.reverse);
}
}
void _updateProgress(AnimatedContainer container) {
container.setState(() {
container.transform = new Matrix4.identity()
..translate(_position.value.x, _position.value.y);
});
}
}
// Changes color from |start| to |end| when the container's |tag| is true
// (reverses if false).
class ColorTransitionIntention extends AnimationIntention {
ColorTransitionIntention({Duration duration, this.performance, Color start, Color end}) {
if (performance == null) {
assert(duration != null);
performance = new AnimationPerformance(duration: duration);
}
_color = new AnimatedColorValue(start, end: end);
performance.addVariable(_color);
}
AnimatedColorValue _color;
AnimationPerformance performance;
void initFields(AnimatedContainer container) {
performance.addListener(() { _updateProgress(container); });
performance.progress = 0.0;
if (container.tag)
performance.play();
}
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
if (original.tag != updated.tag) {
original.tag = updated.tag;
performance.play(original.tag ? Direction.forward : Direction.reverse);
}
}
void _updateProgress(AnimatedContainer container) {
container.setState(() {
container.decoration = new BoxDecoration(backgroundColor: _color.value);
});
}
}
......@@ -2,18 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:sky' as sky;
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/forces.dart';
import 'package:sky/theme/shadows.dart';
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets/animated_component.dart';
import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/animation_intentions.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/scrollable_viewport.dart';
import 'package:sky/widgets/theme.dart';
import 'package:vector_math/vector_math.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
......@@ -34,12 +35,13 @@ const double _kWidth = 304.0;
const double _kMinFlingVelocity = 365.0;
const double _kFlingVelocityScale = 1.0 / 300.0;
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
const Duration _kThemeChangeDuration = const Duration(milliseconds: 200);
const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0);
typedef void DrawerStatusChangedCallback(AnimationStatus status);
class Drawer extends AnimatedComponent {
class Drawer extends StatefulComponent {
Drawer({
Key key,
this.children,
......@@ -55,70 +57,58 @@ class Drawer extends AnimatedComponent {
DrawerStatusChangedCallback onStatusChanged;
Navigator navigator;
AnimatedValue<Point> _position;
AnimatedColor _maskColor;
AnimationPerformance _performance;
SlideInIntention _intention;
ColorTransitionIntention _maskColorIntention;
AnimationPerformance get _performance => _intention.performance;
void initState() {
_position = new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition);
_maskColor = new AnimatedColor(colors.transparent, end: const Color(0x7F000000));
_performance = new AnimationPerformance()
..duration = _kBaseSettleDuration
..variable = new AnimatedList([_position, _maskColor])
..addStatusListener(_onStatusChanged);
watch(_performance);
if (showing)
_show();
_intention = new SlideInIntention(
duration: _kBaseSettleDuration, start: _kClosedPosition, end: _kOpenPosition);
_maskColorIntention = new ColorTransitionIntention(
performance: _intention.performance, start: colors.transparent, end: const Color(0x7F000000));
_performance.addStatusListener(_onStatusChanged);
// Use a spring force for animating the drawer. We can't use curves for
// this because we need a linear curve in order to track the user's finger
// while dragging.
_performance.attachedForce = kDefaultSpringForce;
if (navigator != null)
navigator.pushState(this, (_) => _performance.reverse());
}
void syncFields(Drawer source) {
children = source.children;
level = source.level;
navigator = source.navigator;
if (showing != source.showing) {
showing = source.showing;
showing ? _show() : _hide();
}
showing = source.showing;
onStatusChanged = source.onStatusChanged;
super.syncFields(source);
}
void _show() {
if (navigator != null)
navigator.pushState(this, (_) => _performance.reverse());
_fling(1.0);
}
void _hide() {
_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() {
var mask = new Listener(
child: new Container(
decoration: new BoxDecoration(backgroundColor: _maskColor.value)
child: new AnimatedContainer(
intentions: [_maskColorIntention],
tag: showing
),
onGestureTap: handleMaskTap
);
Matrix4 transform = new Matrix4.identity();
transform.translate(_position.value.x, _position.value.y);
Widget content = new Transform(
transform: transform,
child: new Container(
decoration: new BoxDecoration(
backgroundColor: Theme.of(this).canvasColor,
boxShadow: shadows[level]),
width: _kWidth,
child: new ScrollableBlock(children)
));
Widget content = new AnimatedContainer(
intentions: [
_intention,
// TODO(mpcomplete): it should be easier to override some intentions,
// and have those you don't care about revert to a sensible default.
new ImplicitlySyncDecorationIntention(_kThemeChangeDuration),
new ImplicitlySyncWidthIntention(_kThemeChangeDuration),
],
tag: showing,
decoration: new BoxDecoration(
backgroundColor: Theme.of(this).canvasColor,
boxShadow: shadows[level]),
width: _kWidth,
child: new ScrollableBlock(children)
);
return new Listener(
child: new Stack([ mask, content ]),
......@@ -130,27 +120,27 @@ class Drawer extends AnimatedComponent {
);
}
double get xPosition => _position.value.x;
void _onStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed &&
navigator != null &&
navigator.currentRoute is RouteState &&
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
navigator.pop();
if (onStatusChanged != null)
onStatusChanged(status);
scheduleMicrotask(() {
if (status == AnimationStatus.dismissed &&
navigator != null &&
navigator.currentRoute is RouteState &&
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
navigator.pop();
if (onStatusChanged != null)
onStatusChanged(status);
});
}
bool get _isMostlyClosed => xPosition <= -_kWidth/2;
bool get _isMostlyClosed => _performance.progress < 0.5;
void _settle() => _fling(_isMostlyClosed ? -1.0 : 1.0);
void _settle() { _isMostlyClosed ? _performance.reverse() : _performance.play(); }
void handleMaskTap(_) => _fling(-1.0);
void handleMaskTap(_) { _performance.reverse(); }
// TODO(mpcomplete): Figure out how to generalize these handlers on a
// "PannableThingy" interface.
void handlePointerDown(_) => _performance.stop();
void handlePointerDown(_) { _performance.stop(); }
void handlePointerMove(sky.PointerEvent event) {
if (_performance.isAnimating)
......@@ -169,7 +159,10 @@ class Drawer extends AnimatedComponent {
}
void handleFlingStart(event) {
if (event.velocityX.abs() >= _kMinFlingVelocity)
_performance.fling(velocity: event.velocityX * _kFlingVelocityScale);
if (event.velocityX.abs() >= _kMinFlingVelocity) {
_performance.fling(
event.velocityX < 0.0 ? Direction.reverse : Direction.forward,
velocity: event.velocityX.abs() * _kFlingVelocityScale);
}
}
}
......@@ -4,16 +4,15 @@
import 'dart:async';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/painting/text_style.dart';
import 'package:sky/theme/typography.dart' as typography;
import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/animation_intentions.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/default_text_style.dart';
import 'package:sky/widgets/material.dart';
import 'package:sky/widgets/theme.dart';
import 'package:vector_math/vector_math.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
......@@ -41,39 +40,6 @@ class SnackBarAction extends Component {
}
}
// TODO(mpcomplete): generalize this to a SlideIn class.
class SnackBarSlideInIntention extends AnimationIntention {
SnackBarSlideInIntention(Duration duration) {
_position = new AnimatedValue<Point>(const Point(0.0, 50.0), end: Point.origin);
performance = new AnimationPerformance(duration: duration, variable: _position);
}
SnackBarStatusChangedCallback onStatusChanged;
AnimatedValue<Point> _position;
AnimationPerformance performance;
void initFields(AnimatedContainer container) {
performance.addListener(() { _updateProgress(container); });
performance.progress = 0.0;
if (container.tag)
performance.play();
}
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
if (original.tag != updated.tag) {
original.tag = updated.tag;
performance.play(original.tag ? AnimationDirection.forward : AnimationDirection.reverse);
}
}
void _updateProgress(AnimatedContainer container) {
container.setState(() {
container.transform = new Matrix4.identity()
..translate(_position.value.x, _position.value.y);
});
}
}
class SnackBar extends StatefulComponent {
SnackBar({
......@@ -91,10 +57,12 @@ class SnackBar extends StatefulComponent {
bool showing;
SnackBarStatusChangedCallback onStatusChanged;
SnackBarSlideInIntention _intention;
SlideInIntention _intention;
void initState() {
_intention = new SnackBarSlideInIntention(_kSlideInDuration);
_intention = new SlideInIntention(duration: _kSlideInDuration,
start: const Point(0.0, 50.0),
end: Point.origin);
_intention.performance.addStatusListener(_onStatusChanged);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册