mirror of
https://github.com/flutter/samples.git
synced 2025-11-11 07:18:15 +00:00
Add flutter_web samples (#75)
This commit is contained in:
committed by
Andrew Brogdon
parent
42f2dce01b
commit
3fe927cb29
524
web/gallery/lib/demo/material/bottom_app_bar_demo.dart
Normal file
524
web/gallery/lib/demo/material/bottom_app_bar_demo.dart
Normal file
@@ -0,0 +1,524 @@
|
||||
// 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_web/material.dart';
|
||||
|
||||
import '../../gallery/demo.dart';
|
||||
|
||||
class BottomAppBarDemo extends StatefulWidget {
|
||||
static const String routeName = '/material/bottom_app_bar';
|
||||
|
||||
@override
|
||||
State createState() => _BottomAppBarDemoState();
|
||||
}
|
||||
|
||||
// Flutter generally frowns upon abbrevation however this class uses two
|
||||
// abbrevations extensively: "fab" for floating action button, and "bab"
|
||||
// for bottom application bar.
|
||||
|
||||
class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
|
||||
static final GlobalKey<ScaffoldState> _scaffoldKey =
|
||||
GlobalKey<ScaffoldState>();
|
||||
|
||||
// FAB shape
|
||||
|
||||
static const _ChoiceValue<Widget> kNoFab = _ChoiceValue<Widget>(
|
||||
title: 'None',
|
||||
label: 'do not show a floating action button',
|
||||
value: null,
|
||||
);
|
||||
|
||||
static const _ChoiceValue<Widget> kCircularFab = _ChoiceValue<Widget>(
|
||||
title: 'Circular',
|
||||
label: 'circular floating action button',
|
||||
value: FloatingActionButton(
|
||||
onPressed: _showSnackbar,
|
||||
child: Icon(Icons.add, semanticLabel: 'Action'),
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
);
|
||||
|
||||
static const _ChoiceValue<Widget> kDiamondFab = _ChoiceValue<Widget>(
|
||||
title: 'Diamond',
|
||||
label: 'diamond shape floating action button',
|
||||
value: _DiamondFab(
|
||||
onPressed: _showSnackbar,
|
||||
child: Icon(Icons.add, semanticLabel: 'Action'),
|
||||
),
|
||||
);
|
||||
|
||||
// Notch
|
||||
|
||||
static const _ChoiceValue<bool> kShowNotchTrue = _ChoiceValue<bool>(
|
||||
title: 'On',
|
||||
label: 'show bottom appbar notch',
|
||||
value: true,
|
||||
);
|
||||
|
||||
static const _ChoiceValue<bool> kShowNotchFalse = _ChoiceValue<bool>(
|
||||
title: 'Off',
|
||||
label: 'do not show bottom appbar notch',
|
||||
value: false,
|
||||
);
|
||||
|
||||
// FAB Position
|
||||
|
||||
static const _ChoiceValue<FloatingActionButtonLocation> kFabEndDocked =
|
||||
_ChoiceValue<FloatingActionButtonLocation>(
|
||||
title: 'Attached - End',
|
||||
label: 'floating action button is docked at the end of the bottom app bar',
|
||||
value: FloatingActionButtonLocation.endDocked,
|
||||
);
|
||||
|
||||
static const _ChoiceValue<FloatingActionButtonLocation> kFabCenterDocked =
|
||||
_ChoiceValue<FloatingActionButtonLocation>(
|
||||
title: 'Attached - Center',
|
||||
label:
|
||||
'floating action button is docked at the center of the bottom app bar',
|
||||
value: FloatingActionButtonLocation.centerDocked,
|
||||
);
|
||||
|
||||
static const _ChoiceValue<FloatingActionButtonLocation> kFabEndFloat =
|
||||
_ChoiceValue<FloatingActionButtonLocation>(
|
||||
title: 'Free - End',
|
||||
label: 'floating action button floats above the end of the bottom app bar',
|
||||
value: FloatingActionButtonLocation.endFloat,
|
||||
);
|
||||
|
||||
static const _ChoiceValue<FloatingActionButtonLocation> kFabCenterFloat =
|
||||
_ChoiceValue<FloatingActionButtonLocation>(
|
||||
title: 'Free - Center',
|
||||
label:
|
||||
'floating action button is floats above the center of the bottom app bar',
|
||||
value: FloatingActionButtonLocation.centerFloat,
|
||||
);
|
||||
|
||||
static void _showSnackbar() {
|
||||
const String text =
|
||||
"When the Scaffold's floating action button location changes, "
|
||||
'the floating action button animates to its new position.'
|
||||
'The BottomAppBar adapts its shape appropriately.';
|
||||
_scaffoldKey.currentState.showSnackBar(
|
||||
const SnackBar(content: Text(text)),
|
||||
);
|
||||
}
|
||||
|
||||
// App bar color
|
||||
|
||||
static const List<_NamedColor> kBabColors = <_NamedColor>[
|
||||
_NamedColor(null, 'Clear'),
|
||||
_NamedColor(Color(0xFFFFC100), 'Orange'),
|
||||
_NamedColor(Color(0xFF91FAFF), 'Light Blue'),
|
||||
_NamedColor(Color(0xFF00D1FF), 'Cyan'),
|
||||
_NamedColor(Color(0xFF00BCFF), 'Cerulean'),
|
||||
_NamedColor(Color(0xFF009BEE), 'Blue'),
|
||||
];
|
||||
|
||||
_ChoiceValue<Widget> _fabShape = kCircularFab;
|
||||
_ChoiceValue<bool> _showNotch = kShowNotchTrue;
|
||||
_ChoiceValue<FloatingActionButtonLocation> _fabLocation = kFabEndDocked;
|
||||
Color _babColor = kBabColors.first.color;
|
||||
|
||||
void _onShowNotchChanged(_ChoiceValue<bool> value) {
|
||||
setState(() {
|
||||
_showNotch = value;
|
||||
});
|
||||
}
|
||||
|
||||
void _onFabShapeChanged(_ChoiceValue<Widget> value) {
|
||||
setState(() {
|
||||
_fabShape = value;
|
||||
});
|
||||
}
|
||||
|
||||
void _onFabLocationChanged(_ChoiceValue<FloatingActionButtonLocation> value) {
|
||||
setState(() {
|
||||
_fabLocation = value;
|
||||
});
|
||||
}
|
||||
|
||||
void _onBabColorChanged(Color value) {
|
||||
setState(() {
|
||||
_babColor = value;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: AppBar(
|
||||
title: const Text('Bottom app bar'),
|
||||
elevation: 0.0,
|
||||
actions: <Widget>[
|
||||
MaterialDemoDocumentationButton(BottomAppBarDemo.routeName),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.sentiment_very_satisfied,
|
||||
semanticLabel: 'Update shape'),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_fabShape =
|
||||
_fabShape == kCircularFab ? kDiamondFab : kCircularFab;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.only(bottom: 88.0),
|
||||
children: <Widget>[
|
||||
const _Heading('FAB Shape'),
|
||||
_RadioItem<Widget>(kCircularFab, _fabShape, _onFabShapeChanged),
|
||||
_RadioItem<Widget>(kDiamondFab, _fabShape, _onFabShapeChanged),
|
||||
_RadioItem<Widget>(kNoFab, _fabShape, _onFabShapeChanged),
|
||||
const Divider(),
|
||||
const _Heading('Notch'),
|
||||
_RadioItem<bool>(kShowNotchTrue, _showNotch, _onShowNotchChanged),
|
||||
_RadioItem<bool>(kShowNotchFalse, _showNotch, _onShowNotchChanged),
|
||||
const Divider(),
|
||||
const _Heading('FAB Position'),
|
||||
_RadioItem<FloatingActionButtonLocation>(
|
||||
kFabEndDocked, _fabLocation, _onFabLocationChanged),
|
||||
_RadioItem<FloatingActionButtonLocation>(
|
||||
kFabCenterDocked, _fabLocation, _onFabLocationChanged),
|
||||
_RadioItem<FloatingActionButtonLocation>(
|
||||
kFabEndFloat, _fabLocation, _onFabLocationChanged),
|
||||
_RadioItem<FloatingActionButtonLocation>(
|
||||
kFabCenterFloat, _fabLocation, _onFabLocationChanged),
|
||||
const Divider(),
|
||||
const _Heading('App bar color'),
|
||||
_ColorsItem(kBabColors, _babColor, _onBabColorChanged),
|
||||
],
|
||||
),
|
||||
floatingActionButton: _fabShape.value,
|
||||
floatingActionButtonLocation: _fabLocation.value,
|
||||
bottomNavigationBar: _DemoBottomAppBar(
|
||||
color: _babColor,
|
||||
fabLocation: _fabLocation.value,
|
||||
shape: _selectNotch(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
NotchedShape _selectNotch() {
|
||||
if (!_showNotch.value) return null;
|
||||
if (_fabShape == kCircularFab) return const CircularNotchedRectangle();
|
||||
if (_fabShape == kDiamondFab) return const _DiamondNotchedRectangle();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class _ChoiceValue<T> {
|
||||
const _ChoiceValue({this.value, this.title, this.label});
|
||||
|
||||
final T value;
|
||||
final String title;
|
||||
final String label; // For the Semantics widget that contains title
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType("$title")';
|
||||
}
|
||||
|
||||
class _RadioItem<T> extends StatelessWidget {
|
||||
const _RadioItem(this.value, this.groupValue, this.onChanged);
|
||||
|
||||
final _ChoiceValue<T> value;
|
||||
final _ChoiceValue<T> groupValue;
|
||||
final ValueChanged<_ChoiceValue<T>> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return Container(
|
||||
height: 56.0,
|
||||
padding: const EdgeInsetsDirectional.only(start: 16.0),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: MergeSemantics(
|
||||
child: Row(children: <Widget>[
|
||||
Radio<_ChoiceValue<T>>(
|
||||
value: value,
|
||||
groupValue: groupValue,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
Expanded(
|
||||
child: Semantics(
|
||||
container: true,
|
||||
button: true,
|
||||
label: value.label,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
onChanged(value);
|
||||
},
|
||||
child: Text(
|
||||
value.title,
|
||||
style: theme.textTheme.subhead,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NamedColor {
|
||||
const _NamedColor(this.color, this.name);
|
||||
|
||||
final Color color;
|
||||
final String name;
|
||||
}
|
||||
|
||||
class _ColorsItem extends StatelessWidget {
|
||||
const _ColorsItem(this.colors, this.selectedColor, this.onChanged);
|
||||
|
||||
final List<_NamedColor> colors;
|
||||
final Color selectedColor;
|
||||
final ValueChanged<Color> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: colors.map<Widget>((_NamedColor namedColor) {
|
||||
return RawMaterialButton(
|
||||
onPressed: () {
|
||||
onChanged(namedColor.color);
|
||||
},
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
width: 32.0,
|
||||
height: 32.0,
|
||||
),
|
||||
fillColor: namedColor.color,
|
||||
shape: CircleBorder(
|
||||
side: BorderSide(
|
||||
color: namedColor.color == selectedColor
|
||||
? Colors.black
|
||||
: const Color(0xFFD5D7DA),
|
||||
width: 2.0,
|
||||
),
|
||||
),
|
||||
child: Semantics(
|
||||
value: namedColor.name,
|
||||
selected: namedColor.color == selectedColor,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Heading extends StatelessWidget {
|
||||
const _Heading(this.text);
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return Container(
|
||||
height: 48.0,
|
||||
padding: const EdgeInsetsDirectional.only(start: 56.0),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Text(
|
||||
text,
|
||||
style: theme.textTheme.body1.copyWith(
|
||||
color: theme.primaryColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DemoBottomAppBar extends StatelessWidget {
|
||||
const _DemoBottomAppBar({this.color, this.fabLocation, this.shape});
|
||||
|
||||
final Color color;
|
||||
final FloatingActionButtonLocation fabLocation;
|
||||
final NotchedShape shape;
|
||||
|
||||
static final List<FloatingActionButtonLocation> kCenterLocations =
|
||||
<FloatingActionButtonLocation>[
|
||||
FloatingActionButtonLocation.centerDocked,
|
||||
FloatingActionButtonLocation.centerFloat,
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> rowContents = <Widget>[
|
||||
IconButton(
|
||||
icon: const Icon(Icons.menu, semanticLabel: 'Show bottom sheet'),
|
||||
onPressed: () {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => const _DemoDrawer(),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
if (kCenterLocations.contains(fabLocation)) {
|
||||
rowContents.add(
|
||||
const Expanded(child: SizedBox()),
|
||||
);
|
||||
}
|
||||
|
||||
rowContents.addAll(<Widget>[
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.search,
|
||||
semanticLabel: 'show search action',
|
||||
),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('This is a dummy search action.')),
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Theme.of(context).platform == TargetPlatform.iOS
|
||||
? Icons.more_horiz
|
||||
: Icons.more_vert,
|
||||
semanticLabel: 'Show menu actions',
|
||||
),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('This is a dummy menu action.')),
|
||||
);
|
||||
},
|
||||
),
|
||||
]);
|
||||
|
||||
return BottomAppBar(
|
||||
color: color,
|
||||
child: Row(children: rowContents),
|
||||
shape: shape,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// A drawer that pops up from the bottom of the screen.
|
||||
class _DemoDrawer extends StatelessWidget {
|
||||
const _DemoDrawer();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Drawer(
|
||||
child: Column(
|
||||
children: const <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(Icons.search),
|
||||
title: Text('Search'),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.threed_rotation),
|
||||
title: Text('3D'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// A diamond-shaped floating action button.
|
||||
class _DiamondFab extends StatelessWidget {
|
||||
const _DiamondFab({
|
||||
this.child,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
shape: const _DiamondBorder(),
|
||||
color: Colors.orange,
|
||||
child: InkWell(
|
||||
onTap: onPressed,
|
||||
child: Container(
|
||||
width: 56.0,
|
||||
height: 56.0,
|
||||
child: IconTheme.merge(
|
||||
data: IconThemeData(color: Theme.of(context).accentIconTheme.color),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
elevation: 6.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DiamondNotchedRectangle implements NotchedShape {
|
||||
const _DiamondNotchedRectangle();
|
||||
|
||||
@override
|
||||
Path getOuterPath(Rect host, Rect guest) {
|
||||
if (!host.overlaps(guest)) return Path()..addRect(host);
|
||||
assert(guest.width > 0.0);
|
||||
|
||||
final Rect intersection = guest.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 * (guest.height / 2.0) / (guest.width / 2.0);
|
||||
|
||||
return Path()
|
||||
..moveTo(host.left, host.top)
|
||||
..lineTo(guest.center.dx - notchToCenter, host.top)
|
||||
..lineTo(guest.left + guest.width / 2.0, guest.bottom)
|
||||
..lineTo(guest.center.dx + notchToCenter, host.top)
|
||||
..lineTo(host.right, host.top)
|
||||
..lineTo(host.right, host.bottom)
|
||||
..lineTo(host.left, host.bottom)
|
||||
..close();
|
||||
}
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user