mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
UserAccountsDrawerHeader gallery demo, etc (#7297)
This commit is contained in:
parent
c9c577aeeb
commit
d05c7f62f2
@ -12,6 +12,7 @@ export 'colors_demo.dart';
|
||||
export 'data_table_demo.dart';
|
||||
export 'date_and_time_picker_demo.dart';
|
||||
export 'dialog_demo.dart';
|
||||
export 'drawer_demo.dart';
|
||||
export 'expansion_panels_demo.dart';
|
||||
export 'grid_list_demo.dart';
|
||||
export 'icons_demo.dart';
|
||||
|
186
examples/flutter_gallery/lib/demo/drawer_demo.dart
Normal file
186
examples/flutter_gallery/lib/demo/drawer_demo.dart
Normal file
@ -0,0 +1,186 @@
|
||||
// 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';
|
||||
|
||||
const String _kAsset0 = 'packages/flutter_gallery_assets/shrine/vendors/zach.jpg';
|
||||
const String _kAsset1 = 'packages/flutter_gallery_assets/shrine/vendors/16c477b.jpg';
|
||||
const String _kAsset2 = 'packages/flutter_gallery_assets/shrine/vendors/sandra-adams.jpg';
|
||||
|
||||
class DrawerDemo extends StatefulWidget {
|
||||
static const String routeName = '/drawer';
|
||||
|
||||
@override
|
||||
_DrawerDemoState createState() => new _DrawerDemoState();
|
||||
}
|
||||
|
||||
class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
static const List<String> _drawerContents = const <String>[
|
||||
'A', 'B', 'C', 'D', 'E',
|
||||
];
|
||||
|
||||
AnimationController _controller;
|
||||
Animation<double> _drawerContentsOpacity;
|
||||
Animation<FractionalOffset> _drawerDetailsPosition;
|
||||
bool _showDrawerContents = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = new AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
_drawerContentsOpacity = new CurvedAnimation(
|
||||
parent: new ReverseAnimation(_controller),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
_drawerDetailsPosition = new Tween<FractionalOffset>(
|
||||
begin: const FractionalOffset(0.0, -1.0),
|
||||
end: const FractionalOffset(0.0, 0.0),
|
||||
).animate(new CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
IconData _backIcon() {
|
||||
switch (Theme.of(context).platform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
return Icons.arrow_back;
|
||||
case TargetPlatform.iOS:
|
||||
return Icons.arrow_back_ios;
|
||||
}
|
||||
assert(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
void _showNotImplementedMessage() {
|
||||
Navigator.of(context).pop(); // Dismiss the drawer.
|
||||
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
||||
content: new Text("The drawer's items don't do anything")
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: new AppBar(
|
||||
leading: new IconButton(
|
||||
icon: new Icon(_backIcon()),
|
||||
alignment: FractionalOffset.centerLeft,
|
||||
tooltip: 'Back',
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
title: new Text('Navigation drawer'),
|
||||
),
|
||||
drawer: new Drawer(
|
||||
child: new Block(
|
||||
children: <Widget>[
|
||||
new UserAccountsDrawerHeader(
|
||||
accountName: new Text('Zach Widget'),
|
||||
accountEmail: new Text('zach.widget@example.com'),
|
||||
currentAccountPicture: new CircleAvatar(backgroundImage: new AssetImage(_kAsset0)),
|
||||
otherAccountsPictures: <Widget>[
|
||||
new CircleAvatar(backgroundImage: new AssetImage(_kAsset1)),
|
||||
new CircleAvatar(backgroundImage: new AssetImage(_kAsset2)),
|
||||
],
|
||||
onDetailsPressed: () {
|
||||
_showDrawerContents = !_showDrawerContents;
|
||||
if (_showDrawerContents)
|
||||
_controller.reverse();
|
||||
else
|
||||
_controller.forward();
|
||||
},
|
||||
),
|
||||
new ClipRect(
|
||||
child: new Stack(
|
||||
children: <Widget>[
|
||||
// The initial contents of the drawer.
|
||||
new FadeTransition(
|
||||
opacity: _drawerContentsOpacity,
|
||||
child: new Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: _drawerContents.map((String id) {
|
||||
return new DrawerItem(
|
||||
icon: new CircleAvatar(child: new Text(id)),
|
||||
child: new Text('Drawer item $id'),
|
||||
onPressed: _showNotImplementedMessage,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
// The drawer's "details" view.
|
||||
new SlideTransition(
|
||||
position: _drawerDetailsPosition,
|
||||
child: new FadeTransition(
|
||||
opacity: new ReverseAnimation(_drawerContentsOpacity),
|
||||
child: new Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.add),
|
||||
child: new Text('Add account'),
|
||||
onPressed: _showNotImplementedMessage,
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.settings),
|
||||
child: new Text('Manage accounts'),
|
||||
onPressed: _showNotImplementedMessage,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: new Center(
|
||||
child: new InkWell(
|
||||
onTap: () {
|
||||
_scaffoldKey.currentState.openDrawer();
|
||||
},
|
||||
child: new Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
backgroundImage: new BackgroundImage(
|
||||
image: new AssetImage(_kAsset0),
|
||||
),
|
||||
),
|
||||
),
|
||||
new Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: new Text('Tap here to open the drawer',
|
||||
style: Theme.of(context).textTheme.subhead,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -102,6 +102,12 @@ final List<GalleryItem> kAllGalleryItems = <GalleryItem>[
|
||||
routeName: DialogDemo.routeName,
|
||||
buildRoute: (BuildContext context) => new DialogDemo()
|
||||
),
|
||||
new GalleryItem(
|
||||
title: 'Drawer',
|
||||
subtitle: 'Navigation drawer with a standard header',
|
||||
routeName: DrawerDemo.routeName,
|
||||
buildRoute: (BuildContext context) => new DrawerDemo()
|
||||
),
|
||||
new GalleryItem(
|
||||
title: 'Expand/collapse list control',
|
||||
subtitle: 'List with one level of sublists',
|
||||
|
@ -33,6 +33,7 @@ final List<String> demoTitles = <String>[
|
||||
'Chips',
|
||||
'Date and time pickers',
|
||||
'Dialog',
|
||||
'Drawer',
|
||||
'Expand/collapse list control',
|
||||
'Expansion panels',
|
||||
'Floating action button',
|
||||
|
@ -3,25 +3,128 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'debug.dart';
|
||||
|
||||
class _AccountPictures extends StatelessWidget {
|
||||
_AccountPictures({
|
||||
Key key,
|
||||
this.currentAccountPicture,
|
||||
this.otherAccountsPictures,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget currentAccountPicture;
|
||||
final List<Widget> otherAccountsPictures;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Stack(
|
||||
children: <Widget>[
|
||||
new Positioned(
|
||||
top: 0.0,
|
||||
right: 0.0,
|
||||
child: new Row(
|
||||
children: (otherAccountsPictures ?? <Widget>[]).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 SizedBox(
|
||||
width: 72.0,
|
||||
height: 72.0,
|
||||
child: currentAccountPicture
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountDetails extends StatelessWidget {
|
||||
_AccountDetails({
|
||||
Key key,
|
||||
this.accountName,
|
||||
this.accountEmail,
|
||||
this.onTap,
|
||||
this.isOpen,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget accountName;
|
||||
final Widget accountEmail;
|
||||
final VoidCallback onTap;
|
||||
final bool isOpen;
|
||||
|
||||
Widget addDropdownIcon(Widget line) {
|
||||
final Widget icon = new Expanded(
|
||||
child: new Align(
|
||||
alignment: FractionalOffset.centerRight,
|
||||
child: new Icon(
|
||||
isOpen ? Icons.arrow_drop_up : Icons.arrow_drop_down,
|
||||
color: Colors.white
|
||||
),
|
||||
),
|
||||
);
|
||||
return new Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: line == null ? <Widget>[icon] : <Widget>[line, icon],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
Widget accountNameLine = accountName == null ? null : new DefaultTextStyle(
|
||||
style: theme.primaryTextTheme.body2,
|
||||
child: accountName,
|
||||
);
|
||||
Widget accountEmailLine = accountEmail == null ? null : new DefaultTextStyle(
|
||||
style: theme.primaryTextTheme.body1,
|
||||
child: accountEmail,
|
||||
);
|
||||
if (onTap != null) {
|
||||
if (accountEmailLine != null)
|
||||
accountEmailLine = addDropdownIcon(accountEmailLine);
|
||||
else
|
||||
accountNameLine = addDropdownIcon(accountNameLine);
|
||||
}
|
||||
|
||||
Widget accountDetails;
|
||||
if (accountEmailLine != null || accountNameLine != null) {
|
||||
accountDetails = new Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: (accountEmailLine != null && accountNameLine != null)
|
||||
? <Widget>[accountNameLine, accountEmailLine]
|
||||
: <Widget>[accountNameLine ?? accountEmailLine]
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (onTap != null)
|
||||
accountDetails = new InkWell(onTap: onTap, child: accountDetails);
|
||||
|
||||
return new SizedBox(
|
||||
height: 56.0,
|
||||
child: accountDetails,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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]
|
||||
/// * [DrawerHeader], for a drawer header that doesn't show user acounts
|
||||
/// * <https://material.google.com/patterns/navigation-drawer.html>
|
||||
class UserAccountsDrawerHeader extends StatefulWidget {
|
||||
@ -38,30 +141,31 @@ class UserAccountsDrawerHeader extends StatefulWidget {
|
||||
this.onDetailsPressed
|
||||
}) : super(key: key);
|
||||
|
||||
/// A callback that gets called when the account name/email/dropdown
|
||||
/// section is pressed.
|
||||
final VoidCallback onDetailsPressed;
|
||||
|
||||
/// The background to show in the drawer header.
|
||||
/// The header's background. If decoration is null then a [BoxDecoration]
|
||||
/// with its background color set to the current theme's primaryColor is used.
|
||||
final Decoration decoration;
|
||||
|
||||
/// A widget placed in the upper-left corner representing the current
|
||||
/// account picture. Normally a [CircleAvatar].
|
||||
/// A widget placed in the upper-left corner that represents the current
|
||||
/// user's account. Normally a [CircleAvatar].
|
||||
final Widget currentAccountPicture;
|
||||
|
||||
/// A list of widgets that represent the user's accounts. Up to three of will
|
||||
/// be arranged in a row in the header's upper-right corner. Normally a list
|
||||
/// of [CircleAvatar] widgets.
|
||||
/// A list of widgets that represent the current user's other accounts.
|
||||
/// Up to three of these widgets will be 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 the
|
||||
/// account's name.
|
||||
/// A widget that represents the user's current account name. It is
|
||||
/// displayed on the left, below the [currentAccountPicture].
|
||||
final Widget accountName;
|
||||
|
||||
/// A widget placed on the bottom row of the account details representing the
|
||||
/// account's e-mail address.
|
||||
/// A widget that represents the email address of the user's current account.
|
||||
/// It is displayed on the left, below the [accountName].
|
||||
final Widget accountEmail;
|
||||
|
||||
/// A callback that is called when the horizontal area which contains the
|
||||
/// [accountName] and [accountEmail] is tapped.
|
||||
final VoidCallback onDetailsPressed;
|
||||
|
||||
@override
|
||||
_UserAccountsDrawerHeaderState createState() => new _UserAccountsDrawerHeaderState();
|
||||
}
|
||||
@ -72,89 +176,32 @@ class _UserAccountsDrawerHeaderState extends State<UserAccountsDrawerHeader> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
final List<Widget> otherAccountsPictures = config.otherAccountsPictures ?? <Widget>[];
|
||||
return new DrawerHeader(
|
||||
decoration: config.decoration,
|
||||
decoration: config.decoration ?? new BoxDecoration(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Expanded(
|
||||
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
|
||||
)
|
||||
)
|
||||
]
|
||||
child: new _AccountPictures(
|
||||
currentAccountPicture: config.currentAccountPicture,
|
||||
otherAccountsPictures: config.otherAccountsPictures,
|
||||
)
|
||||
),
|
||||
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 Expanded(
|
||||
child: new Align(
|
||||
alignment: FractionalOffset.centerRight,
|
||||
child: new Icon(
|
||||
_isOpen ? Icons.arrow_drop_up : Icons.arrow_drop_down,
|
||||
color: Colors.white
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
new _AccountDetails(
|
||||
accountName: config.accountName,
|
||||
accountEmail: config.accountEmail,
|
||||
isOpen: _isOpen,
|
||||
onTap: config.onDetailsPressed == null ? null : () {
|
||||
setState(() {
|
||||
_isOpen = !_isOpen;
|
||||
});
|
||||
config.onDetailsPressed();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -71,4 +71,93 @@ void main() {
|
||||
expect(avatarDTopRight.y - topRight.y, equals(16.0));
|
||||
expect(avatarDTopRight.x - avatarCTopRight.x, equals(40.0 + 16.0)); // size + space between
|
||||
});
|
||||
|
||||
|
||||
testWidgets('UserAccountsDrawerHeader null parameters', (WidgetTester tester) async {
|
||||
Widget buildFrame({
|
||||
Widget currentAccountPicture,
|
||||
List<Widget> otherAccountsPictures,
|
||||
Widget accountName,
|
||||
Widget accountEmail,
|
||||
VoidCallback onDetailsPressed,
|
||||
}) {
|
||||
return new Material(
|
||||
child: new UserAccountsDrawerHeader(
|
||||
currentAccountPicture: currentAccountPicture,
|
||||
otherAccountsPictures: otherAccountsPictures,
|
||||
accountName: accountName,
|
||||
accountEmail: accountEmail,
|
||||
onDetailsPressed: onDetailsPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame());
|
||||
expect(find.byType(Icon), findsNothing);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
onDetailsPressed: () { },
|
||||
));
|
||||
expect(find.byType(Icon), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
accountName: new Text('accountName'),
|
||||
onDetailsPressed: () { },
|
||||
));
|
||||
expect(
|
||||
tester.getCenter(find.text('accountName')).y,
|
||||
tester.getCenter(find.byType(Icon)).y
|
||||
);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
accountEmail: new Text('accountEmail'),
|
||||
onDetailsPressed: () { },
|
||||
));
|
||||
expect(
|
||||
tester.getCenter(find.text('accountEmail')).y,
|
||||
tester.getCenter(find.byType(Icon)).y
|
||||
);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
accountName: new Text('accountName'),
|
||||
accountEmail: new Text('accountEmail'),
|
||||
onDetailsPressed: () { },
|
||||
));
|
||||
expect(
|
||||
tester.getCenter(find.text('accountEmail')).y,
|
||||
tester.getCenter(find.byType(Icon)).y
|
||||
);
|
||||
expect(
|
||||
tester.getBottomLeft(find.text('accountEmail')).y,
|
||||
greaterThan(tester.getBottomLeft(find.text('accountName')).y)
|
||||
);
|
||||
expect(
|
||||
tester.getBottomLeft(find.text('accountEmail')).x,
|
||||
tester.getBottomLeft(find.text('accountName')).x
|
||||
);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
currentAccountPicture: new CircleAvatar(child: new Text('A')),
|
||||
));
|
||||
expect(find.text('A'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
otherAccountsPictures: <Widget>[new CircleAvatar(child: new Text('A'))],
|
||||
));
|
||||
expect(find.text('A'), findsOneWidget);
|
||||
|
||||
final Key avatarA = new Key('A');
|
||||
await tester.pumpWidget(buildFrame(
|
||||
currentAccountPicture: new CircleAvatar(key: avatarA, child: new Text('A')),
|
||||
accountName: new Text('accountName'),
|
||||
));
|
||||
expect(
|
||||
tester.getBottomLeft(find.byKey(avatarA)).x,
|
||||
tester.getBottomLeft(find.text('accountName')).x
|
||||
);
|
||||
expect(
|
||||
tester.getBottomLeft(find.text('accountName')).y,
|
||||
greaterThan(tester.getBottomLeft(find.byKey(avatarA)).y)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user