Add clipBehavior to ListView, GridView, PageView (#63147)

These widgets are missing from
https://github.com/flutter/flutter/pull/59364

With this change, developers can use clipBehavior for
https://github.com/flutter/flutter/issues/59424
This commit is contained in:
Yuqian Li 2020-08-13 15:01:41 -07:00 committed by GitHub
parent 39a46bedad
commit 3ff76f47fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 273 additions and 2 deletions

View file

@ -656,7 +656,7 @@ class _NestedScrollViewCustomScrollView extends CustomScrollView {
@required ScrollController controller,
@required List<Widget> slivers,
@required this.handle,
@required this.clipBehavior,
@required Clip clipBehavior,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
String restorationId,
}) : super(
@ -667,10 +667,10 @@ class _NestedScrollViewCustomScrollView extends CustomScrollView {
slivers: slivers,
dragStartBehavior: dragStartBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
final SliverOverlapAbsorberHandle handle;
final Clip clipBehavior;
@override
Widget buildViewport(

View file

@ -587,7 +587,9 @@ class PageView extends StatefulWidget {
this.dragStartBehavior = DragStartBehavior.start,
this.allowImplicitScrolling = false,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
}) : assert(allowImplicitScrolling != null),
assert(clipBehavior != null),
controller = controller ?? _defaultPageController,
childrenDelegate = SliverChildListDelegate(children),
super(key: key);
@ -623,7 +625,9 @@ class PageView extends StatefulWidget {
this.dragStartBehavior = DragStartBehavior.start,
this.allowImplicitScrolling = false,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
}) : assert(allowImplicitScrolling != null),
assert(clipBehavior != null),
controller = controller ?? _defaultPageController,
childrenDelegate = SliverChildBuilderDelegate(itemBuilder, childCount: itemCount),
super(key: key);
@ -722,8 +726,10 @@ class PageView extends StatefulWidget {
this.dragStartBehavior = DragStartBehavior.start,
this.allowImplicitScrolling = false,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
}) : assert(childrenDelegate != null),
assert(allowImplicitScrolling != null),
assert(clipBehavior != null),
controller = controller ?? _defaultPageController,
super(key: key);
@ -794,6 +800,11 @@ class PageView extends StatefulWidget {
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;
@override
_PageViewState createState() => _PageViewState();
}
@ -856,6 +867,7 @@ class _PageViewState extends State<PageView> {
cacheExtentStyle: CacheExtentStyle.viewport,
axisDirection: axisDirection,
offset: position,
clipBehavior: widget.clipBehavior,
slivers: <Widget>[
SliverFillViewport(
viewportFraction: widget.controller.viewportFraction,

View file

@ -92,10 +92,12 @@ abstract class ScrollView extends StatelessWidget {
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
}) : assert(scrollDirection != null),
assert(reverse != null),
assert(shrinkWrap != null),
assert(dragStartBehavior != null),
assert(clipBehavior != null),
assert(!(controller != null && primary == true),
'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
'You cannot both set primary to true and pass an explicit controller.'
@ -264,6 +266,11 @@ abstract class ScrollView extends StatelessWidget {
/// {@macro flutter.widgets.scrollable.restorationId}
final String restorationId;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;
/// Returns the [AxisDirection] in which the scroll view scrolls.
///
/// Combines the [scrollDirection] with the [reverse] boolean to obtain the
@ -312,6 +319,7 @@ abstract class ScrollView extends StatelessWidget {
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
clipBehavior: clipBehavior,
);
}
return Viewport(
@ -321,6 +329,7 @@ abstract class ScrollView extends StatelessWidget {
cacheExtent: cacheExtent,
center: center,
anchor: anchor,
clipBehavior: clipBehavior,
);
}
@ -574,6 +583,7 @@ class CustomScrollView extends ScrollView {
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : super(
key: key,
scrollDirection: scrollDirection,
@ -588,6 +598,7 @@ class CustomScrollView extends ScrollView {
semanticChildCount: semanticChildCount,
dragStartBehavior: dragStartBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// The slivers to place inside the viewport.
@ -623,6 +634,7 @@ abstract class BoxScrollView extends ScrollView {
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : super(
key: key,
scrollDirection: scrollDirection,
@ -636,6 +648,7 @@ abstract class BoxScrollView extends ScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// The amount of space by which to inset the children.
@ -1042,6 +1055,7 @@ class ListView extends BoxScrollView {
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
@ -1062,6 +1076,7 @@ class ListView extends BoxScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// Creates a scrollable, linear array of widgets that are created on demand.
@ -1115,6 +1130,7 @@ class ListView extends BoxScrollView {
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : assert(itemCount == null || itemCount >= 0),
assert(semanticChildCount == null || semanticChildCount <= itemCount),
childrenDelegate = SliverChildBuilderDelegate(
@ -1138,6 +1154,7 @@ class ListView extends BoxScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// Creates a fixed-length scrollable linear array of list "items" separated
@ -1206,6 +1223,7 @@ class ListView extends BoxScrollView {
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : assert(itemBuilder != null),
assert(separatorBuilder != null),
assert(itemCount != null && itemCount >= 0),
@ -1249,6 +1267,7 @@ class ListView extends BoxScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// Creates a scrollable, linear array of widgets with a custom child model.
@ -1349,6 +1368,7 @@ class ListView extends BoxScrollView {
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : assert(childrenDelegate != null),
super(
key: key,
@ -1364,6 +1384,7 @@ class ListView extends BoxScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// If non-null, forces the children to have the given extent in the scroll
@ -1642,6 +1663,7 @@ class GridView extends BoxScrollView {
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
Clip clipBehavior = Clip.hardEdge,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
}) : assert(gridDelegate != null),
@ -1665,6 +1687,7 @@ class GridView extends BoxScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// Creates a scrollable, 2D array of widgets that are created on demand.
@ -1706,6 +1729,7 @@ class GridView extends BoxScrollView {
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : assert(gridDelegate != null),
childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
@ -1728,6 +1752,7 @@ class GridView extends BoxScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// Creates a scrollable, 2D array of widgets with both a custom
@ -1753,6 +1778,7 @@ class GridView extends BoxScrollView {
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : assert(gridDelegate != null),
assert(childrenDelegate != null),
super(
@ -1769,6 +1795,7 @@ class GridView extends BoxScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
@ -1807,6 +1834,7 @@ class GridView extends BoxScrollView {
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
@ -1833,6 +1861,7 @@ class GridView extends BoxScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// Creates a scrollable, 2D array of widgets with tiles that each have a
@ -1871,6 +1900,7 @@ class GridView extends BoxScrollView {
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String restorationId,
Clip clipBehavior = Clip.hardEdge,
}) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: maxCrossAxisExtent,
mainAxisSpacing: mainAxisSpacing,
@ -1897,6 +1927,7 @@ class GridView extends BoxScrollView {
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
restorationId: restorationId,
clipBehavior: clipBehavior,
);
/// A delegate that controls the layout of the children within the [GridView].

View file

@ -6,9 +6,11 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import '../rendering/mock_canvas.dart';
import '../rendering/rendering_tester.dart';
import 'states.dart';
void main() {
@ -602,4 +604,106 @@ void main() {
expect(find.byKey(const ValueKey<int>(4)), findsOneWidget);
expect(counters[4], 2);
});
testWidgets('GridView respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
children: <Widget>[Container(height: 2000.0)],
),
),
);
// 1st, check that the render object has received the default clip behavior.
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
// 2nd, check that the painting context has received the default clip behavior.
final TestClipPaintingContext context = TestClipPaintingContext();
renderObject.paint(context, Offset.zero);
expect(context.clipBehavior, equals(Clip.hardEdge));
// 3rd, pump a new widget to check that the render object can update its clip behavior.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
children: <Widget>[Container(height: 2000.0)],
clipBehavior: Clip.antiAlias,
),
),
);
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
// 4th, check that a non-default clip behavior can be sent to the painting context.
renderObject.paint(context, Offset.zero);
expect(context.clipBehavior, equals(Clip.antiAlias));
});
testWidgets('GridView.builder respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemCount: 10,
itemBuilder: (BuildContext _, int __) => Container(height: 2000.0),
clipBehavior: Clip.antiAlias,
),
),
);
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
});
testWidgets('GridView.custom respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GridView.custom(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
childrenDelegate: SliverChildBuilderDelegate(
(BuildContext context, int index) => Container(height: 2000.0),
childCount: 1,
),
clipBehavior: Clip.antiAlias,
),
),
);
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
});
testWidgets('GridView.count respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GridView.count(
crossAxisCount: 3,
children: <Widget>[Container(height: 2000.0)],
clipBehavior: Clip.antiAlias,
),
),
);
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
});
testWidgets('GridView.extent respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GridView.extent(
maxCrossAxisExtent: 1000,
children: <Widget>[Container(height: 2000.0)],
clipBehavior: Clip.antiAlias,
),
),
);
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
});
}

