Floating Action Button transitions
This commit is contained in:
parent
d04e2221f1
commit
cafea7f51f
@ -14,3 +14,4 @@ material-design-icons:
|
||||
- name: action/face
|
||||
- name: action/language
|
||||
- name: content/add
|
||||
- name: content/create
|
||||
|
94
examples/widgets/fab.dart
Normal file
94
examples/widgets/fab.dart
Normal file
@ -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<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,7 +86,11 @@ class _FloatingActionButtonState extends State<FloatingActionButton> {
|
||||
child: new Center(
|
||||
child: new IconTheme(
|
||||
data: new IconThemeData(color: iconThemeColor),
|
||||
child: config.child
|
||||
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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user