diff --git a/examples/material_gallery/flutter.yaml b/examples/material_gallery/flutter.yaml index b0ee9969783..853033b08fd 100644 --- a/examples/material_gallery/flutter.yaml +++ b/examples/material_gallery/flutter.yaml @@ -14,3 +14,4 @@ material-design-icons: - name: action/face - name: action/language - name: content/add + - name: content/create diff --git a/examples/widgets/fab.dart b/examples/widgets/fab.dart new file mode 100644 index 00000000000..4c3189f1536 --- /dev/null +++ b/examples/widgets/fab.dart @@ -0,0 +1,94 @@ +// 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:flutter/material.dart'; + +class _Page { + _Page({this.label, this.color, this.icon}); + final String label; + final Map color; + final String icon; + TabLabel get tabLabel => new TabLabel(text: label); + bool get fabDefined => color != null && icon != null; + Color get fabColor => color[400]; + Icon get fabIcon => new Icon(icon: icon); + Key get fabKey => new ValueKey(fabColor); +} + +List<_Page> _pages = <_Page>[ + new _Page(label: "Blue", color: Colors.indigo, icon: 'content/add'), + new _Page(label: "Too", color: Colors.indigo, icon: 'content/add'), + new _Page(label: "Eco", color: Colors.green, icon: 'content/create'), + new _Page(label: "No"), + new _Page(label: "Teal", color: Colors.teal, icon: 'content/add'), + new _Page(label: "Red", color: Colors.red, icon: 'content/create') +]; + +class FabApp extends StatefulComponent { + FabApp(); + + FabAppState createState() => new FabAppState(); +} + +class FabAppState extends State { + _Page selectedPage = _pages[0]; + void _handleTabSelection(_Page page) { + setState(() { + selectedPage = page; + }); + } + + Widget buildTabView(_Page page) { + return new Builder( + builder: (BuildContext context) { + final TextStyle textStyle = new TextStyle( + color: Theme.of(context).primaryColor, + fontSize: 32.0, + textAlign: TextAlign.center + ); + + return new Container( + key: new ValueKey(page.label), + padding: const EdgeDims.TRBL(48.0, 48.0, 96.0, 48.0), + child: new Card( + child: new Center( + child: new Text(page.label, style: textStyle) + ) + ) + ); + } + ); + } + + Widget build(BuildContext context) { + return new TabBarSelection<_Page>( + values: _pages, + onChanged: _handleTabSelection, + child: new Scaffold( + toolBar: new ToolBar( + elevation: 0, + center: new Text('FAB Transition Demo'), + tabBar: new TabBar( + labels: new Map.fromIterable(_pages, value: (_Page page) => page.tabLabel) + ) + ), + body: new TabBarView(children: _pages.map(buildTabView).toList()), + floatingActionButton: !selectedPage.fabDefined ? null : new FloatingActionButton( + key: selectedPage.fabKey, + backgroundColor: selectedPage.fabColor, + child: selectedPage.fabIcon + ) + ) + ); + } +} + +void main() { + runApp(new MaterialApp( + title: 'FabApp', + routes: { + '/': (RouteArguments args) => new FabApp() + } + )); +} diff --git a/examples/widgets/flutter.yaml b/examples/widgets/flutter.yaml index 74e49a1abde..bdaa15bb9c3 100644 --- a/examples/widgets/flutter.yaml +++ b/examples/widgets/flutter.yaml @@ -10,6 +10,8 @@ material-design-icons: - name: action/home - name: action/language - name: action/list + - name: content/add + - name: content/create - name: device/dvr - name: editor/format_align_center - name: editor/format_align_left diff --git a/packages/flutter/lib/src/material/floating_action_button.dart b/packages/flutter/lib/src/material/floating_action_button.dart index d47ee9b405c..3e2e057bec8 100644 --- a/packages/flutter/lib/src/material/floating_action_button.dart +++ b/packages/flutter/lib/src/material/floating_action_button.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/animation.dart'; import 'package:flutter/widgets.dart'; import 'icon_theme.dart'; @@ -14,6 +15,8 @@ import 'theme.dart'; // http://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-keylines-spacing const double _kSize = 56.0; const double _kSizeMini = 40.0; +const Duration _kChildSegue = const Duration(milliseconds: 400); +const Interval _kChildSegueInterval = const Interval(0.65, 1.0); class FloatingActionButton extends StatefulComponent { const FloatingActionButton({ @@ -37,6 +40,22 @@ class FloatingActionButton extends StatefulComponent { } class _FloatingActionButtonState extends State { + final Performance _childSegue = new Performance(duration: _kChildSegue); + + void initState() { + super.initState(); + _childSegue.play(); + } + + void didUpdateConfig(FloatingActionButton oldConfig) { + super.didUpdateConfig(oldConfig); + if (Widget.canUpdate(oldConfig.child, config.child) && config.backgroundColor == oldConfig.backgroundColor) + return; + _childSegue + ..progress = 0.0 + ..play(); + } + bool _highlight = false; void _handleHighlightChanged(bool value) { @@ -67,7 +86,11 @@ class _FloatingActionButtonState extends State { child: new Center( child: new IconTheme( data: new IconThemeData(color: iconThemeColor), - child: config.child + child: new RotationTransition( + performance: _childSegue, + turns: new AnimatedValue(-0.125, end: 0.0, curve: _kChildSegueInterval), + child: config.child + ) ) ) ) diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 33b684a13d3..abd78119453 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -19,6 +19,7 @@ import 'drawer.dart'; import 'icon_button.dart'; const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent +const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 400); enum _Child { body, @@ -96,6 +97,66 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false; } +class _FloatingActionButtonTransition extends StatefulComponent { + _FloatingActionButtonTransition({ + Key key, + this.child + }) : super(key: key) { + assert(child != null); + } + + final Widget child; + + _FloatingActionButtonTransitionState createState() => new _FloatingActionButtonTransitionState(); +} + +class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTransition> { + final Performance performance = new Performance(duration: _kFloatingActionButtonSegue); + Widget oldChild; + + void initState() { + super.initState(); + performance.play().then((_) { + oldChild = null; + }); + } + + void dispose() { + performance.stop(); + super.dispose(); + } + + void didUpdateConfig(_FloatingActionButtonTransition oldConfig) { + if (Widget.canUpdate(oldConfig.child, config.child)) + return; + oldChild = oldConfig.child; + performance + ..progress = 0.0 + ..play().then((_) { + oldChild = null; + }); + } + + Widget build(BuildContext context) { + final List children = new List(); + if (oldChild != null) { + children.add(new ScaleTransition( + scale: new AnimatedValue(1.0, end: 0.0, curve: new Interval(0.0, 0.5, curve: Curves.easeIn)), + performance: performance, + child: oldChild + )); + } + + children.add(new ScaleTransition( + scale: new AnimatedValue(0.0, end: 1.0, curve: new Interval(0.5, 1.0, curve: Curves.easeIn)), + performance: performance, + child: config.child + )); + + return new Stack(children: children); + } +} + class Scaffold extends StatefulComponent { Scaffold({ Key key, @@ -323,7 +384,13 @@ class ScaffoldState extends State { if (_snackBars.isNotEmpty) _addIfNonNull(children, _snackBars.first._widget, _Child.snackBar); - _addIfNonNull(children, config.floatingActionButton, _Child.floatingActionButton); + if (config.floatingActionButton != null) { + Widget fab = new _FloatingActionButtonTransition( + key: new ValueKey(config.floatingActionButton.key), + child: config.floatingActionButton + ); + children.add(new LayoutId(child: fab, id: _Child.floatingActionButton)); + } if (config.drawer != null) { children.add(new LayoutId( diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index b446cc9133f..3e80bd2c6f5 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -230,6 +230,11 @@ abstract class Widget { } void debugFillDescription(List description) { } + + static bool canUpdate(Widget oldWidget, Widget newWidget) { + return oldWidget.runtimeType == newWidget.runtimeType && + oldWidget.key == newWidget.key; + } } // TODO(ianh): move the next four classes to below InheritedWidget @@ -512,11 +517,6 @@ abstract class InheritedWidget extends _ProxyComponent { // ELEMENTS -bool _canUpdate(Widget oldWidget, Widget newWidget) { - return oldWidget.runtimeType == newWidget.runtimeType && - oldWidget.key == newWidget.key; -} - enum _ElementLifecycle { initial, active, @@ -691,7 +691,7 @@ abstract class Element implements BuildContext { updateSlotForChild(child, newSlot); return child; } - if (_canUpdate(child.widget, newWidget)) { + if (Widget.canUpdate(child.widget, newWidget)) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); @@ -741,7 +741,7 @@ abstract class Element implements BuildContext { assert(newWidget != widget); assert(depth != null); assert(_active); - assert(_canUpdate(widget, newWidget)); + assert(Widget.canUpdate(widget, newWidget)); _widget = newWidget; } @@ -798,7 +798,7 @@ abstract class Element implements BuildContext { Element element = key._currentElement; if (element == null) return null; - if (!_canUpdate(element.widget, newWidget)) + if (!Widget.canUpdate(element.widget, newWidget)) return null; if (element._parent != null && !element._parent.detachChild(element)) return null; @@ -1493,7 +1493,7 @@ abstract class RenderObjectElement extends Buildab Element oldChild = oldChildren[childrenTop]; Widget newWidget = newWidgets[childrenTop]; assert(oldChild._debugLifecycleState == _ElementLifecycle.active); - if (!_canUpdate(oldChild.widget, newWidget)) + if (!Widget.canUpdate(oldChild.widget, newWidget)) break; childrenTop += 1; } @@ -1508,7 +1508,7 @@ abstract class RenderObjectElement extends Buildab Element oldChild = oldChildren[oldChildrenBottom]; Widget newWidget = newWidgets[newChildrenBottom]; assert(oldChild._debugLifecycleState == _ElementLifecycle.active); - if (!_canUpdate(oldChild.widget, newWidget)) + if (!Widget.canUpdate(oldChild.widget, newWidget)) break; Element newChild = updateChild(oldChild, newWidget, nextSibling); assert(newChild._debugLifecycleState == _ElementLifecycle.active); @@ -1543,7 +1543,7 @@ abstract class RenderObjectElement extends Buildab if (key != null) { oldChild = oldKeyedChildren[newWidget.key]; if (oldChild != null) { - if (_canUpdate(oldChild.widget, newWidget)) { + if (Widget.canUpdate(oldChild.widget, newWidget)) { // we found a match! // remove it from oldKeyedChildren so we don't unsync it later oldKeyedChildren.remove(key); @@ -1554,7 +1554,7 @@ abstract class RenderObjectElement extends Buildab } } } - assert(oldChild == null || _canUpdate(oldChild.widget, newWidget)); + assert(oldChild == null || Widget.canUpdate(oldChild.widget, newWidget)); Element newChild = updateChild(oldChild, newWidget, nextSibling); assert(newChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active); @@ -1571,7 +1571,7 @@ abstract class RenderObjectElement extends Buildab Element oldChild = oldChildren[childrenTop]; assert(oldChild._debugLifecycleState == _ElementLifecycle.active); Widget newWidget = newWidgets[childrenTop]; - assert(_canUpdate(oldChild.widget, newWidget)); + assert(Widget.canUpdate(oldChild.widget, newWidget)); Element newChild = updateChild(oldChild, newWidget, nextSibling); assert(newChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active); diff --git a/packages/flutter/lib/src/widgets/transitions.dart b/packages/flutter/lib/src/widgets/transitions.dart index 401339bebd8..3c0cafcefae 100644 --- a/packages/flutter/lib/src/widgets/transitions.dart +++ b/packages/flutter/lib/src/widgets/transitions.dart @@ -97,6 +97,32 @@ class SlideTransition extends TransitionWithChild { } } +class ScaleTransition extends TransitionWithChild { + ScaleTransition({ + Key key, + this.scale, + this.alignment: const FractionalOffset(0.5, 0.5), + PerformanceView performance, + Widget child + }) : super(key: key, + performance: performance, + child: child); + + final AnimatedValue scale; + final FractionalOffset alignment; + + Widget buildWithChild(BuildContext context, Widget child) { + performance.updateVariable(scale); + Matrix4 transform = new Matrix4.identity() + ..scale(scale.value, scale.value); + return new Transform( + transform: transform, + alignment: alignment, + child: child + ); + } +} + class RotationTransition extends TransitionWithChild { RotationTransition({ Key key,