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/face
|
||||||
- name: action/language
|
- name: action/language
|
||||||
- name: content/add
|
- 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/home
|
||||||
- name: action/language
|
- name: action/language
|
||||||
- name: action/list
|
- name: action/list
|
||||||
|
- name: content/add
|
||||||
|
- name: content/create
|
||||||
- name: device/dvr
|
- name: device/dvr
|
||||||
- name: editor/format_align_center
|
- name: editor/format_align_center
|
||||||
- name: editor/format_align_left
|
- name: editor/format_align_left
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/animation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'icon_theme.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
|
// http://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-keylines-spacing
|
||||||
const double _kSize = 56.0;
|
const double _kSize = 56.0;
|
||||||
const double _kSizeMini = 40.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 {
|
class FloatingActionButton extends StatefulComponent {
|
||||||
const FloatingActionButton({
|
const FloatingActionButton({
|
||||||
@ -37,6 +40,22 @@ class FloatingActionButton extends StatefulComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FloatingActionButtonState extends State<FloatingActionButton> {
|
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;
|
bool _highlight = false;
|
||||||
|
|
||||||
void _handleHighlightChanged(bool value) {
|
void _handleHighlightChanged(bool value) {
|
||||||
@ -67,7 +86,11 @@ class _FloatingActionButtonState extends State<FloatingActionButton> {
|
|||||||
child: new Center(
|
child: new Center(
|
||||||
child: new IconTheme(
|
child: new IconTheme(
|
||||||
data: new IconThemeData(color: iconThemeColor),
|
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';
|
import 'icon_button.dart';
|
||||||
|
|
||||||
const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent
|
const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent
|
||||||
|
const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 400);
|
||||||
|
|
||||||
enum _Child {
|
enum _Child {
|
||||||
body,
|
body,
|
||||||
@ -96,6 +97,66 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
|||||||
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false;
|
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 {
|
class Scaffold extends StatefulComponent {
|
||||||
Scaffold({
|
Scaffold({
|
||||||
Key key,
|
Key key,
|
||||||
@ -323,7 +384,13 @@ class ScaffoldState extends State<Scaffold> {
|
|||||||
if (_snackBars.isNotEmpty)
|
if (_snackBars.isNotEmpty)
|
||||||
_addIfNonNull(children, _snackBars.first._widget, _Child.snackBar);
|
_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) {
|
if (config.drawer != null) {
|
||||||
children.add(new LayoutId(
|
children.add(new LayoutId(
|
||||||
|
@ -230,6 +230,11 @@ abstract class Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void debugFillDescription(List<String> description) { }
|
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
|
// TODO(ianh): move the next four classes to below InheritedWidget
|
||||||
@ -512,11 +517,6 @@ abstract class InheritedWidget extends _ProxyComponent {
|
|||||||
|
|
||||||
// ELEMENTS
|
// ELEMENTS
|
||||||
|
|
||||||
bool _canUpdate(Widget oldWidget, Widget newWidget) {
|
|
||||||
return oldWidget.runtimeType == newWidget.runtimeType &&
|
|
||||||
oldWidget.key == newWidget.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum _ElementLifecycle {
|
enum _ElementLifecycle {
|
||||||
initial,
|
initial,
|
||||||
active,
|
active,
|
||||||
@ -691,7 +691,7 @@ abstract class Element<T extends Widget> implements BuildContext {
|
|||||||
updateSlotForChild(child, newSlot);
|
updateSlotForChild(child, newSlot);
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
if (_canUpdate(child.widget, newWidget)) {
|
if (Widget.canUpdate(child.widget, newWidget)) {
|
||||||
if (child.slot != newSlot)
|
if (child.slot != newSlot)
|
||||||
updateSlotForChild(child, newSlot);
|
updateSlotForChild(child, newSlot);
|
||||||
child.update(newWidget);
|
child.update(newWidget);
|
||||||
@ -741,7 +741,7 @@ abstract class Element<T extends Widget> implements BuildContext {
|
|||||||
assert(newWidget != widget);
|
assert(newWidget != widget);
|
||||||
assert(depth != null);
|
assert(depth != null);
|
||||||
assert(_active);
|
assert(_active);
|
||||||
assert(_canUpdate(widget, newWidget));
|
assert(Widget.canUpdate(widget, newWidget));
|
||||||
_widget = newWidget;
|
_widget = newWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -798,7 +798,7 @@ abstract class Element<T extends Widget> implements BuildContext {
|
|||||||
Element element = key._currentElement;
|
Element element = key._currentElement;
|
||||||
if (element == null)
|
if (element == null)
|
||||||
return null;
|
return null;
|
||||||
if (!_canUpdate(element.widget, newWidget))
|
if (!Widget.canUpdate(element.widget, newWidget))
|
||||||
return null;
|
return null;
|
||||||
if (element._parent != null && !element._parent.detachChild(element))
|
if (element._parent != null && !element._parent.detachChild(element))
|
||||||
return null;
|
return null;
|
||||||
@ -1493,7 +1493,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
|
|||||||
Element oldChild = oldChildren[childrenTop];
|
Element oldChild = oldChildren[childrenTop];
|
||||||
Widget newWidget = newWidgets[childrenTop];
|
Widget newWidget = newWidgets[childrenTop];
|
||||||
assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
|
assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
|
||||||
if (!_canUpdate(oldChild.widget, newWidget))
|
if (!Widget.canUpdate(oldChild.widget, newWidget))
|
||||||
break;
|
break;
|
||||||
childrenTop += 1;
|
childrenTop += 1;
|
||||||
}
|
}
|
||||||
@ -1508,7 +1508,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
|
|||||||
Element oldChild = oldChildren[oldChildrenBottom];
|
Element oldChild = oldChildren[oldChildrenBottom];
|
||||||
Widget newWidget = newWidgets[newChildrenBottom];
|
Widget newWidget = newWidgets[newChildrenBottom];
|
||||||
assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
|
assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
|
||||||
if (!_canUpdate(oldChild.widget, newWidget))
|
if (!Widget.canUpdate(oldChild.widget, newWidget))
|
||||||
break;
|
break;
|
||||||
Element newChild = updateChild(oldChild, newWidget, nextSibling);
|
Element newChild = updateChild(oldChild, newWidget, nextSibling);
|
||||||
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
|
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
|
||||||
@ -1543,7 +1543,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
|
|||||||
if (key != null) {
|
if (key != null) {
|
||||||
oldChild = oldKeyedChildren[newWidget.key];
|
oldChild = oldKeyedChildren[newWidget.key];
|
||||||
if (oldChild != null) {
|
if (oldChild != null) {
|
||||||
if (_canUpdate(oldChild.widget, newWidget)) {
|
if (Widget.canUpdate(oldChild.widget, newWidget)) {
|
||||||
// we found a match!
|
// we found a match!
|
||||||
// remove it from oldKeyedChildren so we don't unsync it later
|
// remove it from oldKeyedChildren so we don't unsync it later
|
||||||
oldKeyedChildren.remove(key);
|
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);
|
Element newChild = updateChild(oldChild, newWidget, nextSibling);
|
||||||
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
|
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
|
||||||
assert(oldChild == newChild || oldChild == null || oldChild._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];
|
Element oldChild = oldChildren[childrenTop];
|
||||||
assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
|
assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
|
||||||
Widget newWidget = newWidgets[childrenTop];
|
Widget newWidget = newWidgets[childrenTop];
|
||||||
assert(_canUpdate(oldChild.widget, newWidget));
|
assert(Widget.canUpdate(oldChild.widget, newWidget));
|
||||||
Element newChild = updateChild(oldChild, newWidget, nextSibling);
|
Element newChild = updateChild(oldChild, newWidget, nextSibling);
|
||||||
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
|
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
|
||||||
assert(oldChild == newChild || oldChild == null || oldChild._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 {
|
class RotationTransition extends TransitionWithChild {
|
||||||
RotationTransition({
|
RotationTransition({
|
||||||
Key key,
|
Key key,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user