Commit 5ae1b41c authored by Hans Muller's avatar Hans Muller

Added TwoLevelList

parent 869d13c1
......@@ -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
......
// 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
......
// 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);
}
// 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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment