From 09a82d49352801ff949b3cfd195cdf2f5ce92b7c Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Fri, 10 Jul 2015 16:27:17 -0700 Subject: [PATCH] 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 . --- sdk/BUILD.gn | 2 +- sdk/example/fitness/lib/home.dart | 13 ++- sdk/example/fitness/lib/main.dart | 5 +- sdk/example/stocks/lib/main.dart | 5 +- sdk/example/stocks/lib/stock_home.dart | 6 +- sdk/example/stocks/lib/stock_settings.dart | 2 +- sdk/example/widgets/card_collection.dart | 6 +- sdk/lib/editing/input.dart | 15 ++- sdk/lib/widgets/README.md | 18 ++- sdk/lib/widgets/animated_container.dart | 12 +- sdk/lib/widgets/animation_builder.dart | 122 +++++++++++++++++++++ sdk/lib/widgets/drawer.dart | 12 +- sdk/lib/widgets/flat_button.dart | 2 +- sdk/lib/widgets/material.dart | 47 +++++--- sdk/lib/widgets/scrollable.dart | 1 - sdk/lib/widgets/widget.dart | 11 ++ 16 files changed, 227 insertions(+), 52 deletions(-) create mode 100644 sdk/lib/widgets/animation_builder.dart diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 7446a11b326..98f031299a3 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -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", diff --git a/sdk/example/fitness/lib/home.dart b/sdk/example/fitness/lib/home.dart index 4ed0bf7ac83..c22a80fb128 100644 --- a/sdk/example/fitness/lib/home.dart +++ b/sdk/example/fitness/lib/home.dart @@ -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 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; diff --git a/sdk/example/fitness/lib/main.dart b/sdk/example/fitness/lib/main.dart index 6d447a831e4..5368c91db64 100644 --- a/sdk/example/fitness/lib/main.dart +++ b/sdk/example/fitness/lib/main.dart @@ -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() { diff --git a/sdk/example/stocks/lib/main.dart b/sdk/example/stocks/lib/main.dart index 8552b2fd735..cecbb5b1a45 100644 --- a/sdk/example/stocks/lib/main.dart +++ b/sdk/example/stocks/lib/main.dart @@ -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() { diff --git a/sdk/example/stocks/lib/stock_home.dart b/sdk/example/stocks/lib/stock_home.dart index e57aeda63ed..7d5679cea8f 100644 --- a/sdk/example/stocks/lib/stock_home.dart +++ b/sdk/example/stocks/lib/stock_home.dart @@ -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(const Point(0.0, 45.0), end: Point.origin); var performance = _snackbarTransform.createPerformance( [_snackbarTransform.position], duration: _kSnackbarSlideDuration); diff --git a/sdk/example/stocks/lib/stock_settings.dart b/sdk/example/stocks/lib/stock_settings.dart index 98088ae7f04..f1072afdc1a 100644 --- a/sdk/example/stocks/lib/stock_settings.dart +++ b/sdk/example/stocks/lib/stock_settings.dart @@ -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') ); diff --git a/sdk/example/widgets/card_collection.dart b/sdk/example/widgets/card_collection.dart index ef6d82d8fc5..cbd064ed2d6 100644 --- a/sdk/example/widgets/card_collection.dart +++ b/sdk/example/widgets/card_collection.dart @@ -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 visibleCardIndices; CardCollectionApp() { - _activeCardTransform = new AnimatedContainer() + _activeCardTransform = new AnimationBuilder() ..position = new AnimatedType(Point.origin) ..opacity = new AnimatedType(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; diff --git a/sdk/lib/editing/input.dart b/sdk/lib/editing/input.dart index ddcd2e9406d..639b8bff60f 100644 --- a/sdk/lib/editing/input.dart +++ b/sdk/lib/editing/input.dart @@ -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; diff --git a/sdk/lib/widgets/README.md b/sdk/lib/widgets/README.md index e6077549058..478b6439322 100644 --- a/sdk/lib/widgets/README.md +++ b/sdk/lib/widgets/README.md @@ -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 ---- diff --git a/sdk/lib/widgets/animated_container.dart b/sdk/lib/widgets/animated_container.dart index 89a5a0cb194..4a42a530ff9 100644 --- a/sdk/lib/widgets/animated_container.dart +++ b/sdk/lib/widgets/animated_container.dart @@ -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 opacity; AnimatedType position; AnimatedType 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 _variableToPerformance = new Map(); - AnimatedContainer(); + AnimationBuilder(); AnimationPerformance createPerformance(List 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); diff --git a/sdk/lib/widgets/animation_builder.dart b/sdk/lib/widgets/animation_builder.dart new file mode 100644 index 00000000000..bbb61d6fd64 --- /dev/null +++ b/sdk/lib/widgets/animation_builder.dart @@ -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 opacity; + AnimatedType position; + AnimatedType shadow; + AnimatedColor backgroundColor; + + // These don't animate, but are used to build the AnimationBuilder anyway. + double borderRadius; + Shape shape; + + Map _variableToPerformance = + new Map(); + + AnimationBuilder(); + + AnimationPerformance createPerformance(List 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 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 { + AnimatedColor(Color begin, {Color end, Curve curve: linear}) + : super(begin, end: end, curve: curve); + + void setFraction(double t) { + value = lerpColor(begin, end, t); + } +} + +List _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 shadow = new List(); + for (int i = 0; i < shadows[level1].length; ++i) + shadow.add(lerpBoxShadow(shadows[level1][i], shadows[level2][i], t)); + return shadow; +} diff --git a/sdk/lib/widgets/drawer.dart b/sdk/lib/widgets/drawer.dart index e64974a6d02..c53bd79bfb9 100644 --- a/sdk/lib/widgets/drawer.dart +++ b/sdk/lib/widgets/drawer.dart @@ -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( 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, diff --git a/sdk/lib/widgets/flat_button.dart b/sdk/lib/widgets/flat_button.dart index c4b65cceed3..5efbcd49ef7 100644 --- a/sdk/lib/widgets/flat_button.dart +++ b/sdk/lib/widgets/flat_button.dart @@ -29,5 +29,5 @@ class FlatButton extends MaterialButton { } } - int get level => null; + int get level => 0; } diff --git a/sdk/lib/widgets/material.dart b/sdk/lib/widgets/material.dart index cb24a8dd0a8..d8f6c9952c7 100644 --- a/sdk/lib/widgets/material.dart +++ b/sdk/lib/widgets/material.dart @@ -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(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(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) ); } diff --git a/sdk/lib/widgets/scrollable.dart b/sdk/lib/widgets/scrollable.dart index b9eab727001..fc7061635dd 100644 --- a/sdk/lib/widgets/scrollable.dart +++ b/sdk/lib/widgets/scrollable.dart @@ -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); diff --git a/sdk/lib/widgets/widget.dart b/sdk/lib/widgets/widget.dart index b099a3a98df..4626da58e25 100644 --- a/sdk/lib/widgets/widget.dart +++ b/sdk/lib/widgets/widget.dart @@ -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();