Merge pull request #4953 from flutter/drawer-header-update
Updated DrawerHeader to new spec.
This commit is contained in:
commit
6f0635d6e8
@ -126,7 +126,7 @@ class CardCollectionState extends State<CardCollection> {
|
|||||||
child: new IconTheme(
|
child: new IconTheme(
|
||||||
data: const IconThemeData(color: Colors.black),
|
data: const IconThemeData(color: Colors.black),
|
||||||
child: new Block(children: <Widget>[
|
child: new Block(children: <Widget>[
|
||||||
new DrawerHeader(content: new Center(child: new Text('Options'))),
|
new DrawerHeader(child: new Center(child: new Text('Options'))),
|
||||||
buildDrawerCheckbox("Make card labels editable", _editable, _toggleEditable),
|
buildDrawerCheckbox("Make card labels editable", _editable, _toggleEditable),
|
||||||
buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter),
|
buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter),
|
||||||
buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
|
buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
|
||||||
|
@ -85,7 +85,7 @@ class PageableListAppState extends State<PageableListApp> {
|
|||||||
Widget _buildDrawer() {
|
Widget _buildDrawer() {
|
||||||
return new Drawer(
|
return new Drawer(
|
||||||
child: new Block(children: <Widget>[
|
child: new Block(children: <Widget>[
|
||||||
new DrawerHeader(content: new Center(child: new Text('Options'))),
|
new DrawerHeader(child: new Center(child: new Text('Options'))),
|
||||||
new DrawerItem(
|
new DrawerItem(
|
||||||
icon: new Icon(Icons.more_horiz),
|
icon: new Icon(Icons.more_horiz),
|
||||||
selected: scrollDirection == Axis.horizontal,
|
selected: scrollDirection == Axis.horizontal,
|
||||||
|
@ -121,7 +121,8 @@ class _PestoDemoState extends State<PestoDemo> {
|
|||||||
child: new Block(
|
child: new Block(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new DrawerHeader(
|
new DrawerHeader(
|
||||||
content: new Column(
|
child: new Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new Container(
|
new Container(
|
||||||
decoration: new BoxDecoration(
|
decoration: new BoxDecoration(
|
||||||
@ -131,7 +132,7 @@ class _PestoDemoState extends State<PestoDemo> {
|
|||||||
width: 72.0,
|
width: 72.0,
|
||||||
height: 72.0,
|
height: 72.0,
|
||||||
padding: const EdgeInsets.all(2.0),
|
padding: const EdgeInsets.all(2.0),
|
||||||
margin: const EdgeInsets.only(bottom: 8.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: new ClipOval(
|
child: new ClipOval(
|
||||||
child: new Image(
|
child: new Image(
|
||||||
image: new AssetImage(_kUserImage),
|
image: new AssetImage(_kUserImage),
|
||||||
|
@ -49,7 +49,7 @@ class GalleryDrawer extends StatelessWidget {
|
|||||||
child: new Block(
|
child: new Block(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new DrawerHeader(
|
new DrawerHeader(
|
||||||
content: new Center(child: new Text('Flutter gallery'))
|
child: new Center(child: new Text('Flutter gallery'))
|
||||||
),
|
),
|
||||||
new DrawerItem(
|
new DrawerItem(
|
||||||
icon: new Icon(Icons.brightness_5),
|
icon: new Icon(Icons.brightness_5),
|
||||||
|
@ -124,7 +124,7 @@ class StockHomeState extends State<StockHome> {
|
|||||||
Widget _buildDrawer(BuildContext context) {
|
Widget _buildDrawer(BuildContext context) {
|
||||||
return new Drawer(
|
return new Drawer(
|
||||||
child: new Block(children: <Widget>[
|
child: new Block(children: <Widget>[
|
||||||
new DrawerHeader(content: new Center(child: new Text('Stocks'))),
|
new DrawerHeader(child: new Center(child: new Text('Stocks'))),
|
||||||
new DrawerItem(
|
new DrawerItem(
|
||||||
icon: new Icon(Icons.assessment),
|
icon: new Icon(Icons.assessment),
|
||||||
selected: true,
|
selected: true,
|
||||||
|
@ -73,5 +73,6 @@ export 'src/material/toggleable.dart';
|
|||||||
export 'src/material/tooltip.dart';
|
export 'src/material/tooltip.dart';
|
||||||
export 'src/material/two_level_list.dart';
|
export 'src/material/two_level_list.dart';
|
||||||
export 'src/material/typography.dart';
|
export 'src/material/typography.dart';
|
||||||
|
export 'src/material/user_accounts_drawer_header.dart';
|
||||||
|
|
||||||
export 'widgets.dart';
|
export 'widgets.dart';
|
||||||
|
@ -7,11 +7,11 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'debug.dart';
|
import 'debug.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
||||||
const double _kDrawerHeaderHeight = 140.0;
|
const double _kDrawerHeaderHeight = 160.0 + 1.0; // bottom edge
|
||||||
|
|
||||||
/// The top-most region of a material design drawer. The header's [background]
|
/// The top-most region of a material design drawer. The header's [child]
|
||||||
/// widget extends behind the system status bar and its [content] widget is
|
/// widget is placed inside of a [Container] whose [decoration] can be passed as
|
||||||
/// stacked on top of the background and below the status bar.
|
/// an argument.
|
||||||
///
|
///
|
||||||
/// Part of the material design [Drawer].
|
/// Part of the material design [Drawer].
|
||||||
///
|
///
|
||||||
@ -22,19 +22,24 @@ const double _kDrawerHeaderHeight = 140.0;
|
|||||||
/// * [Drawer]
|
/// * [Drawer]
|
||||||
/// * [DrawerItem]
|
/// * [DrawerItem]
|
||||||
/// * <https://www.google.com/design/spec/patterns/navigation-drawer.html>
|
/// * <https://www.google.com/design/spec/patterns/navigation-drawer.html>
|
||||||
|
|
||||||
class DrawerHeader extends StatelessWidget {
|
class DrawerHeader extends StatelessWidget {
|
||||||
/// Creates a material design drawer header.
|
/// Creates a material design drawer header.
|
||||||
///
|
///
|
||||||
/// Requires one of its ancestors to be a [Material] widget.
|
/// Requires one of its ancestors to be a [Material] widget.
|
||||||
const DrawerHeader({ Key key, this.background, this.content }) : super(key: key);
|
const DrawerHeader({
|
||||||
|
Key key,
|
||||||
|
this.decoration,
|
||||||
|
this.child
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
/// A widget that extends behind the system status bar and is stacked
|
/// Decoration for the main drawer header [Container]; useful for applying
|
||||||
/// behind the [content] widget.
|
/// backgrounds.
|
||||||
final Widget background;
|
final BoxDecoration decoration;
|
||||||
|
|
||||||
/// A widget that's positioned below the status bar and stacked on top of the
|
/// A widget that extends behind the system status bar and is placed inside a
|
||||||
/// [background] widget. Typically a view of the user's id.
|
/// [Container].
|
||||||
final Widget content;
|
final Widget child;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -42,7 +47,7 @@ class DrawerHeader extends StatelessWidget {
|
|||||||
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||||
return new Container(
|
return new Container(
|
||||||
height: statusBarHeight + _kDrawerHeaderHeight,
|
height: statusBarHeight + _kDrawerHeaderHeight,
|
||||||
margin: const EdgeInsets.only(bottom: 7.0), // 8 less 1 for the bottom border.
|
margin: const EdgeInsets.only(bottom: 8.0),
|
||||||
decoration: new BoxDecoration(
|
decoration: new BoxDecoration(
|
||||||
border: const Border(
|
border: const Border(
|
||||||
bottom: const BorderSide(
|
bottom: const BorderSide(
|
||||||
@ -51,21 +56,19 @@ class DrawerHeader extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
child: new Stack(
|
child: new Container(
|
||||||
children: <Widget>[
|
padding: new EdgeInsets.only(
|
||||||
background ?? new Container(),
|
top: 16.0 + statusBarHeight,
|
||||||
new Positioned(
|
|
||||||
top: statusBarHeight + 16.0,
|
|
||||||
left: 16.0,
|
left: 16.0,
|
||||||
right: 16.0,
|
right: 16.0,
|
||||||
bottom: 8.0,
|
bottom: 8.0
|
||||||
|
),
|
||||||
|
decoration: decoration,
|
||||||
child: new DefaultTextStyle(
|
child: new DefaultTextStyle(
|
||||||
style: Theme.of(context).textTheme.body2,
|
style: Theme.of(context).textTheme.body2,
|
||||||
child: content
|
child: child
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,166 @@
|
|||||||
|
// 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';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'debug.dart';
|
||||||
|
|
||||||
|
/// A material design [Drawer] header that identifies the app's user.
|
||||||
|
///
|
||||||
|
/// The top-most region of a material design drawer with user accounts. The
|
||||||
|
/// header's [decoration] is used to provide a background.
|
||||||
|
/// [currentAccountPicture] is the main account picture on the left, while
|
||||||
|
/// [otherAccountsPictures] are the smaller account pictures on the right.
|
||||||
|
/// [accountName] and [accountEmail] provide access to the top and bottom rows
|
||||||
|
/// of the account details in the lower part of the header. When touched, this
|
||||||
|
/// area triggers [onDetailsPressed] and toggles the dropdown icon on the right.
|
||||||
|
///
|
||||||
|
/// Requires one of its ancestors to be a [Material] widget.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [Drawer]
|
||||||
|
/// * [DrawerItem]
|
||||||
|
/// * <https://www.google.com/design/spec/patterns/navigation-drawer.html>
|
||||||
|
|
||||||
|
class UserAccountsDrawerHeader extends StatefulWidget {
|
||||||
|
/// Creates a material design drawer header.
|
||||||
|
///
|
||||||
|
/// Requires one of its ancestors to be a [Material] widget.
|
||||||
|
UserAccountsDrawerHeader({
|
||||||
|
Key key,
|
||||||
|
this.decoration,
|
||||||
|
this.currentAccountPicture,
|
||||||
|
this.otherAccountsPictures,
|
||||||
|
this.accountName,
|
||||||
|
this.accountEmail,
|
||||||
|
this.onDetailsPressed
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// A callback that gets called when the account name/email/dropdown
|
||||||
|
/// section is pressed.
|
||||||
|
final VoidCallback onDetailsPressed;
|
||||||
|
|
||||||
|
/// Decoration for the main drawer header container useful for applying
|
||||||
|
/// backgrounds.
|
||||||
|
final BoxDecoration decoration;
|
||||||
|
|
||||||
|
/// A widget placed in the upper-left corner representing the current
|
||||||
|
/// account picture. Normally a [CircleAvatar].
|
||||||
|
final Widget currentAccountPicture;
|
||||||
|
|
||||||
|
/// A list of widgets that represent the user's accounts. Up to three of them
|
||||||
|
/// are arranged in a row in the header's upper-right corner. Normally a list
|
||||||
|
/// of [CircleAvatar] widgets.
|
||||||
|
final List<Widget> otherAccountsPictures;
|
||||||
|
|
||||||
|
/// A widget placed on the top row of the account details representing
|
||||||
|
/// account name.
|
||||||
|
final Widget accountName;
|
||||||
|
|
||||||
|
/// A widget placed on the bottom row of the account details representing
|
||||||
|
/// account email.
|
||||||
|
final Widget accountEmail;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_UserAccountsDrawerHeaderState createState() =>
|
||||||
|
new _UserAccountsDrawerHeaderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserAccountsDrawerHeaderState extends State<UserAccountsDrawerHeader> {
|
||||||
|
/// Saves whether the account dropdown is open or not.
|
||||||
|
bool isOpen = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
|
final List<Widget> otherAccountsPictures = config.otherAccountsPictures ??
|
||||||
|
<Widget>[];
|
||||||
|
return new DrawerHeader(
|
||||||
|
decoration: config.decoration,
|
||||||
|
child: new Column(
|
||||||
|
children: <Widget>[
|
||||||
|
new Flexible(
|
||||||
|
child: new Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
new Positioned(
|
||||||
|
top: 0.0,
|
||||||
|
right: 0.0,
|
||||||
|
child: new Row(
|
||||||
|
children: otherAccountsPictures.take(3).map(
|
||||||
|
(Widget picture) {
|
||||||
|
return new Container(
|
||||||
|
margin: const EdgeInsets.only(left: 16.0),
|
||||||
|
width: 40.0,
|
||||||
|
height: 40.0,
|
||||||
|
child: picture
|
||||||
|
);
|
||||||
|
}
|
||||||
|
).toList()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new Positioned(
|
||||||
|
top: 0.0,
|
||||||
|
child: new Container(
|
||||||
|
width: 72.0,
|
||||||
|
height: 72.0,
|
||||||
|
child: config.currentAccountPicture
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new Container(
|
||||||
|
height: 56.0,
|
||||||
|
child: new InkWell(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
isOpen = !isOpen;
|
||||||
|
});
|
||||||
|
if (config.onDetailsPressed != null)
|
||||||
|
config.onDetailsPressed();
|
||||||
|
},
|
||||||
|
child: new Container(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: new Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: <Widget>[
|
||||||
|
new DefaultTextStyle(
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white
|
||||||
|
),
|
||||||
|
child: config.accountName
|
||||||
|
),
|
||||||
|
new Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
new DefaultTextStyle(
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
child: config.accountEmail
|
||||||
|
),
|
||||||
|
new Flexible(
|
||||||
|
child: new Align(
|
||||||
|
alignment: FractionalOffset.centerRight,
|
||||||
|
child: new Icon(
|
||||||
|
isOpen ? Icons.arrow_drop_up :
|
||||||
|
Icons.arrow_drop_down,
|
||||||
|
color: Colors.white
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,13 +7,18 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Drawer control test', (WidgetTester tester) async {
|
testWidgets('Drawer control test', (WidgetTester tester) async {
|
||||||
|
final Key containerKey = new Key('container');
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
new Scaffold(
|
new Scaffold(
|
||||||
drawer: new Drawer(
|
drawer: new Drawer(
|
||||||
child: new Block(
|
child: new Block(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new DrawerHeader(
|
new DrawerHeader(
|
||||||
content: new Text('header')
|
child: new Container(
|
||||||
|
key: containerKey,
|
||||||
|
child: new Text('header')
|
||||||
|
)
|
||||||
),
|
),
|
||||||
new DrawerItem(
|
new DrawerItem(
|
||||||
icon: new Icon(Icons.archive),
|
icon: new Icon(Icons.archive),
|
||||||
@ -28,8 +33,21 @@ void main() {
|
|||||||
expect(find.text('Archive'), findsNothing);
|
expect(find.text('Archive'), findsNothing);
|
||||||
ScaffoldState state = tester.firstState(find.byType(Scaffold));
|
ScaffoldState state = tester.firstState(find.byType(Scaffold));
|
||||||
state.openDrawer();
|
state.openDrawer();
|
||||||
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
expect(find.text('Archive'), findsOneWidget);
|
expect(find.text('Archive'), findsOneWidget);
|
||||||
|
|
||||||
|
RenderBox box = tester.renderObject(find.byType(DrawerHeader));
|
||||||
|
expect(box.size.height, equals(160.0 + 8.0 + 1.0)); // height + bottom margin + bottom edge
|
||||||
|
|
||||||
|
final double drawerWidth = box.size.width;
|
||||||
|
final double drawerHeight = box.size.height;
|
||||||
|
|
||||||
|
box = tester.renderObject(find.byKey(containerKey));
|
||||||
|
expect(box.size.width, equals(drawerWidth - 2 * 16.0));
|
||||||
|
expect(box.size.height, equals(drawerHeight - 2 * 16.0 - 1.0)); // bottom edge
|
||||||
|
|
||||||
|
expect(find.text('header'), findsOneWidget);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2016 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';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('UserAccuntsDrawerHeader test', (WidgetTester tester) async {
|
||||||
|
final Key avatarA = new Key('A');
|
||||||
|
final Key avatarC = new Key('C');
|
||||||
|
final Key avatarD = new Key('D');
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Material(
|
||||||
|
child: new UserAccountsDrawerHeader(
|
||||||
|
currentAccountPicture: new CircleAvatar(
|
||||||
|
key: avatarA,
|
||||||
|
child: new Text('A')
|
||||||
|
),
|
||||||
|
otherAccountsPictures: <Widget>[
|
||||||
|
new CircleAvatar(
|
||||||
|
child: new Text('B')
|
||||||
|
),
|
||||||
|
new CircleAvatar(
|
||||||
|
key: avatarC,
|
||||||
|
child: new Text('C')
|
||||||
|
),
|
||||||
|
new CircleAvatar(
|
||||||
|
key: avatarD,
|
||||||
|
child: new Text('D')
|
||||||
|
),
|
||||||
|
new CircleAvatar(
|
||||||
|
child: new Text('E')
|
||||||
|
)
|
||||||
|
],
|
||||||
|
accountName: new Text("name"),
|
||||||
|
accountEmail: new Text("email")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('A'), findsOneWidget);
|
||||||
|
expect(find.text('B'), findsOneWidget);
|
||||||
|
expect(find.text('C'), findsOneWidget);
|
||||||
|
expect(find.text('D'), findsOneWidget);
|
||||||
|
expect(find.text('E'), findsNothing);
|
||||||
|
|
||||||
|
expect(find.text('name'), findsOneWidget);
|
||||||
|
expect(find.text('email'), findsOneWidget);
|
||||||
|
|
||||||
|
RenderBox box = tester.renderObject(find.byKey(avatarA));
|
||||||
|
expect(box.size.width, equals(72.0));
|
||||||
|
expect(box.size.height, equals(72.0));
|
||||||
|
|
||||||
|
box = tester.renderObject(find.byKey(avatarC));
|
||||||
|
expect(box.size.width, equals(40.0));
|
||||||
|
expect(box.size.height, equals(40.0));
|
||||||
|
|
||||||
|
Point topLeft = tester.getTopLeft(find.byType(UserAccountsDrawerHeader));
|
||||||
|
Point topRight = tester.getTopRight(find.byType(UserAccountsDrawerHeader));
|
||||||
|
|
||||||
|
Point avatarATopLeft = tester.getTopLeft(find.byKey(avatarA));
|
||||||
|
Point avatarDTopRight = tester.getTopRight(find.byKey(avatarD));
|
||||||
|
Point avatarCTopRight = tester.getTopRight(find.byKey(avatarC));
|
||||||
|
|
||||||
|
expect(avatarATopLeft.x - topLeft.x, equals(16.0));
|
||||||
|
expect(avatarATopLeft.y - topLeft.y, equals(16.0));
|
||||||
|
expect(topRight.x - avatarDTopRight.x, equals(16.0));
|
||||||
|
expect(avatarDTopRight.y - topRight.y, equals(16.0));
|
||||||
|
expect(avatarDTopRight.x - avatarCTopRight.x, equals(40.0 + 16.0)); // size + space between
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user