Updated DrawerHeader and added UserAccountDrawer.
Removed old Stack layout and added a simple-to-extend interface for the new drawer header. Also added a specialized UserAccountsDrawerHeader consistent with Material Design guidelines.
This commit is contained in:
parent
5f7b89990e
commit
65e77142e9
@ -126,7 +126,7 @@ class CardCollectionState extends State<CardCollection> {
|
||||
child: new IconTheme(
|
||||
data: const IconThemeData(color: Colors.black),
|
||||
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("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter),
|
||||
buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
|
||||
|
@ -85,7 +85,7 @@ class PageableListAppState extends State<PageableListApp> {
|
||||
Widget _buildDrawer() {
|
||||
return new Drawer(
|
||||
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(
|
||||
icon: new Icon(Icons.more_horiz),
|
||||
selected: scrollDirection == Axis.horizontal,
|
||||
|
@ -121,7 +121,8 @@ class _PestoDemoState extends State<PestoDemo> {
|
||||
child: new Block(
|
||||
children: <Widget>[
|
||||
new DrawerHeader(
|
||||
content: new Column(
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
decoration: new BoxDecoration(
|
||||
@ -131,7 +132,7 @@ class _PestoDemoState extends State<PestoDemo> {
|
||||
width: 72.0,
|
||||
height: 72.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 Image(
|
||||
image: new AssetImage(_kUserImage),
|
||||
|
@ -49,7 +49,7 @@ class GalleryDrawer extends StatelessWidget {
|
||||
child: new Block(
|
||||
children: <Widget>[
|
||||
new DrawerHeader(
|
||||
content: new Center(child: new Text('Flutter gallery'))
|
||||
child: new Center(child: new Text('Flutter gallery'))
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.brightness_5),
|
||||
|
@ -124,7 +124,7 @@ class StockHomeState extends State<StockHome> {
|
||||
Widget _buildDrawer(BuildContext context) {
|
||||
return new Drawer(
|
||||
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(
|
||||
icon: new Icon(Icons.assessment),
|
||||
selected: true,
|
||||
|
@ -72,5 +72,6 @@ export 'src/material/toggleable.dart';
|
||||
export 'src/material/tooltip.dart';
|
||||
export 'src/material/two_level_list.dart';
|
||||
export 'src/material/typography.dart';
|
||||
export 'src/material/user_accounts_drawer_header.dart';
|
||||
|
||||
export 'widgets.dart';
|
||||
|
@ -7,11 +7,11 @@ import 'package:flutter/widgets.dart';
|
||||
import 'debug.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]
|
||||
/// widget extends behind the system status bar and its [content] widget is
|
||||
/// stacked on top of the background and below the status bar.
|
||||
/// The top-most region of a material design drawer. The header's [child]
|
||||
/// widget is placed inside of a [Container] whose [decoration] can be passed as
|
||||
/// an argument.
|
||||
///
|
||||
/// Part of the material design [Drawer].
|
||||
///
|
||||
@ -22,19 +22,24 @@ const double _kDrawerHeaderHeight = 140.0;
|
||||
/// * [Drawer]
|
||||
/// * [DrawerItem]
|
||||
/// * <https://www.google.com/design/spec/patterns/navigation-drawer.html>
|
||||
|
||||
class DrawerHeader extends StatelessWidget {
|
||||
/// Creates a material design drawer header.
|
||||
///
|
||||
/// 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
|
||||
/// behind the [content] widget.
|
||||
final Widget background;
|
||||
/// Decoration for the main drawer header [Container]; useful for applying
|
||||
/// backgrounds.
|
||||
final BoxDecoration decoration;
|
||||
|
||||
/// A widget that's positioned below the status bar and stacked on top of the
|
||||
/// [background] widget. Typically a view of the user's id.
|
||||
final Widget content;
|
||||
/// A widget that extends behind the system status bar and is placed inside a
|
||||
/// [Container].
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -42,7 +47,7 @@ class DrawerHeader extends StatelessWidget {
|
||||
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||
return new Container(
|
||||
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(
|
||||
border: const Border(
|
||||
bottom: const BorderSide(
|
||||
@ -51,20 +56,18 @@ class DrawerHeader extends StatelessWidget {
|
||||
)
|
||||
)
|
||||
),
|
||||
child: new Stack(
|
||||
children: <Widget>[
|
||||
background ?? new Container(),
|
||||
new Positioned(
|
||||
top: statusBarHeight + 16.0,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
bottom: 8.0,
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.body2,
|
||||
child: content
|
||||
)
|
||||
)
|
||||
]
|
||||
child: new Container(
|
||||
padding: new EdgeInsets.only(
|
||||
top: 16.0 + statusBarHeight,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
bottom: 8.0
|
||||
),
|
||||
decoration: decoration,
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.body2,
|
||||
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() {
|
||||
testWidgets('Drawer control test', (WidgetTester tester) async {
|
||||
final Key containerKey = new Key('container');
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Scaffold(
|
||||
drawer: new Drawer(
|
||||
child: new Block(
|
||||
children: <Widget>[
|
||||
new DrawerHeader(
|
||||
content: new Text('header')
|
||||
child: new Container(
|
||||
key: containerKey,
|
||||
child: new Text('header')
|
||||
)
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.archive),
|
||||
@ -28,8 +33,21 @@ void main() {
|
||||
expect(find.text('Archive'), findsNothing);
|
||||
ScaffoldState state = tester.firstState(find.byType(Scaffold));
|
||||
state.openDrawer();
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
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