Unverified Commit bec91216 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added ListView.separated() constructor (#18619)

parent a57aff05
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/rendering.dart';
import 'basic.dart';
......@@ -681,6 +683,91 @@ class ListView extends BoxScrollView {
cacheExtent: cacheExtent
);
/// Creates a fixed-length scrollable linear array of list "items" separated
/// by list item "separators".
///
/// This constructor is appropriate for list views with a large number of
/// item and separator children because the builders are only called for
/// the children that are actually visible.
///
/// The `itemBuilder` callback will be called with indices greater than
/// or equal to zero and less than `itemCount`.
///
/// Separators only appear between list items: separator 0 appears after item
/// 0 and the last separator appears before the last item.
///
/// The `separatorBuilder` callback will be called with indices greater than
/// or equal to zero and less than `itemCount - 1`.
///
/// The `itemBuilder` and `separatorBuilder` callbacks should actually create
/// widget instances when called. Avoid using a builder that returns a
/// previously-constructed widget; if the list view's children are created in
/// advance, or all at once when the [ListView] itself is created, it is more
/// efficient to use [new ListView].
///
/// ## Sample code
///
/// This example shows how to create [ListView] whose [ListTile] list items
/// are separated by [Divider]s.
///
/// ```dart
/// new ListView.separated(
/// itemCount: 25,
/// separatorBuilder: (BuildContext context, int index) => new Divider(),
/// itemBuilder: (BuildContext context, int index) {
/// return new ListTile(
/// title: new Text('item $index'),
/// );
/// },
/// )
/// ```
///
/// The `addAutomaticKeepAlives` argument corresponds to the
/// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
/// `addRepaintBoundaries` argument corresponds to the
/// [SliverChildBuilderDelegate.addRepaintBoundaries] property. Both must not
/// be null.
ListView.separated({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required IndexedWidgetBuilder itemBuilder,
@required IndexedWidgetBuilder separatorBuilder,
@required int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double cacheExtent,
}) : assert(itemBuilder != null),
assert(separatorBuilder != null),
assert(itemCount != null && itemCount >= 0),
itemExtent = null,
childrenDelegate = new SliverChildBuilderDelegate(
(BuildContext context, int index) {
final int itemIndex = index ~/ 2;
return (index == 0 || index.isEven)
? itemBuilder(context, itemIndex)
: separatorBuilder(context, itemIndex);
},
childCount: math.max(0, itemCount * 2 - 1),
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
), super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller,
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent
);
/// Creates a scrollable, linear array of widgets with a custom child model.
///
/// For example, a custom child model can control the algorithm used to
......
......@@ -261,6 +261,49 @@ void main() {
callbackTracker.clear();
});
testWidgets('ListView.separated', (WidgetTester tester) async {
Widget buildFrame({ int itemCount }) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new ListView.separated(
itemCount: itemCount,
itemBuilder: (BuildContext context, int index) {
return new SizedBox(
height: 100.0,
child: new Text('i$index'),
);
},
separatorBuilder: (BuildContext context, int index) {
return new SizedBox(
height: 10.0,
child: new Text('s$index'),
);
},
),
);
}
await tester.pumpWidget(buildFrame(itemCount: 0));
expect(find.text('i0'), findsNothing);
expect(find.text('s0'), findsNothing);
await tester.pumpWidget(buildFrame(itemCount: 1));
expect(find.text('i0'), findsOneWidget);
expect(find.text('s0'), findsNothing);
await tester.pumpWidget(buildFrame(itemCount: 2));
expect(find.text('i0'), findsOneWidget);
expect(find.text('s0'), findsOneWidget);
expect(find.text('i1'), findsOneWidget);
expect(find.text('s1'), findsNothing);
// ListView's height is 600, so items i0-i5 and s0-s4 fit.
await tester.pumpWidget(buildFrame(itemCount: 25));
for(String s in <String>['i0', 's0', 'i1', 's1', 'i2', 's2', 'i3', 's3', 'i4', 's4', 'i5'])
expect(find.text(s), findsOneWidget);
expect(find.text('s5'), findsNothing);
expect(find.text('i6'), findsNothing);
});
}
void check({List<int> visible = const <int>[], List<int> hidden = const <int>[]}) {
......
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