mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Added TwoLevelList
This commit is contained in:
parent
869d13c18d
commit
5ae1b41ca4
@ -12,6 +12,8 @@ material-design-icons:
|
||||
- name: navigation/arrow_forward
|
||||
- name: navigation/arrow_back
|
||||
- name: navigation/cancel
|
||||
- name: navigation/expand_less
|
||||
- name: navigation/expand_more
|
||||
- name: navigation/menu
|
||||
- name: action/event
|
||||
- name: action/home
|
||||
|
32
examples/material_gallery/lib/demo/two_level_list_demo.dart
Normal file
32
examples/material_gallery/lib/demo/two_level_list_demo.dart
Normal file
@ -0,0 +1,32 @@
|
||||
// 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';
|
||||
|
||||
class TwoLevelListDemo extends StatelessComponent {
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
toolBar: new ToolBar(center: new Text('Expand/Collapse List Control')),
|
||||
body: new Padding(
|
||||
padding: const EdgeDims.all(0.0),
|
||||
child: new TwoLevelList(
|
||||
type: MaterialListType.oneLine,
|
||||
items: <Widget>[
|
||||
new TwoLevelListItem(center: new Text('Top')),
|
||||
new TwoLevelSublist(
|
||||
center: new Text('Sublist'),
|
||||
children: <Widget>[
|
||||
new TwoLevelListItem(center: new Text('One')),
|
||||
new TwoLevelListItem(center: new Text('Two')),
|
||||
new TwoLevelListItem(center: new Text('Free')),
|
||||
new TwoLevelListItem(center: new Text('Four'))
|
||||
]
|
||||
),
|
||||
new TwoLevelListItem(center: new Text('Bottom'))
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import 'demo/toggle_controls_demo.dart';
|
||||
import 'demo/slider_demo.dart';
|
||||
import 'demo/tabs_demo.dart';
|
||||
import 'demo/time_picker_demo.dart';
|
||||
import 'demo/two_level_list_demo.dart';
|
||||
|
||||
class GalleryDemo {
|
||||
GalleryDemo({ this.title, this.builder });
|
||||
@ -162,6 +163,7 @@ class GalleryHome extends StatelessComponent {
|
||||
new GalleryDemo(title: 'Toggle Controls', builder: (_) => new ToggleControlsDemo()),
|
||||
new GalleryDemo(title: 'Dropdown Button', builder: (_) => new DropDownDemo()),
|
||||
new GalleryDemo(title: 'Tabs', builder: (_) => new TabsDemo()),
|
||||
new GalleryDemo(title: 'Expland/Collapse List Control', builder: (_) => new TwoLevelListDemo()),
|
||||
new GalleryDemo(title: 'Page Selector', builder: (_) => new PageSelectorDemo()),
|
||||
new GalleryDemo(title: 'Date Picker', builder: (_) => new DatePickerDemo()),
|
||||
new GalleryDemo(title: 'Time Picker', builder: (_) => new TimePickerDemo())
|
||||
|
@ -54,6 +54,7 @@ export 'src/material/time_picker_dialog.dart';
|
||||
export 'src/material/toggleable.dart';
|
||||
export 'src/material/tool_bar.dart';
|
||||
export 'src/material/tooltip.dart';
|
||||
export 'src/material/two_level_list.dart';
|
||||
export 'src/material/typography.dart';
|
||||
|
||||
export 'widgets.dart';
|
||||
|
@ -14,7 +14,7 @@ enum MaterialListType {
|
||||
threeLine
|
||||
}
|
||||
|
||||
Map<MaterialListType, double> _kItemExtent = const <MaterialListType, double>{
|
||||
Map<MaterialListType, double> kListItemExtent = const <MaterialListType, double>{
|
||||
MaterialListType.oneLine: kOneLineListItemHeight,
|
||||
MaterialListType.oneLineWithAvatar: kOneLineListItemWithAvatarHeight,
|
||||
MaterialListType.twoLine: kTwoLineListItemHeight,
|
||||
@ -46,7 +46,7 @@ class _MaterialListState extends State<MaterialList> {
|
||||
initialScrollOffset: config.initialScrollOffset,
|
||||
scrollDirection: Axis.vertical,
|
||||
onScroll: config.onScroll,
|
||||
itemExtent: _kItemExtent[config.type],
|
||||
itemExtent: kListItemExtent[config.type],
|
||||
padding: const EdgeDims.symmetric(vertical: 8.0),
|
||||
scrollableListPainter: _scrollbarPainter,
|
||||
children: config.children
|
||||
|
159
packages/flutter/lib/src/material/two_level_list.dart
Normal file
159
packages/flutter/lib/src/material/two_level_list.dart
Normal file
@ -0,0 +1,159 @@
|
||||
// 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/animation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
import 'icon.dart';
|
||||
import 'list_item.dart';
|
||||
import 'material_list.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
|
||||
const Duration _kExpand = const Duration(milliseconds: 200);
|
||||
|
||||
class TwoLevelListItem extends StatelessComponent {
|
||||
TwoLevelListItem({
|
||||
Key key,
|
||||
this.left,
|
||||
this.center,
|
||||
this.right,
|
||||
this.onTap,
|
||||
this.onLongPress
|
||||
}) : super(key: key) {
|
||||
assert(center != null);
|
||||
}
|
||||
|
||||
final Widget left;
|
||||
final Widget center;
|
||||
final Widget right;
|
||||
final GestureTapCallback onTap;
|
||||
final GestureLongPressCallback onLongPress;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
final TwoLevelList parentList = context.ancestorWidgetOfExactType(TwoLevelList);
|
||||
assert(parentList != null);
|
||||
|
||||
return new SizedBox(
|
||||
height: kListItemExtent[parentList.type],
|
||||
child: new ListItem(
|
||||
left: left,
|
||||
center: center,
|
||||
right: right,
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TwoLevelSublist extends StatefulComponent {
|
||||
TwoLevelSublist({ Key key, this.left, this.center, this.children }) : super(key: key);
|
||||
|
||||
final Widget left;
|
||||
final Widget center;
|
||||
final List<Widget> children;
|
||||
|
||||
_TwoLevelSublistState createState() => new _TwoLevelSublistState();
|
||||
}
|
||||
|
||||
class _TwoLevelSublistState extends State<TwoLevelSublist> {
|
||||
AnimationController _controller;
|
||||
CurvedAnimation _easeOutAnimation;
|
||||
CurvedAnimation _easeInAnimation;
|
||||
ColorTween _borderColor;
|
||||
ColorTween _headerColor;
|
||||
ColorTween _iconColor;
|
||||
Animation<double> _iconTurns;
|
||||
|
||||
bool _isExpanded = false;
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = new AnimationController(duration: _kExpand);
|
||||
_easeOutAnimation = new CurvedAnimation(parent: _controller, curve: Curves.easeOut);
|
||||
_easeInAnimation = new CurvedAnimation(parent: _controller, curve: Curves.easeIn);
|
||||
_borderColor = new ColorTween(begin: Colors.transparent);
|
||||
_headerColor = new ColorTween();
|
||||
_iconColor = new ColorTween();
|
||||
_iconTurns = new Tween<double>(begin: 0.0, end: 0.5).animate(_easeInAnimation);
|
||||
|
||||
_isExpanded = PageStorage.of(context)?.readState(context) ?? false;
|
||||
if (_isExpanded)
|
||||
_controller.value = 1.0;
|
||||
}
|
||||
|
||||
void _handleOnTap() {
|
||||
setState(() {
|
||||
_isExpanded = !_isExpanded;
|
||||
if (_isExpanded)
|
||||
_controller.forward();
|
||||
else
|
||||
_controller.reverse();
|
||||
PageStorage.of(context)?.writeState(context, _isExpanded);
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildList(BuildContext context, Widget child) {
|
||||
return new Container(
|
||||
decoration: new BoxDecoration(
|
||||
border: new Border(
|
||||
top: new BorderSide(color: _borderColor.evaluate(_easeOutAnimation)),
|
||||
bottom: new BorderSide(color: _borderColor.evaluate(_easeOutAnimation))
|
||||
)
|
||||
),
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new TwoLevelListItem(
|
||||
onTap: _handleOnTap,
|
||||
left: config.left,
|
||||
center: new DefaultTextStyle(
|
||||
style: Theme.of(context).text.body1.copyWith(color: _headerColor.evaluate(_easeInAnimation)),
|
||||
child: config.center
|
||||
),
|
||||
right: new RotationTransition(
|
||||
turns: _iconTurns,
|
||||
child: new Icon(
|
||||
icon: 'navigation/expand_more',
|
||||
colorFilter: new ColorFilter.mode(_iconColor.evaluate(_easeInAnimation), TransferMode.srcATop)
|
||||
)
|
||||
)
|
||||
),
|
||||
new ClipRect(
|
||||
child: new Align(
|
||||
heightFactor: _easeInAnimation.value,
|
||||
child: new Column(children: config.children)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
_borderColor.end = theme.dividerColor;
|
||||
_headerColor
|
||||
..begin = theme.text.body1.color
|
||||
..end = theme.accentColor;
|
||||
_iconColor
|
||||
..begin = theme.unselectedColor
|
||||
..end = theme.accentColor;
|
||||
|
||||
return new AnimatedBuilder(
|
||||
animation: _controller.view,
|
||||
builder: buildList
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TwoLevelList extends StatelessComponent {
|
||||
TwoLevelList({ Key key, this.items, this.type: MaterialListType.twoLine }) : super(key: key);
|
||||
|
||||
final List<Widget> items;
|
||||
final MaterialListType type;
|
||||
|
||||
Widget build(BuildContext context) => new Block(items);
|
||||
}
|
71
packages/flutter/test/widget/two_level_list_test.dart
Normal file
71
packages/flutter/test/widget/two_level_list_test.dart
Normal file
@ -0,0 +1,71 @@
|
||||
// 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_test/flutter_test.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('TwoLeveList basics', () {
|
||||
final Key topKey = new UniqueKey();
|
||||
final Key sublistKey = new UniqueKey();
|
||||
final Key bottomKey = new UniqueKey();
|
||||
|
||||
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
|
||||
'/': (_) {
|
||||
return new Material(
|
||||
child: new Viewport(
|
||||
child: new TwoLevelList(
|
||||
items: <Widget>[
|
||||
new TwoLevelListItem(center: new Text('Top'), key: topKey),
|
||||
new TwoLevelSublist(
|
||||
key: sublistKey,
|
||||
center: new Text('Sublist'),
|
||||
children: <Widget>[
|
||||
new TwoLevelListItem(center: new Text('0')),
|
||||
new TwoLevelListItem(center: new Text('1'))
|
||||
]
|
||||
),
|
||||
new TwoLevelListItem(center: new Text('Bottom'), key: bottomKey)
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
testWidgets((WidgetTester tester) {
|
||||
tester.pumpWidget(new MaterialApp(routes: routes));
|
||||
|
||||
expect(tester.findText('Top'), isNotNull);
|
||||
expect(tester.findText('Sublist'), isNotNull);
|
||||
expect(tester.findText('Bottom'), isNotNull);
|
||||
|
||||
double getY(Key key) => tester.getTopLeft(tester.findElementByKey(key)).y;
|
||||
double getHeight(Key key) => tester.getSize(tester.findElementByKey(key)).height;
|
||||
|
||||
expect(getY(topKey), lessThan(getY(sublistKey)));
|
||||
expect(getY(sublistKey), lessThan(getY(bottomKey)));
|
||||
|
||||
expect(getHeight(topKey), equals(getHeight(sublistKey)));
|
||||
expect(getHeight(sublistKey), equals(getHeight(bottomKey)));
|
||||
|
||||
tester.tap(tester.findText('Sublist'));
|
||||
tester.pump(const Duration(seconds: 1));
|
||||
tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(tester.findText('Top'), isNotNull);
|
||||
expect(tester.findText('Sublist'), isNotNull);
|
||||
expect(tester.findText('0'), isNotNull);
|
||||
expect(tester.findText('1'), isNotNull);
|
||||
expect(tester.findText('Bottom'), isNotNull);
|
||||
|
||||
expect(getY(topKey), lessThan(getY(sublistKey)));
|
||||
expect(getY(sublistKey), lessThan(getY(bottomKey)));
|
||||
expect(getY(bottomKey) - getY(sublistKey), greaterThan(getHeight(topKey)));
|
||||
expect(getY(bottomKey) - getY(sublistKey), greaterThan(getHeight(bottomKey)));
|
||||
});
|
||||
});
|
||||
}
|
@ -99,6 +99,13 @@ class Instrumentation {
|
||||
return _getElementPoint(element, (Size size) => size.bottomRight(Point.origin));
|
||||
}
|
||||
|
||||
Size getSize(Element element) {
|
||||
assert(element != null);
|
||||
RenderBox box = element.renderObject as RenderBox;
|
||||
assert(box != null);
|
||||
return box.size;
|
||||
}
|
||||
|
||||
Point _getElementPoint(Element element, SizeToPointFunction sizeToPoint) {
|
||||
assert(element != null);
|
||||
RenderBox box = element.renderObject as RenderBox;
|
||||
|
Loading…
Reference in New Issue
Block a user