Add new initState method to widget, rename animated_container to animation_builder and refactor it. This fixes the background color of the settings page.

R=abarth@chromium.org, abarth, mpcomplete

Review URL: https://codereview.chromium.org/1233703003 .
This commit is contained in:
Collin Jackson 2015-07-10 16:27:17 -07:00
parent 688a084e86
commit 09a82d4935
16 changed files with 227 additions and 52 deletions

View File

@ -50,7 +50,7 @@ dart_pkg("sky") {
"lib/theme/typography.dart",
"lib/theme/view_configuration.dart",
"lib/widgets/animated_component.dart",
"lib/widgets/animated_container.dart",
"lib/widgets/animation_builder.dart",
"lib/widgets/basic.dart",
"lib/widgets/block_viewport.dart",
"lib/widgets/button_base.dart",

View File

@ -24,17 +24,20 @@ import 'measurement.dart';
class HomeFragment extends StatefulComponent {
HomeFragment(this.navigator, this.userData) {
// if (debug)
// new Timer(new Duration(seconds: 1), dumpState);
_drawerController = new DrawerController(_handleDrawerStatusChanged);
}
HomeFragment(this.navigator, this.userData);
Navigator navigator;
List<Measurement> userData;
FitnessMode _fitnessMode = FitnessMode.measure;
void initState {
// if (debug)
// new Timer(new Duration(seconds: 1), dumpState);
_drawerController = new DrawerController(_handleDrawerStatusChanged);
super.initState();
}
void syncFields(HomeFragment source) {
navigator = source.navigator;
userData = source.userData;

View File

@ -16,7 +16,9 @@ import 'fitness_types.dart';
class FitnessApp extends App {
NavigationState _navigationState;
FitnessApp() {
FitnessApp();
void initState() {
_navigationState = new NavigationState([
new Route(
name: '/',
@ -27,6 +29,7 @@ class FitnessApp extends App {
builder: (navigator, route) => new SettingsFragment(navigator, backupSetting, settingsUpdater)
),
]);
super.initState();
}
void onBack() {

View File

@ -19,7 +19,9 @@ import 'stock_types.dart';
class StocksApp extends App {
NavigationState _navigationState;
StocksApp() {
StocksApp();
void initState() {
_navigationState = new NavigationState([
new Route(
name: '/',
@ -30,6 +32,7 @@ class StocksApp extends App {
builder: (navigator, route) => new StockSettings(navigator, optimismSetting, backupSetting, settingsUpdater)
),
]);
super.initState();
}
void onBack() {

View File

@ -5,7 +5,7 @@
import 'package:sky/editing/input.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/widgets/animated_component.dart';
import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/animation_builder.dart';
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/drawer.dart';
@ -58,7 +58,7 @@ class StockHome extends AnimatedComponent {
bool _isSearching = false;
String _searchQuery;
AnimatedContainer _snackbarTransform;
AnimationBuilder _snackbarTransform;
void _handleSearchBegin() {
navigator.pushState("/search", (_) {
@ -282,7 +282,7 @@ class StockHome extends AnimatedComponent {
void _handleStockPurchased() {
setState(() {
_snackbarTransform = new AnimatedContainer()
_snackbarTransform = new AnimationBuilder()
..position = new AnimatedType<Point>(const Point(0.0, 45.0), end: Point.origin);
var performance = _snackbarTransform.createPerformance(
[_snackbarTransform.position], duration: _kSnackbarSlideDuration);

View File

@ -78,7 +78,7 @@ class StockSettings extends StatefulComponent {
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: 'navigation/arrow_back_white',
icon: 'navigation/arrow_back',
onPressed: navigator.pop),
center: new Text('Settings')
);

View File

@ -10,7 +10,7 @@ import 'package:sky/animation/scroll_behavior.dart';
import 'package:sky/base/lerp.dart';
import 'package:sky/painting/text_style.dart';
import 'package:sky/theme/colors.dart';
import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/animation_builder.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/block_viewport.dart';
import 'package:sky/widgets/card.dart';
@ -78,7 +78,7 @@ class CardCollectionApp extends App {
List<int> visibleCardIndices;
CardCollectionApp() {
_activeCardTransform = new AnimatedContainer()
_activeCardTransform = new AnimationBuilder()
..position = new AnimatedType<Point>(Point.origin)
..opacity = new AnimatedType<double>(1.0, end: 0.0);
@ -91,7 +91,7 @@ class CardCollectionApp extends App {
}
int _activeCardIndex = -1;
AnimatedContainer _activeCardTransform;
AnimationBuilder _activeCardTransform;
AnimationPerformance _activeCardAnimation;
double _activeCardWidth;
double _activeCardDragX = 0.0;

View File

@ -20,17 +20,20 @@ class Input extends StatefulComponent {
this.placeholder,
this.onChanged,
this.focused})
: super(key: key) {
_editableValue = new EditableString(
text: _value,
onUpdated: _handleTextUpdated
);
}
: super(key: key);
String placeholder;
ValueChanged onChanged;
bool focused = false;
void initState() {
_editableValue = new EditableString(
text: _value,
onUpdated: _handleTextUpdated
);
super.initState();
}
void syncFields(Input source) {
placeholder = source.placeholder;
onChanged = source.onChanged;

View File

@ -335,7 +335,7 @@ Let's walk through the differences in `MyDialog` caused by its being stateful:
components created by the parent component are retained in the widget
hierchy. Old _stateful_ components, however, cannot simply be discarded
because they contain state that needs to be preserved. Instead, the old
stateful components are retained in the widget hiearchy and asked to
stateful components are retained in the widget hierarchy and asked to
`syncFields` with the new instance of the component created by the parent in
its `build` function.
@ -386,6 +386,22 @@ subscriptions to event streams from outside the widget hierachy. When overriding
either `didMount` or `didUnmount`, a component should call its superclass's
`didMount` or `didUnmount` function.
initState
---------
The framework calls the `initState` function on stateful components before
building them. The default implementation of initState does nothing. If your
component requires non-trivial work to initialize its state, you should
override initState and do it there rather than doing it in the stateful
component's constructor. If the component doesn't need to be built (for
example, if it was constructed just to have its fields synchronized with
an existing stateful component) you'll avoid unnecessary work. Also, some
operations that involve interacting with the widget hierarchy cannot be
done in a component's constructor.
When overriding `initState`, a component should call its superclass's
`initState` function.
Keys
----

View File

@ -11,23 +11,23 @@ import '../painting/box_painter.dart';
import '../theme/shadows.dart';
import 'basic.dart';
// This class builds a Container object from a collection of optionally-
// animated properties. Use syncFields to update the Container's properties,
// This class builds a Builder object from a collection of optionally-
// animated properties. Use syncFields to update the Builder's properties,
// which will optionally animate them using an AnimationPerformance.
class AnimatedContainer {
class AnimationBuilder {
AnimatedType<double> opacity;
AnimatedType<Point> position;
AnimatedType<double> shadow;
AnimatedColor backgroundColor;
// These don't animate, but are used to build the Container anyway.
// These don't animate, but are used to build the AnimationBuilder anyway.
double borderRadius;
Shape shape;
Map<AnimatedVariable, AnimationPerformance> _variableToPerformance =
new Map<AnimatedVariable, AnimationPerformance>();
AnimatedContainer();
AnimationBuilder();
AnimationPerformance createPerformance(List<AnimatedType> variables,
{Duration duration}) {
@ -65,7 +65,7 @@ class AnimatedContainer {
return current;
}
void syncFields(AnimatedContainer source) {
void syncFields(AnimationBuilder source) {
_syncField(position, source.position);
_syncField(shadow, source.shadow);
_syncField(backgroundColor, source.backgroundColor);

View File

@ -0,0 +1,122 @@
// 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.
import 'package:vector_math/vector_math.dart';
import '../animation/animation_performance.dart';
import '../animation/curves.dart';
import '../base/lerp.dart';
import '../painting/box_painter.dart';
import '../theme/shadows.dart';
import 'basic.dart';
// This class builds a Container object from a collection of optionally-
// animated properties. Use syncFields to update the Container's properties,
// which will optionally animate them using an AnimationPerformance.
class AnimationBuilder {
AnimatedType<double> opacity;
AnimatedType<Point> position;
AnimatedType<double> shadow;
AnimatedColor backgroundColor;
// These don't animate, but are used to build the AnimationBuilder anyway.
double borderRadius;
Shape shape;
Map<AnimatedVariable, AnimationPerformance> _variableToPerformance =
new Map<AnimatedVariable, AnimationPerformance>();
AnimationBuilder();
AnimationPerformance createPerformance(List<AnimatedType> variables,
{Duration duration}) {
AnimationPerformance performance = new AnimationPerformance()
..duration = duration
..variable = new AnimatedList(variables);
for (AnimatedVariable variable in variables)
_variableToPerformance[variable] = performance;
return performance;
}
Widget build(Widget child) {
Widget current = child;
if (shadow != null || backgroundColor != null ||
borderRadius != null || shape != null) {
current = new DecoratedBox(
decoration: new BoxDecoration(
borderRadius: borderRadius,
shape: shape,
boxShadow: shadow != null ? _computeShadow(shadow.value) : null,
backgroundColor: backgroundColor != null ? backgroundColor.value : null),
child: current);
}
if (position != null) {
Matrix4 transform = new Matrix4.identity();
transform.translate(position.value.x, position.value.y);
current = new Transform(transform: transform, child: current);
}
if (opacity != null) {
current = new Opacity(opacity: opacity.value, child: current);
}
return current;
}
void updateFields({ AnimatedType<double> shadow,
AnimatedColor backgroundColor,
double borderRadius,
Shape shape }) {
_updateField(this.shadow, shadow);
_updateField(this.backgroundColor, backgroundColor);
this.borderRadius = borderRadius;
this.shape = shape;
}
void _updateField(AnimatedType variable, AnimatedType sourceVariable) {
if (variable == null)
return; // TODO(mpcomplete): Should we handle transition from null?
AnimationPerformance performance = _variableToPerformance[variable];
if (performance == null) {
// If there's no performance, no need to animate.
if (sourceVariable != null)
variable.value = sourceVariable.value;
return;
}
if (variable.value != sourceVariable.value) {
variable
..begin = variable.value
..end = sourceVariable.value;
performance
..progress = 0.0
..play();
}
}
}
class AnimatedColor extends AnimatedType<Color> {
AnimatedColor(Color begin, {Color end, Curve curve: linear})
: super(begin, end: end, curve: curve);
void setFraction(double t) {
value = lerpColor(begin, end, t);
}
}
List<BoxShadow> _computeShadow(double level) {
if (level < 1.0) // shadows[1] is the first shadow
return null;
int level1 = level.floor();
int level2 = level.ceil();
double t = level - level1.toDouble();
List<BoxShadow> shadow = new List<BoxShadow>();
for (int i = 0; i < shadows[level1].length; ++i)
shadow.add(lerpBoxShadow(shadows[level1][i], shadows[level2][i], t));
return shadow;
}

View File

@ -8,7 +8,7 @@ import '../animation/animation_performance.dart';
import '../animation/curves.dart';
import '../theme/shadows.dart';
import 'animated_component.dart';
import 'animated_container.dart';
import 'animation_builder.dart';
import 'basic.dart';
import 'theme.dart';
@ -37,19 +37,19 @@ typedef void DrawerStatusChangeHandler (bool showing);
class DrawerController {
DrawerController(this.onStatusChange) {
container = new AnimatedContainer()
builder = new AnimationBuilder()
..position = new AnimatedType<Point>(
new Point(-_kWidth, 0.0), end: Point.origin, curve: _kAnimationCurve);
performance = container.createPerformance([container.position],
performance = builder.createPerformance([builder.position],
duration: _kBaseSettleDuration)
..addListener(_checkValue);
}
final DrawerStatusChangeHandler onStatusChange;
AnimationPerformance performance;
AnimatedContainer container;
AnimationBuilder builder;
double get xPosition => container.position.value.x;
double get xPosition => builder.position.value.x;
bool _oldClosedState = true;
void _checkValue() {
@ -132,7 +132,7 @@ class Drawer extends AnimatedComponent {
onGestureTap: controller.handleMaskTap
);
Widget content = controller.container.build(
Widget content = controller.builder.build(
new Container(
decoration: new BoxDecoration(
backgroundColor: Theme.of(this).canvasColor,

View File

@ -29,5 +29,5 @@ class FlatButton extends MaterialButton {
}
}
int get level => null;
int get level => 0;
}

View File

@ -5,7 +5,7 @@
import '../animation/animation_performance.dart';
import '../painting/box_painter.dart';
import 'animated_component.dart';
import 'animated_container.dart';
import 'animation_builder.dart';
import 'basic.dart';
import 'default_text_style.dart';
import 'theme.dart';
@ -27,29 +27,44 @@ class Material extends AnimatedComponent {
Material({
String key,
this.child,
MaterialType type: MaterialType.card,
int level: 0,
Color color: null
this.type: MaterialType.card,
this.level: 0,
this.color
}) : super(key: key) {
if (level == null) level = 0;
_container = new AnimatedContainer()
assert(level != null);
}
Widget child;
MaterialType type;
int level;
Color color;
AnimationBuilder _builder;
void initState() {
_builder = new AnimationBuilder()
..shadow = new AnimatedType<double>(level.toDouble())
..backgroundColor = _getBackgroundColor(type, color)
..borderRadius = edges[type]
..shape = type == MaterialType.circle ? Shape.circle : Shape.rectangle;
watchPerformance(_container.createPerformance(
[_container.shadow], duration: _kAnimateShadowDuration));
watchPerformance(_container.createPerformance(
[_container.backgroundColor], duration: _kAnimateColorDuration));
watchPerformance(_builder.createPerformance(
[_builder.shadow], duration: _kAnimateShadowDuration));
watchPerformance(_builder.createPerformance(
[_builder.backgroundColor], duration: _kAnimateColorDuration));
super.initState();
}
Widget child;
AnimatedContainer _container;
void syncFields(Material source) {
child = source.child;
_container.syncFields(source._container);
type = source.type;
level = source.level;
color = source.color;
_builder.updateFields(
shadow: new AnimatedType<double>(level.toDouble()),
backgroundColor: _getBackgroundColor(type, color),
borderRadius: edges[type],
shape: type == MaterialType.circle ? Shape.circle : Shape.rectangle
);
super.syncFields(source);
}
@ -70,7 +85,7 @@ class Material extends AnimatedComponent {
}
Widget build() {
return _container.build(
return _builder.build(
new DefaultTextStyle(style: Theme.of(this).text.body1, child: child)
);
}

View File

@ -135,7 +135,6 @@ abstract class Scrollable extends StatefulComponent {
void _startSimulation({ double velocity: 0.0 }) {
_stopSimulation();
print("velocity=$velocity");
Simulation simulation = scrollBehavior.release(scrollOffset, velocity);
if (simulation != null)
_animation.start(simulation);

View File

@ -521,6 +521,7 @@ abstract class StatefulComponent extends Component {
StatefulComponent({ String key }) : super(key: key);
bool _disqualifiedFromEverAppearingAgain = false;
bool _isStateInitialized = false;
void didMount() {
assert(!_disqualifiedFromEverAppearingAgain);
@ -534,6 +535,11 @@ abstract class StatefulComponent extends Component {
void _sync(Widget old, dynamic slot) {
assert(!_disqualifiedFromEverAppearingAgain);
// TODO(ianh): _sync should only be called once when old == null
if (old == null && !_isStateInitialized) {
initState();
_isStateInitialized = true;
}
super._sync(old, slot);
}
@ -567,6 +573,11 @@ abstract class StatefulComponent extends Component {
// extending StatefulComponent directly.
void syncFields(Component source);
// Stateful components can override initState if they want
// to do non-trivial work to initialize state. This is
// always called before build().
void initState() { }
void setState(Function fn()) {
assert(!_disqualifiedFromEverAppearingAgain);
fn();