Update ExpansionTile, added a sample app (#10019)
This commit is contained in:
parent
ff0aa51326
commit
09e8c2ffb0
87
examples/catalog/lib/expansion_tile_sample.dart
Normal file
87
examples/catalog/lib/expansion_tile_sample.dart
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright 2017 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 Entry {
|
||||
Entry(this.title, [this.children = const <Entry>[]]);
|
||||
final String title;
|
||||
final List<Entry> children;
|
||||
}
|
||||
|
||||
final List<Entry> data = <Entry>[
|
||||
new Entry('Chapter A',
|
||||
<Entry>[
|
||||
new Entry('Section A0',
|
||||
<Entry>[
|
||||
new Entry('Item A0.1'),
|
||||
new Entry('Item A0.2'),
|
||||
new Entry('Item A0.3'),
|
||||
],
|
||||
),
|
||||
new Entry('Section A1'),
|
||||
new Entry('Section A2'),
|
||||
],
|
||||
),
|
||||
new Entry('Chapter B',
|
||||
<Entry>[
|
||||
new Entry('Section B0'),
|
||||
new Entry('Section B1'),
|
||||
],
|
||||
),
|
||||
new Entry('Chapter C',
|
||||
<Entry>[
|
||||
new Entry('Section C0'),
|
||||
new Entry('Section C1'),
|
||||
new Entry('Section C2',
|
||||
<Entry>[
|
||||
new Entry('Item C2.0'),
|
||||
new Entry('Item C2.1'),
|
||||
new Entry('Item C2.2'),
|
||||
new Entry('Item C2.3'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
class EntryItem extends StatelessWidget {
|
||||
EntryItem(this.entry);
|
||||
|
||||
final Entry entry;
|
||||
|
||||
Widget _buildTiles(Entry root) {
|
||||
if (root.children.isEmpty)
|
||||
return new ListTile(title: new Text(root.title));
|
||||
return new ExpansionTile(
|
||||
key: new ValueKey<Entry>(root),
|
||||
title: new Text(root.title),
|
||||
children: root.children.map(_buildTiles).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _buildTiles(entry);
|
||||
}
|
||||
}
|
||||
|
||||
class ExpansionTileSample extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
appBar: new AppBar(
|
||||
title: const Text('ExpansionTile'),
|
||||
),
|
||||
body: new ListView.builder(
|
||||
itemBuilder: (BuildContext context, int index) => new EntryItem(data[index]),
|
||||
itemCount: data.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(new MaterialApp(home: new ExpansionTileSample()));
|
||||
}
|
91
examples/catalog/test/expansion_tile_sample_test.dart
Normal file
91
examples/catalog/test/expansion_tile_sample_test.dart
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2017 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_test/flutter_test.dart';
|
||||
|
||||
import '../lib/expansion_tile_sample.dart' as expansion_tile_sample;
|
||||
import '../lib/expansion_tile_sample.dart' show Entry;
|
||||
|
||||
void main() {
|
||||
testWidgets("expansion_tile sample smoke test", (WidgetTester tester) async {
|
||||
expansion_tile_sample.main();
|
||||
await tester.pump();
|
||||
|
||||
// Initially only the top level EntryItems (the "chapters") are present.
|
||||
for (Entry chapter in expansion_tile_sample.data) {
|
||||
expect(find.text(chapter.title), findsOneWidget);
|
||||
for (Entry section in chapter.children) {
|
||||
expect(find.text(section.title), findsNothing);
|
||||
for (Entry item in section.children)
|
||||
expect(find.text(item.title), findsNothing);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Null> scrollUpOneEntry() async {
|
||||
await tester.dragFrom(const Offset(200.0, 200.0), const Offset(0.0, -88.00));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<Null> tapEntry(String title) async {
|
||||
await tester.tap(find.text(title));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
// Expand the chapters. Now the chapter and sections, but not the
|
||||
// items, should be present.
|
||||
for (Entry chapter in expansion_tile_sample.data.reversed)
|
||||
await tapEntry(chapter.title);
|
||||
|
||||
for (Entry chapter in expansion_tile_sample.data) {
|
||||
expect(find.text(chapter.title), findsOneWidget);
|
||||
for (Entry section in chapter.children) {
|
||||
expect(find.text(section.title), findsOneWidget);
|
||||
await scrollUpOneEntry();
|
||||
for (Entry item in section.children)
|
||||
expect(find.text(item.title), findsNothing);
|
||||
}
|
||||
await scrollUpOneEntry();
|
||||
}
|
||||
|
||||
// - scroll to the top -
|
||||
await tester.flingFrom(const Offset(200.0, 200.0), const Offset(0.0, 100.0), 5000.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expand the sections. Now Widgets for all three levels should be present.
|
||||
for (Entry chapter in expansion_tile_sample.data) {
|
||||
for (Entry section in chapter.children) {
|
||||
await tapEntry(section.title);
|
||||
await scrollUpOneEntry();
|
||||
}
|
||||
await scrollUpOneEntry();
|
||||
}
|
||||
|
||||
// We're scrolled to the bottom so the very last item is visible.
|
||||
// Working in reverse order, so we don't need to do anymore scrolling,
|
||||
// check that everything is visible and close the sections and
|
||||
// chapters as we go up.
|
||||
for (Entry chapter in expansion_tile_sample.data.reversed) {
|
||||
expect(find.text(chapter.title), findsOneWidget);
|
||||
for (Entry section in chapter.children.reversed) {
|
||||
expect(find.text(section.title), findsOneWidget);
|
||||
for (Entry item in section.children.reversed)
|
||||
expect(find.text(item.title), findsOneWidget);
|
||||
await tapEntry(section.title); // close the section
|
||||
}
|
||||
await tapEntry(chapter.title); // close the chapter
|
||||
}
|
||||
|
||||
// Finally only the top level EntryItems (the "chapters") are present.
|
||||
for (Entry chapter in expansion_tile_sample.data) {
|
||||
expect(find.text(chapter.title), findsOneWidget);
|
||||
for (Entry section in chapter.children) {
|
||||
expect(find.text(section.title), findsNothing);
|
||||
for (Entry item in section.children)
|
||||
expect(find.text(item.title), findsNothing);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
@ -20,7 +20,9 @@ const Duration _kExpand = const Duration(milliseconds: 200);
|
||||
/// the tile to reveal or hide the [children].
|
||||
///
|
||||
/// This widget is typically used with [ListView] to create an
|
||||
/// "expand / collapse" list entry.
|
||||
/// "expand / collapse" list entry. When used with scrolling widgets like
|
||||
/// [ListView], a unique [key] must be specified to enable the [ExpansionTile] to
|
||||
/// save and restore its expanded state when it is scrolled in and out of view.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -110,7 +112,11 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
||||
if (_isExpanded)
|
||||
_controller.forward();
|
||||
else
|
||||
_controller.reverse();
|
||||
_controller.reverse().then((Null value) {
|
||||
setState(() {
|
||||
// Rebuild without widget.children.
|
||||
});
|
||||
});
|
||||
PageStorage.of(context)?.writeState(context, _isExpanded);
|
||||
});
|
||||
if (widget.onExpansionChanged != null)
|
||||
@ -172,10 +178,12 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
||||
..begin = Colors.transparent
|
||||
..end = widget.backgroundColor ?? Colors.transparent;
|
||||
|
||||
final bool closed = !_isExpanded && _controller.isDismissed;
|
||||
return new AnimatedBuilder(
|
||||
animation: _controller.view,
|
||||
builder: _buildChildren,
|
||||
child: new Column(children: widget.children),
|
||||
child: closed ? null : new Column(children: widget.children),
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user