View file

@ -4,10 +4,13 @@
// @dart = 2.8
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import '../rendering/mock_canvas.dart';
import '../rendering/rendering_tester.dart';
class TestSliverChildListDelegate extends SliverChildListDelegate {
TestSliverChildListDelegate(List<Widget> children) : super(children);
@ -575,4 +578,88 @@ void main() {
await tester.pumpWidget(buildListView(scrollDirection: Axis.horizontal));
expect(controller.position.viewportDimension, 100.0);
});
testWidgets('ListView respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ListView(
children: <Widget>[Container(height: 2000.0)],
),
),
);
// 1st, check that the render object has received the default clip behavior.
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
// 2nd, check that the painting context has received the default clip behavior.
final TestClipPaintingContext context = TestClipPaintingContext();
renderObject.paint(context, Offset.zero);
expect(context.clipBehavior, equals(Clip.hardEdge));
// 3rd, pump a new widget to check that the render object can update its clip behavior.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ListView(
children: <Widget>[Container(height: 2000.0)],
clipBehavior: Clip.antiAlias,
),
),
);
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
// 4th, check that a non-default clip behavior can be sent to the painting context.
renderObject.paint(context, Offset.zero);
expect(context.clipBehavior, equals(Clip.antiAlias));
});
testWidgets('ListView.builder respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext _, int __) => Container(height: 2000.0),
clipBehavior: Clip.antiAlias,
),
),
);
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
});
testWidgets('ListView.custom respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ListView.custom(
childrenDelegate: SliverChildBuilderDelegate(
(BuildContext context, int index) => Container(height: 2000.0),
childCount: 1,
),
clipBehavior: Clip.antiAlias,
),
),
);
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
});
testWidgets('ListView.separated respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ListView.separated(
itemCount: 10,
itemBuilder: (BuildContext _, int __) => Container(height: 2000.0),
separatorBuilder: (BuildContext _, int __) => const Divider(),
clipBehavior: Clip.antiAlias,
),
),
);
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
});
}

View file

@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import '../rendering/rendering_tester.dart';
import 'semantics_tester.dart';
import 'states.dart';
@ -951,4 +952,40 @@ void main() {
semantics.dispose();
});
testWidgets('PageView respects clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: PageView(
children: <Widget>[Container(height: 2000.0)],
),
),
);
// 1st, check that the render object has received the default clip behavior.
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
// 2nd, check that the painting context has received the default clip behavior.
final TestClipPaintingContext context = TestClipPaintingContext();
renderObject.paint(context, Offset.zero);
expect(context.clipBehavior, equals(Clip.hardEdge));
// 3rd, pump a new widget to check that the render object can update its clip behavior.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: PageView(
children: <Widget>[Container(height: 2000.0)],
clipBehavior: Clip.antiAlias,
),
),
);
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
// 4th, check that a non-default clip behavior can be sent to the painting context.
renderObject.paint(context, Offset.zero);
expect(context.clipBehavior, equals(Clip.antiAlias));
});
}