Commit cafea7f5 authored by Hans Muller's avatar Hans Muller

Floating Action Button transitions

parent d04e2221
......@@ -14,3 +14,4 @@ material-design-icons:
- name: action/face
- name: action/language
- name: content/add
- name: content/create
// 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<int, Color> 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<Color>(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<FabApp> {
_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<String>(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<String>(
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()
}
));
}
......@@ -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
......
......@@ -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<FloatingActionButton> {
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,11 +86,15 @@ class _FloatingActionButtonState extends State<FloatingActionButton> {
child: new Center(
child: new IconTheme(
data: new IconThemeData(color: iconThemeColor),
child: new RotationTransition(
performance: _childSegue,
turns: new AnimatedValue<double>(-0.125, end: 0.0, curve: _kChildSegueInterval),
child: config.child
)
)
)
)
)
);
}
}
......@@ -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<Widget> children = new List<Widget>();
if (oldChild != null) {
children.add(new ScaleTransition(
scale: new AnimatedValue<double>(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<double>(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<Scaffold> {
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<Key>(config.floatingActionButton.key),
child: config.floatingActionButton
);
children.add(new LayoutId(child: fab, id: _Child.floatingActionButton));
}
if (config.drawer != null) {
children.add(new LayoutId(
......
......@@ -230,6 +230,11 @@ abstract class Widget {
}
void debugFillDescription(List<String> 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<T extends Widget> 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<T extends Widget> 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<T extends Widget> 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<T extends RenderObjectWidget> 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<T extends RenderObjectWidget> 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<T extends RenderObjectWidget> 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<T extends RenderObjectWidget> 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<T extends RenderObjectWidget> 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);
......
......@@ -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<double> 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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment