From bec912165a1b26bdfd369e7cd94fd20536e179dc Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Thu, 21 Jun 2018 16:04:16 -0700 Subject: [PATCH] Added ListView.separated() constructor (#18619) --- .../flutter/lib/src/widgets/scroll_view.dart | 87 +++++++++++++++++++ .../test/widgets/list_view_builder_test.dart | 43 +++++++++ 2 files changed, 130 insertions(+) diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index 32d9ba2983a..2dad1644a21 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -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 diff --git a/packages/flutter/test/widgets/list_view_builder_test.dart b/packages/flutter/test/widgets/list_view_builder_test.dart index 4d59b39ae31..38ec80399c2 100644 --- a/packages/flutter/test/widgets/list_view_builder_test.dart +++ b/packages/flutter/test/widgets/list_view_builder_test.dart @@ -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 ['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 visible = const [], List hidden = const []}) {