Skip to content

Commit a9fac73

Browse files
authored
[New feature] Allowing the ListView slivers to have different extents while still having scrolling performance (#131393)
Fixes flutter/flutter#113431 Currently we only support specifying all slivers to have the same extent. This patch introduces an `itemExtentBuilder` property for `ListView`, allowing the slivers to have different extents while still having scrolling performance, especially when the scroll position changes drastically(such as scrolling by the scrollbar or controller.jumpTo()). @Piinks Hi, Any thoughts about this? :)
1 parent e645fb7 commit a9fac73

File tree

8 files changed

+614
-73
lines changed

8 files changed

+614
-73
lines changed

packages/flutter/lib/src/material/reorderable_list.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:ui' show lerpDouble;
66

77
import 'package:flutter/gestures.dart';
8+
import 'package:flutter/rendering.dart';
89
import 'package:flutter/widgets.dart';
910

1011
import 'debug.dart';
@@ -76,6 +77,7 @@ class ReorderableListView extends StatefulWidget {
7677
this.onReorderStart,
7778
this.onReorderEnd,
7879
this.itemExtent,
80+
this.itemExtentBuilder,
7981
this.prototypeItem,
8082
this.proxyDecorator,
8183
this.buildDefaultDragHandles = true,
@@ -96,8 +98,10 @@ class ReorderableListView extends StatefulWidget {
9698
this.clipBehavior = Clip.hardEdge,
9799
this.autoScrollerVelocityScalar,
98100
}) : assert(
99-
itemExtent == null || prototypeItem == null,
100-
'You can only pass itemExtent or prototypeItem, not both',
101+
(itemExtent == null && prototypeItem == null) ||
102+
(itemExtent == null && itemExtentBuilder == null) ||
103+
(prototypeItem == null && itemExtentBuilder == null),
104+
'You can only pass one of itemExtent, prototypeItem and itemExtentBuilder.',
101105
),
102106
assert(
103107
children.every((Widget w) => w.key != null),
@@ -142,6 +146,7 @@ class ReorderableListView extends StatefulWidget {
142146
this.onReorderStart,
143147
this.onReorderEnd,
144148
this.itemExtent,
149+
this.itemExtentBuilder,
145150
this.prototypeItem,
146151
this.proxyDecorator,
147152
this.buildDefaultDragHandles = true,
@@ -163,8 +168,10 @@ class ReorderableListView extends StatefulWidget {
163168
this.autoScrollerVelocityScalar,
164169
}) : assert(itemCount >= 0),
165170
assert(
166-
itemExtent == null || prototypeItem == null,
167-
'You can only pass itemExtent or prototypeItem, not both',
171+
(itemExtent == null && prototypeItem == null) ||
172+
(itemExtent == null && itemExtentBuilder == null) ||
173+
(prototypeItem == null && itemExtentBuilder == null),
174+
'You can only pass one of itemExtent, prototypeItem and itemExtentBuilder.',
168175
);
169176

170177
/// {@macro flutter.widgets.reorderable_list.itemBuilder}
@@ -269,6 +276,9 @@ class ReorderableListView extends StatefulWidget {
269276
/// {@macro flutter.widgets.list_view.itemExtent}
270277
final double? itemExtent;
271278

279+
/// {@macro flutter.widgets.list_view.itemExtentBuilder}
280+
final ItemExtentBuilder? itemExtentBuilder;
281+
272282
/// {@macro flutter.widgets.list_view.prototypeItem}
273283
final Widget? prototypeItem;
274284

@@ -440,6 +450,7 @@ class _ReorderableListViewState extends State<ReorderableListView> {
440450
sliver: SliverReorderableList(
441451
itemBuilder: _itemBuilder,
442452
itemExtent: widget.itemExtent,
453+
itemExtentBuilder: widget.itemExtentBuilder,
443454
prototypeItem: widget.prototypeItem,
444455
itemCount: widget.itemCount,
445456
onReorder: widget.onReorder,

packages/flutter/lib/src/rendering/sliver.dart

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,71 @@ import 'viewport_offset.dart';
1616
// CORE TYPES FOR SLIVERS
1717
// The RenderSliver base class and its helper types.
1818

19+
/// Called to get the item extent by the index of item.
20+
///
21+
/// Used by [ListView.itemExtentBuilder] and [SliverVariedExtentList.itemExtentBuilder].
22+
typedef ItemExtentBuilder = double Function(int index, SliverLayoutDimensions dimensions);
23+
24+
/// Relates the dimensions of the [RenderSliver] during layout.
25+
///
26+
/// Used by [ListView.itemExtentBuilder] and [SliverVariedExtentList.itemExtentBuilder].
27+
@immutable
28+
class SliverLayoutDimensions {
29+
/// Constructs a [SliverLayoutDimensions] with the specified parameters.
30+
const SliverLayoutDimensions({
31+
required this.scrollOffset,
32+
required this.precedingScrollExtent,
33+
required this.viewportMainAxisExtent,
34+
required this.crossAxisExtent
35+
});
36+
37+
/// {@macro flutter.rendering.SliverConstraints.scrollOffset}
38+
final double scrollOffset;
39+
40+
/// {@macro flutter.rendering.SliverConstraints.precedingScrollExtent}
41+
final double precedingScrollExtent;
42+
43+
/// The number of pixels the viewport can display in the main axis.
44+
///
45+
/// For a vertical list, this is the height of the viewport.
46+
final double viewportMainAxisExtent;
47+
48+
/// The number of pixels in the cross-axis.
49+
///
50+
/// For a vertical list, this is the width of the sliver.
51+
final double crossAxisExtent;
52+
53+
@override
54+
bool operator ==(Object other) {
55+
if (identical(this, other)) {
56+
return true;
57+
}
58+
if (other is! SliverLayoutDimensions) {
59+
return false;
60+
}
61+
return other.scrollOffset == scrollOffset &&
62+
other.precedingScrollExtent == precedingScrollExtent &&
63+
other.viewportMainAxisExtent == viewportMainAxisExtent &&
64+
other.crossAxisExtent == crossAxisExtent;
65+
}
66+
67+
@override
68+
String toString() {
69+
return 'scrollOffset: $scrollOffset'
70+
' precedingScrollExtent: $precedingScrollExtent'
71+
' viewportMainAxisExtent: $viewportMainAxisExtent'
72+
' crossAxisExtent: $crossAxisExtent';
73+
}
74+
75+
@override
76+
int get hashCode => Object.hash(
77+
scrollOffset,
78+
precedingScrollExtent,
79+
viewportMainAxisExtent,
80+
viewportMainAxisExtent
81+
);
82+
}
83+
1984
/// The direction in which a sliver's contents are ordered, relative to the
2085
/// scroll offset axis.
2186
///
@@ -224,12 +289,13 @@ class SliverConstraints extends Constraints {
224289
/// {@macro flutter.rendering.ScrollDirection.sample}
225290
final ScrollDirection userScrollDirection;
226291

292+
/// {@template flutter.rendering.SliverConstraints.scrollOffset}
227293
/// The scroll offset, in this sliver's coordinate system, that corresponds to
228294
/// the earliest visible part of this sliver in the [AxisDirection] if
229-
/// [growthDirection] is [GrowthDirection.forward] or in the opposite
230-
/// [AxisDirection] direction if [growthDirection] is [GrowthDirection.reverse].
295+
/// [SliverConstraints.growthDirection] is [GrowthDirection.forward] or in the opposite
296+
/// [AxisDirection] direction if [SliverConstraints.growthDirection] is [GrowthDirection.reverse].
231297
///
232-
/// For example, if [AxisDirection] is [AxisDirection.down] and [growthDirection]
298+
/// For example, if [AxisDirection] is [AxisDirection.down] and [SliverConstraints.growthDirection]
233299
/// is [GrowthDirection.forward], then scroll offset is the amount the top of
234300
/// the sliver has been scrolled past the top of the viewport.
235301
///
@@ -240,7 +306,7 @@ class SliverConstraints extends Constraints {
240306
///
241307
/// For slivers whose top is not past the top of the viewport, the
242308
/// [scrollOffset] is `0` when [AxisDirection] is [AxisDirection.down] and
243-
/// [growthDirection] is [GrowthDirection.forward]. The set of slivers with
309+
/// [SliverConstraints.growthDirection] is [GrowthDirection.forward]. The set of slivers with
244310
/// [scrollOffset] `0` includes all the slivers that are below the bottom of the
245311
/// viewport.
246312
///
@@ -249,9 +315,11 @@ class SliverConstraints extends Constraints {
249315
/// partially 'protrude in' from the bottom of the viewport.
250316
///
251317
/// Whether this corresponds to the beginning or the end of the sliver's
252-
/// contents depends on the [growthDirection].
318+
/// contents depends on the [SliverConstraints.growthDirection].
319+
/// {@endtemplate}
253320
final double scrollOffset;
254321

322+
/// {@template flutter.rendering.SliverConstraints.precedingScrollExtent}
255323
/// The scroll distance that has been consumed by all [RenderSliver]s that
256324
/// came before this [RenderSliver].
257325
///
@@ -273,6 +341,7 @@ class SliverConstraints extends Constraints {
273341
/// content forever without reaching the end. For any [RenderSliver]s that
274342
/// appear after the infinite [RenderSliver], the [precedingScrollExtent] will
275343
/// be [double.infinity].
344+
/// {@endtemplate}
276345
final double precedingScrollExtent;
277346

278347
/// The number of pixels from where the pixels corresponding to the

0 commit comments

Comments
 (0)