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_forward
|
||||||
- name: navigation/arrow_back
|
- name: navigation/arrow_back
|
||||||
- name: navigation/cancel
|
- name: navigation/cancel
|
||||||
|
- name: navigation/expand_less
|
||||||
|
- name: navigation/expand_more
|
||||||
- name: navigation/menu
|
- name: navigation/menu
|
||||||
- name: action/event
|
- name: action/event
|
||||||
- name: action/home
|
- 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/slider_demo.dart';
|
||||||
import 'demo/tabs_demo.dart';
|
import 'demo/tabs_demo.dart';
|
||||||
import 'demo/time_picker_demo.dart';
|
import 'demo/time_picker_demo.dart';
|
||||||
|
import 'demo/two_level_list_demo.dart';
|
||||||
|
|
||||||
class GalleryDemo {
|
class GalleryDemo {
|
||||||
GalleryDemo({ this.title, this.builder });
|
GalleryDemo({ this.title, this.builder });
|
||||||
@ -162,6 +163,7 @@ class GalleryHome extends StatelessComponent {
|
|||||||
new GalleryDemo(title: 'Toggle Controls', builder: (_) => new ToggleControlsDemo()),
|
new GalleryDemo(title: 'Toggle Controls', builder: (_) => new ToggleControlsDemo()),
|
||||||
new GalleryDemo(title: 'Dropdown Button', builder: (_) => new DropDownDemo()),
|
new GalleryDemo(title: 'Dropdown Button', builder: (_) => new DropDownDemo()),
|
||||||
new GalleryDemo(title: 'Tabs', builder: (_) => new TabsDemo()),
|
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: 'Page Selector', builder: (_) => new PageSelectorDemo()),
|
||||||
new GalleryDemo(title: 'Date Picker', builder: (_) => new DatePickerDemo()),
|
new GalleryDemo(title: 'Date Picker', builder: (_) => new DatePickerDemo()),
|
||||||
new GalleryDemo(title: 'Time Picker', builder: (_) => new TimePickerDemo())
|
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/toggleable.dart';
|
||||||
export 'src/material/tool_bar.dart';
|
export 'src/material/tool_bar.dart';
|
||||||
export 'src/material/tooltip.dart';
|
export 'src/material/tooltip.dart';
|
||||||
|
export 'src/material/two_level_list.dart';
|
||||||
export 'src/material/typography.dart';
|
export 'src/material/typography.dart';
|
||||||
|
|
||||||
export 'widgets.dart';
|
export 'widgets.dart';
|
||||||
|
@ -14,7 +14,7 @@ enum MaterialListType {
|
|||||||
threeLine
|
threeLine
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<MaterialListType, double> _kItemExtent = const <MaterialListType, double>{
|
Map<MaterialListType, double> kListItemExtent = const <MaterialListType, double>{
|
||||||
MaterialListType.oneLine: kOneLineListItemHeight,
|
MaterialListType.oneLine: kOneLineListItemHeight,
|
||||||
MaterialListType.oneLineWithAvatar: kOneLineListItemWithAvatarHeight,
|
MaterialListType.oneLineWithAvatar: kOneLineListItemWithAvatarHeight,
|
||||||
MaterialListType.twoLine: kTwoLineListItemHeight,
|
MaterialListType.twoLine: kTwoLineListItemHeight,
|
||||||
@ -46,7 +46,7 @@ class _MaterialListState extends State<MaterialList> {
|
|||||||
initialScrollOffset: config.initialScrollOffset,
|
initialScrollOffset: config.initialScrollOffset,
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
onScroll: config.onScroll,
|
onScroll: config.onScroll,
|
||||||
itemExtent: _kItemExtent[config.type],
|
itemExtent: kListItemExtent[config.type],
|
||||||
padding: const EdgeDims.symmetric(vertical: 8.0),
|
padding: const EdgeDims.symmetric(vertical: 8.0),
|
||||||
scrollableListPainter: _scrollbarPainter,
|
scrollableListPainter: _scrollbarPainter,
|
||||||
children: config.children
|
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));
|
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) {
|
Point _getElementPoint(Element element, SizeToPointFunction sizeToPoint) {
|
||||||
assert(element != null);
|
assert(element != null);
|
||||||
RenderBox box = element.renderObject as RenderBox;
|
RenderBox box = element.renderObject as RenderBox;
|
||||||
|
Loading…
Reference in New Issue
Block a user