mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add a bottom app bar to the floating action button motion demo. (#16196)
This commit is contained in:
parent
b8629bab8d
commit
68c77e37e2
@ -0,0 +1,500 @@
|
|||||||
|
// Copyright 2018 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/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BottomAppBarDemo extends StatefulWidget {
|
||||||
|
static const String routeName = '/material/bottom_app_bar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
State createState() => new _BottomAppBarDemoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
|
||||||
|
// The key given to the Scaffold so that _showSnackbar can find it.
|
||||||
|
static final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
// The index of the currently-selected _FabLocationConfiguration.
|
||||||
|
int fabLocationIndex = 1;
|
||||||
|
|
||||||
|
static const List<_FabLocationConfiguration> _fabLocationConfigurations = const <_FabLocationConfiguration>[
|
||||||
|
const _FabLocationConfiguration('End, undocked above the bottom app bar', _BabMode.END_FAB, FloatingActionButtonLocation.endFloat),
|
||||||
|
const _FabLocationConfiguration('End, docked to the bottom app bar', _BabMode.END_FAB, FloatingActionButtonLocation.endDocked),
|
||||||
|
const _FabLocationConfiguration('Center, docked to the bottom app bar', _BabMode.CENTER_FAB, FloatingActionButtonLocation.centerDocked),
|
||||||
|
const _FabLocationConfiguration('Center, undocked above the bottom app bar', _BabMode.CENTER_FAB, FloatingActionButtonLocation.centerFloat),
|
||||||
|
// This configuration uses a custom FloatingActionButtonLocation.
|
||||||
|
const _FabLocationConfiguration('Start, docked to the top app bar', _BabMode.CENTER_FAB, const _StartTopFloatingActionButtonLocation()),
|
||||||
|
];
|
||||||
|
|
||||||
|
// The index of the currently-selected _FabShapeConfiguration.
|
||||||
|
int fabShapeIndex = 1;
|
||||||
|
|
||||||
|
static const List<_FabShapeConfiguration> _fabShapeConfigurations = const <_FabShapeConfiguration>[
|
||||||
|
const _FabShapeConfiguration('None', null),
|
||||||
|
const _FabShapeConfiguration('Circular',
|
||||||
|
const FloatingActionButton(
|
||||||
|
onPressed: _showSnackbar,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const _FabShapeConfiguration('Diamond',
|
||||||
|
const _DiamondFab(
|
||||||
|
onPressed: _showSnackbar,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// The currently-selected Color for the Bottom App Bar.
|
||||||
|
Color babColor;
|
||||||
|
|
||||||
|
static const List<Color> babColors = const <Color> [
|
||||||
|
null,
|
||||||
|
Colors.orange,
|
||||||
|
Colors.green,
|
||||||
|
Colors.lightBlue,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Whether or not to show a notch in the Bottom App Bar around the
|
||||||
|
// Floating Action Button when it is docked.
|
||||||
|
bool notchEnabled = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new Scaffold(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
appBar: new AppBar(
|
||||||
|
title: const Text('Bottom App Bar with FAB location'),
|
||||||
|
// Add 48dp of space onto the bottom of the appbar.
|
||||||
|
// This gives space for the top-start location to attach to without
|
||||||
|
// blocking the 'back' button.
|
||||||
|
bottom: const PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(48.0),
|
||||||
|
child: const SizedBox(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: new SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: buildControls(context),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: new _DemoBottomAppBar(_fabLocationConfigurations[fabLocationIndex].babMode, babColor, notchEnabled),
|
||||||
|
floatingActionButton: _fabShapeConfigurations[fabShapeIndex].fab,
|
||||||
|
floatingActionButtonLocation: _fabLocationConfigurations[fabLocationIndex].fabLocation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildControls(BuildContext context) {
|
||||||
|
return new Column(
|
||||||
|
children: <Widget> [
|
||||||
|
new Text(
|
||||||
|
'Floating action button',
|
||||||
|
style: Theme.of(context).textTheme.title,
|
||||||
|
),
|
||||||
|
new Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
const SizedBox(width: 96.0,
|
||||||
|
child: const Text('Shape: '),
|
||||||
|
),
|
||||||
|
new Expanded(child: buildFabShapePicker()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
new Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
const SizedBox(
|
||||||
|
width: 96.0,
|
||||||
|
child: const Text('Location: '),
|
||||||
|
),
|
||||||
|
new Expanded(child: buildFabLocationPicker()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
new Text(
|
||||||
|
'Bottom app bar options',
|
||||||
|
style: Theme.of(context).textTheme.title,
|
||||||
|
),
|
||||||
|
buildBabColorPicker(),
|
||||||
|
new CheckboxListTile(
|
||||||
|
title: const Text('Enable notch'),
|
||||||
|
value: notchEnabled,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
notchEnabled = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildFabShapePicker() {
|
||||||
|
return new Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: new RaisedButton(
|
||||||
|
child: const Text('Change'),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
fabShapeIndex = (fabShapeIndex + 1) % _fabShapeConfigurations.length;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildFabLocationPicker() {
|
||||||
|
return new Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: new RaisedButton(
|
||||||
|
child: const Text('Move'),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
fabLocationIndex = (fabLocationIndex + 1) % _fabLocationConfigurations.length;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildBabColorPicker() {
|
||||||
|
final List<Widget> colors = <Widget> [
|
||||||
|
const Text('Color:'),
|
||||||
|
];
|
||||||
|
for (Color color in babColors) {
|
||||||
|
colors.add(
|
||||||
|
new Radio<Color>(
|
||||||
|
value: color,
|
||||||
|
groupValue: babColor,
|
||||||
|
onChanged: (Color color) {
|
||||||
|
setState(() {
|
||||||
|
babColor = color;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
colors.add(
|
||||||
|
new Container(
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
border: new Border.all(width:2.0, color: Colors.black),
|
||||||
|
),
|
||||||
|
child: const SizedBox(width: 20.0, height: 20.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
colors.add(const Padding(padding: const EdgeInsets.only(left: 12.0)));
|
||||||
|
}
|
||||||
|
return new Row(
|
||||||
|
children: colors,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _showSnackbar() {
|
||||||
|
_scaffoldKey.currentState.showSnackBar(
|
||||||
|
const SnackBar(content: const Text(_explanatoryText)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const String _explanatoryText =
|
||||||
|
"When the Scaffold's floating action button location changes, "
|
||||||
|
'the floating action button animates to its new position.'
|
||||||
|
'The BottomAppBar adapts its shape appropriately.';
|
||||||
|
|
||||||
|
// Whether the Bottom App Bar's menu should keep icons away from the center or from the end of the screen.
|
||||||
|
//
|
||||||
|
// When the Floating Action Button is positioned at the end of the screen,
|
||||||
|
// it would cover icons at the end of the screen, so the END_FAB mode tells
|
||||||
|
// the MyBottomAppBar to place icons away from the end.
|
||||||
|
//
|
||||||
|
// Similar logic applies to the CENTER_FAB mode.
|
||||||
|
enum _BabMode {
|
||||||
|
END_FAB,
|
||||||
|
CENTER_FAB,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pairs the Bottom App Bar's menu mode with a Floating Action Button Location.
|
||||||
|
class _FabLocationConfiguration {
|
||||||
|
const _FabLocationConfiguration(this.name, this.babMode, this.fabLocation);
|
||||||
|
|
||||||
|
// The name of this configuration.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
// The _BabMode to place the menu in the bab with.
|
||||||
|
final _BabMode babMode;
|
||||||
|
|
||||||
|
// The location for the Floating Action Button.
|
||||||
|
final FloatingActionButtonLocation fabLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map of names to the different shapes of Floating Action Button in this demo.
|
||||||
|
class _FabShapeConfiguration {
|
||||||
|
const _FabShapeConfiguration(this.name, this.fab);
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
final Widget fab;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bottom app bar with a menu inside it.
|
||||||
|
class _DemoBottomAppBar extends StatelessWidget {
|
||||||
|
const _DemoBottomAppBar(this.babMode, this.color, this.enableNotch);
|
||||||
|
|
||||||
|
final _BabMode babMode;
|
||||||
|
final Color color;
|
||||||
|
final bool enableNotch;
|
||||||
|
|
||||||
|
final Curve fadeOutCurve = const Interval(0.0, 0.3333);
|
||||||
|
final Curve fadeInCurve = const Interval(0.3333, 1.0);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final bool showsFirst = babMode == _BabMode.END_FAB;
|
||||||
|
return new BottomAppBar(
|
||||||
|
color: color,
|
||||||
|
hasNotch: enableNotch,
|
||||||
|
child: new Row(
|
||||||
|
children: <Widget> [
|
||||||
|
new IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet<Null>(context: context, builder: (BuildContext context) => const _DemoDrawer()); },
|
||||||
|
),
|
||||||
|
new Expanded(
|
||||||
|
child: new AnimatedCrossFade(
|
||||||
|
duration: const Duration(milliseconds: 225),
|
||||||
|
firstChild: buildBabContents(context, _BabMode.END_FAB),
|
||||||
|
firstCurve: showsFirst ? fadeOutCurve : fadeInCurve,
|
||||||
|
secondChild: buildBabContents(context, _BabMode.CENTER_FAB),
|
||||||
|
secondCurve: showsFirst ? fadeInCurve : fadeOutCurve,
|
||||||
|
crossFadeState: showsFirst ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildBabContents(BuildContext context, _BabMode babMode) {
|
||||||
|
final List<Widget> rowContents = <Widget> [];
|
||||||
|
if (babMode == _BabMode.CENTER_FAB) {
|
||||||
|
rowContents.add(
|
||||||
|
new Expanded(
|
||||||
|
child: new ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 0.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
rowContents.addAll(<Widget> [
|
||||||
|
new IconButton(
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
new IconButton(
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
onPressed: () {},
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
return new Row(
|
||||||
|
children: rowContents,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A drawer that pops up from the bottom of the screen.
|
||||||
|
class _DemoDrawer extends StatelessWidget {
|
||||||
|
const _DemoDrawer();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new Drawer(
|
||||||
|
child: new Column(
|
||||||
|
children: const <Widget>[
|
||||||
|
const ListTile(
|
||||||
|
leading: const Icon(Icons.search),
|
||||||
|
title: const Text('Search'),
|
||||||
|
),
|
||||||
|
const ListTile(
|
||||||
|
leading: const Icon(Icons.threed_rotation),
|
||||||
|
title: const Text('3D'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A diamond-shaped floating action button.
|
||||||
|
class _DiamondFab extends StatefulWidget {
|
||||||
|
const _DiamondFab({
|
||||||
|
this.child,
|
||||||
|
this.notchMargin: 6.0,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final double notchMargin;
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State createState() => new _DiamondFabState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DiamondFabState extends State<_DiamondFab> {
|
||||||
|
|
||||||
|
VoidCallback _clearComputeNotch;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new Material(
|
||||||
|
shape: const _DiamondBorder(),
|
||||||
|
color: Colors.orange,
|
||||||
|
child: new InkWell(
|
||||||
|
onTap: widget.onPressed,
|
||||||
|
child: new Container(
|
||||||
|
width: 56.0,
|
||||||
|
height: 56.0,
|
||||||
|
child: IconTheme.merge(
|
||||||
|
data: new IconThemeData(color: Theme.of(context).accentIconTheme.color),
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
elevation: 6.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_clearComputeNotch = Scaffold.setFloatingActionButtonNotchFor(context, _computeNotch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void deactivate() {
|
||||||
|
if (_clearComputeNotch != null)
|
||||||
|
_clearComputeNotch();
|
||||||
|
super.deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Path _computeNotch(Rect host, Rect guest, Offset start, Offset end) {
|
||||||
|
final Rect marginedGuest = guest.inflate(widget.notchMargin);
|
||||||
|
if (!host.overlaps(marginedGuest))
|
||||||
|
return new Path()..lineTo(end.dx, end.dy);
|
||||||
|
|
||||||
|
final Rect intersection = marginedGuest.intersect(host);
|
||||||
|
// We are computing a "V" shaped notch, as in this diagram:
|
||||||
|
// -----\**** /-----
|
||||||
|
// \ /
|
||||||
|
// \ /
|
||||||
|
// \ /
|
||||||
|
//
|
||||||
|
// "-" marks the top edge of the bottom app bar.
|
||||||
|
// "\" and "/" marks the notch outline
|
||||||
|
//
|
||||||
|
// notchToCenter is the horizontal distance between the guest's center and
|
||||||
|
// the host's top edge where the notch starts (marked with "*").
|
||||||
|
// We compute notchToCenter by similar triangles:
|
||||||
|
final double notchToCenter =
|
||||||
|
intersection.height * (marginedGuest.height / 2.0)
|
||||||
|
/ (marginedGuest.width / 2.0);
|
||||||
|
|
||||||
|
return new Path()
|
||||||
|
..lineTo(marginedGuest.center.dx - notchToCenter, host.top)
|
||||||
|
..lineTo(marginedGuest.left + marginedGuest.width / 2.0, marginedGuest.bottom)
|
||||||
|
..lineTo(marginedGuest.center.dx + notchToCenter, host.top)
|
||||||
|
..lineTo(end.dx, end.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DiamondBorder extends ShapeBorder {
|
||||||
|
const _DiamondBorder();
|
||||||
|
|
||||||
|
@override
|
||||||
|
EdgeInsetsGeometry get dimensions {
|
||||||
|
return const EdgeInsets.only();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
|
||||||
|
return getOuterPath(rect, textDirection: textDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
|
||||||
|
return new Path()
|
||||||
|
..moveTo(rect.left + rect.width / 2.0, rect.top)
|
||||||
|
..lineTo(rect.right, rect.top + rect.height / 2.0)
|
||||||
|
..lineTo(rect.left + rect.width / 2.0, rect.bottom)
|
||||||
|
..lineTo(rect.left, rect.top + rect.height / 2.0)
|
||||||
|
..close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {}
|
||||||
|
|
||||||
|
// This border doesn't support scaling.
|
||||||
|
@override
|
||||||
|
ShapeBorder scale(double t) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Places the Floating Action Button at the top of the content area of the
|
||||||
|
// app, on the border between the body and the app bar.
|
||||||
|
class _StartTopFloatingActionButtonLocation extends FloatingActionButtonLocation {
|
||||||
|
const _StartTopFloatingActionButtonLocation();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
||||||
|
// First, we'll place the X coordinate for the Floating Action Button
|
||||||
|
// at the start of the screen, based on the text direction.
|
||||||
|
double fabX;
|
||||||
|
assert(scaffoldGeometry.textDirection != null);
|
||||||
|
switch (scaffoldGeometry.textDirection) {
|
||||||
|
case TextDirection.rtl:
|
||||||
|
// In RTL layouts, the start of the screen is on the right side,
|
||||||
|
// and the end of the screen is on the left.
|
||||||
|
//
|
||||||
|
// We need to align the right edge of the floating action button with
|
||||||
|
// the right edge of the screen, then move it inwards by the designated padding.
|
||||||
|
//
|
||||||
|
// The Scaffold's origin is at its top-left, so we need to offset fabX
|
||||||
|
// by the Scaffold's width to get the right edge of the screen.
|
||||||
|
//
|
||||||
|
// The Floating Action Button's origin is at its top-left, so we also need
|
||||||
|
// to subtract the Floating Action Button's width to align the right edge
|
||||||
|
// of the Floating Action Button instead of the left edge.
|
||||||
|
final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.right;
|
||||||
|
fabX = scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width - startPadding;
|
||||||
|
break;
|
||||||
|
case TextDirection.ltr:
|
||||||
|
// In LTR layouts, the start of the screen is on the left side,
|
||||||
|
// and the end of the screen is on the right.
|
||||||
|
//
|
||||||
|
// Placing the fabX at 0.0 will align the left edge of the
|
||||||
|
// Floating Action Button with the left edge of the screen, so all
|
||||||
|
// we need to do is offset fabX by the designated padding.
|
||||||
|
final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.left;
|
||||||
|
fabX = startPadding;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Finally, we'll place the Y coordinate for the Floating Action Button
|
||||||
|
// at the top of the content body.
|
||||||
|
//
|
||||||
|
// We want to place the middle of the Floating Action Button on the
|
||||||
|
// border between the Scaffold's app bar and its body. To do this,
|
||||||
|
// we place fabY at the scaffold geometry's contentTop, then subtract
|
||||||
|
// half of the Floating Action Button's height to place the center
|
||||||
|
// over the contentTop.
|
||||||
|
//
|
||||||
|
// We don't have to worry about which way is the top like we did
|
||||||
|
// for left and right, so we place fabY in this one-liner.
|
||||||
|
final double fabY = scaffoldGeometry.contentTop - (scaffoldGeometry.floatingActionButtonSize.height / 2.0);
|
||||||
|
return new Offset(fabX, fabY);
|
||||||
|
}
|
||||||
|
}
|
@ -1,147 +0,0 @@
|
|||||||
// Copyright 2018 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';
|
|
||||||
|
|
||||||
const String _explanatoryText =
|
|
||||||
"When the Scaffold's floating action button location changes, "
|
|
||||||
'the floating action button animates to its new position';
|
|
||||||
|
|
||||||
class FabMotionDemo extends StatefulWidget {
|
|
||||||
static const String routeName = '/material/fab-motion';
|
|
||||||
|
|
||||||
@override
|
|
||||||
_FabMotionDemoState createState() {
|
|
||||||
return new _FabMotionDemoState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FabMotionDemoState extends State<FabMotionDemo> {
|
|
||||||
static const List<FloatingActionButtonLocation> _floatingActionButtonLocations = const <FloatingActionButtonLocation>[
|
|
||||||
FloatingActionButtonLocation.endFloat,
|
|
||||||
FloatingActionButtonLocation.centerFloat,
|
|
||||||
const _StartTopFloatingActionButtonLocation(),
|
|
||||||
];
|
|
||||||
|
|
||||||
bool _showFab = true;
|
|
||||||
FloatingActionButtonLocation _floatingActionButtonLocation = FloatingActionButtonLocation.endFloat;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final Widget floatingActionButton = _showFab
|
|
||||||
? new Builder(builder: (BuildContext context) {
|
|
||||||
// We use a widget builder here so that this inner context can find the Scaffold.
|
|
||||||
// This makes it possible to show the snackbar.
|
|
||||||
return new FloatingActionButton(
|
|
||||||
backgroundColor: Colors.yellow.shade900,
|
|
||||||
onPressed: () => _showSnackbar(context),
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
return new Scaffold(
|
|
||||||
appBar: new AppBar(
|
|
||||||
title: const Text('FAB Location'),
|
|
||||||
// Add 48dp of space onto the bottom of the appbar.
|
|
||||||
// This gives space for the top-start location to attach to without
|
|
||||||
// blocking the 'back' button.
|
|
||||||
bottom: const PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(48.0),
|
|
||||||
child: const SizedBox(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
floatingActionButtonLocation: _floatingActionButtonLocation,
|
|
||||||
floatingActionButton: floatingActionButton,
|
|
||||||
body: new Center(
|
|
||||||
child: new Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
new RaisedButton(
|
|
||||||
onPressed: _moveFab,
|
|
||||||
child: const Text('MOVE FAB'),
|
|
||||||
),
|
|
||||||
new Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
const Text('Toggle FAB'),
|
|
||||||
new Switch(value: _showFab, onChanged: _toggleFab),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _moveFab() {
|
|
||||||
setState(() {
|
|
||||||
_floatingActionButtonLocation = _floatingActionButtonLocations[(_floatingActionButtonLocations.indexOf(_floatingActionButtonLocation) + 1) % _floatingActionButtonLocations.length];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _toggleFab(bool showFab) {
|
|
||||||
setState(() {
|
|
||||||
_showFab = showFab;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showSnackbar(BuildContext context) {
|
|
||||||
Scaffold.of(context).showSnackBar(const SnackBar(content: const Text(_explanatoryText)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Places the Floating Action Button at the top of the content area of the
|
|
||||||
// app, on the border between the body and the app bar.
|
|
||||||
class _StartTopFloatingActionButtonLocation extends FloatingActionButtonLocation {
|
|
||||||
const _StartTopFloatingActionButtonLocation();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
|
||||||
// First, we'll place the X coordinate for the Floating Action Button
|
|
||||||
// at the start of the screen, based on the text direction.
|
|
||||||
double fabX;
|
|
||||||
assert(scaffoldGeometry.textDirection != null);
|
|
||||||
switch (scaffoldGeometry.textDirection) {
|
|
||||||
case TextDirection.rtl:
|
|
||||||
// In RTL layouts, the start of the screen is on the right side,
|
|
||||||
// and the end of the screen is on the left.
|
|
||||||
//
|
|
||||||
// We need to align the right edge of the floating action button with
|
|
||||||
// the right edge of the screen, then move it inwards by the designated padding.
|
|
||||||
//
|
|
||||||
// The Scaffold's origin is at its top-left, so we need to offset fabX
|
|
||||||
// by the Scaffold's width to get the right edge of the screen.
|
|
||||||
//
|
|
||||||
// The Floating Action Button's origin is at its top-left, so we also need
|
|
||||||
// to subtract the Floating Action Button's width to align the right edge
|
|
||||||
// of the Floating Action Button instead of the left edge.
|
|
||||||
final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.right;
|
|
||||||
fabX = scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width - startPadding;
|
|
||||||
break;
|
|
||||||
case TextDirection.ltr:
|
|
||||||
// In LTR layouts, the start of the screen is on the left side,
|
|
||||||
// and the end of the screen is on the right.
|
|
||||||
//
|
|
||||||
// Placing the fabX at 0.0 will align the left edge of the
|
|
||||||
// Floating Action Button with the left edge of the screen, so all
|
|
||||||
// we need to do is offset fabX by the designated padding.
|
|
||||||
final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.left;
|
|
||||||
fabX = startPadding;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Finally, we'll place the Y coordinate for the Floating Action Button
|
|
||||||
// at the top of the content body.
|
|
||||||
//
|
|
||||||
// We want to place the middle of the Floating Action Button on the
|
|
||||||
// border between the Scaffold's app bar and its body. To do this,
|
|
||||||
// we place fabY at the scaffold geometry's contentTop, then subtract
|
|
||||||
// half of the Floating Action Button's height to place the center
|
|
||||||
// over the contentTop.
|
|
||||||
//
|
|
||||||
// We don't have to worry about which way is the top like we did
|
|
||||||
// for left and right, so we place fabY in this one-liner.
|
|
||||||
final double fabY = scaffoldGeometry.contentTop - (scaffoldGeometry.floatingActionButtonSize.height / 2.0);
|
|
||||||
return new Offset(fabX, fabY);
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
export 'backdrop_demo.dart';
|
export 'backdrop_demo.dart';
|
||||||
|
export 'bottom_app_bar_demo.dart';
|
||||||
export 'bottom_navigation_demo.dart';
|
export 'bottom_navigation_demo.dart';
|
||||||
export 'buttons_demo.dart';
|
export 'buttons_demo.dart';
|
||||||
export 'cards_demo.dart';
|
export 'cards_demo.dart';
|
||||||
@ -12,7 +13,6 @@ export 'date_and_time_picker_demo.dart';
|
|||||||
export 'dialog_demo.dart';
|
export 'dialog_demo.dart';
|
||||||
export 'drawer_demo.dart';
|
export 'drawer_demo.dart';
|
||||||
export 'expansion_panels_demo.dart';
|
export 'expansion_panels_demo.dart';
|
||||||
export 'fab_motion_demo.dart';
|
|
||||||
export 'grid_list_demo.dart';
|
export 'grid_list_demo.dart';
|
||||||
export 'icons_demo.dart';
|
export 'icons_demo.dart';
|
||||||
export 'leave_behind_demo.dart';
|
export 'leave_behind_demo.dart';
|
||||||
|
@ -88,6 +88,13 @@ List<GalleryItem> _buildGalleryItems() {
|
|||||||
routeName: BackdropDemo.routeName,
|
routeName: BackdropDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new BackdropDemo(),
|
buildRoute: (BuildContext context) => new BackdropDemo(),
|
||||||
),
|
),
|
||||||
|
new GalleryItem(
|
||||||
|
title: 'Bottom App Bar',
|
||||||
|
subtitle: 'With repositionable floating action button',
|
||||||
|
category: 'Material Components',
|
||||||
|
routeName: BottomAppBarDemo.routeName,
|
||||||
|
buildRoute: (BuildContext context) => new BottomAppBarDemo(),
|
||||||
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Bottom navigation',
|
title: 'Bottom navigation',
|
||||||
subtitle: 'Bottom navigation with cross-fading views',
|
subtitle: 'Bottom navigation with cross-fading views',
|
||||||
@ -165,13 +172,6 @@ List<GalleryItem> _buildGalleryItems() {
|
|||||||
routeName: TabsFabDemo.routeName,
|
routeName: TabsFabDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new TabsFabDemo(),
|
buildRoute: (BuildContext context) => new TabsFabDemo(),
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
|
||||||
title: 'Floating action button motion',
|
|
||||||
subtitle: 'Action buttons with customized positions',
|
|
||||||
category: 'Material Components',
|
|
||||||
routeName: FabMotionDemo.routeName,
|
|
||||||
buildRoute: (BuildContext context) => new FabMotionDemo(),
|
|
||||||
),
|
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Grid',
|
title: 'Grid',
|
||||||
subtitle: 'Row and column layout',
|
subtitle: 'Row and column layout',
|
||||||
|
@ -67,7 +67,7 @@ abstract class FloatingActionButtonLocation {
|
|||||||
///
|
///
|
||||||
/// This is unlikely to be a useful location for apps that lack a bottom
|
/// This is unlikely to be a useful location for apps that lack a bottom
|
||||||
/// navigation bar.
|
/// navigation bar.
|
||||||
static FloatingActionButtonLocation endDocked = const _EndDockedFloatingActionButtonLocation();
|
static const FloatingActionButtonLocation endDocked = const _EndDockedFloatingActionButtonLocation();
|
||||||
|
|
||||||
/// Center-aligned [FloatingActionButton], floating over the
|
/// Center-aligned [FloatingActionButton], floating over the
|
||||||
/// [Scaffold.bottomNavigationBar] so that the center of the floating
|
/// [Scaffold.bottomNavigationBar] so that the center of the floating
|
||||||
@ -79,7 +79,7 @@ abstract class FloatingActionButtonLocation {
|
|||||||
///
|
///
|
||||||
/// This is unlikely to be a useful location for apps that lack a bottom
|
/// This is unlikely to be a useful location for apps that lack a bottom
|
||||||
/// navigation bar.
|
/// navigation bar.
|
||||||
static FloatingActionButtonLocation centerDocked = const _CenterDockedFloatingActionButtonLocation();
|
static const FloatingActionButtonLocation centerDocked = const _CenterDockedFloatingActionButtonLocation();
|
||||||
|
|
||||||
/// Places the [FloatingActionButton] based on the [Scaffold]'s layout.
|
/// Places the [FloatingActionButton] based on the [Scaffold]'s layout.
|
||||||
///
|
///
|
||||||
|
Loading…
Reference in New Issue
Block a user