Unverified Commit 8e8a1c8c authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Fix `StretchingOverscrollIndicator` clipping and add `clipBehavior` parameter (#105303)

parent 3f401a19
...@@ -819,6 +819,7 @@ class MaterialScrollBehavior extends ScrollBehavior { ...@@ -819,6 +819,7 @@ class MaterialScrollBehavior extends ScrollBehavior {
case AndroidOverscrollIndicator.stretch: case AndroidOverscrollIndicator.stretch:
return StretchingOverscrollIndicator( return StretchingOverscrollIndicator(
axisDirection: details.direction, axisDirection: details.direction,
clipBehavior: details.clipBehavior,
child: child, child: child,
); );
case AndroidOverscrollIndicator.glow: case AndroidOverscrollIndicator.glow:
......
...@@ -653,9 +653,11 @@ class StretchingOverscrollIndicator extends StatefulWidget { ...@@ -653,9 +653,11 @@ class StretchingOverscrollIndicator extends StatefulWidget {
super.key, super.key,
required this.axisDirection, required this.axisDirection,
this.notificationPredicate = defaultScrollNotificationPredicate, this.notificationPredicate = defaultScrollNotificationPredicate,
this.clipBehavior = Clip.hardEdge,
this.child, this.child,
}) : assert(axisDirection != null), }) : assert(axisDirection != null),
assert(notificationPredicate != null); assert(notificationPredicate != null),
assert(clipBehavior != null);
/// {@macro flutter.overscroll.axisDirection} /// {@macro flutter.overscroll.axisDirection}
final AxisDirection axisDirection; final AxisDirection axisDirection;
...@@ -666,6 +668,11 @@ class StretchingOverscrollIndicator extends StatefulWidget { ...@@ -666,6 +668,11 @@ class StretchingOverscrollIndicator extends StatefulWidget {
/// {@macro flutter.overscroll.notificationPredicate} /// {@macro flutter.overscroll.notificationPredicate}
final ScrollNotificationPredicate notificationPredicate; final ScrollNotificationPredicate notificationPredicate;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
/// ///
/// The overscroll indicator will apply a stretch effect to this child. This /// The overscroll indicator will apply a stretch effect to this child. This
...@@ -806,7 +813,8 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi ...@@ -806,7 +813,8 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi
// screen, overflow from transforming the viewport is irrelevant. // screen, overflow from transforming the viewport is irrelevant.
return ClipRect( return ClipRect(
clipBehavior: stretch != 0.0 && viewportDimension != mainAxisSize clipBehavior: stretch != 0.0 && viewportDimension != mainAxisSize
? Clip.hardEdge : Clip.none, ? widget.clipBehavior
: Clip.none,
child: transform, child: transform,
); );
}, },
......
...@@ -425,6 +425,7 @@ abstract class ScrollView extends StatelessWidget { ...@@ -425,6 +425,7 @@ abstract class ScrollView extends StatelessWidget {
viewportBuilder: (BuildContext context, ViewportOffset offset) { viewportBuilder: (BuildContext context, ViewportOffset offset) {
return buildViewport(context, offset, axisDirection, slivers); return buildViewport(context, offset, axisDirection, slivers);
}, },
clipBehavior: clipBehavior,
); );
final Widget scrollableResult = effectivePrimary && scrollController != null final Widget scrollableResult = effectivePrimary && scrollController != null
......
...@@ -97,6 +97,7 @@ class Scrollable extends StatefulWidget { ...@@ -97,6 +97,7 @@ class Scrollable extends StatefulWidget {
this.dragStartBehavior = DragStartBehavior.start, this.dragStartBehavior = DragStartBehavior.start,
this.restorationId, this.restorationId,
this.scrollBehavior, this.scrollBehavior,
this.clipBehavior = Clip.hardEdge,
}) : assert(axisDirection != null), }) : assert(axisDirection != null),
assert(dragStartBehavior != null), assert(dragStartBehavior != null),
assert(viewportBuilder != null), assert(viewportBuilder != null),
...@@ -261,6 +262,14 @@ class Scrollable extends StatefulWidget { ...@@ -261,6 +262,14 @@ class Scrollable extends StatefulWidget {
/// [ScrollBehavior]. /// [ScrollBehavior].
final ScrollBehavior? scrollBehavior; final ScrollBehavior? scrollBehavior;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
///
/// Rather than clipping [Scrollable], this is passed to decorators in
/// [ScrollableDetails].
final Clip clipBehavior;
/// The axis along which the scroll view scrolls. /// The axis along which the scroll view scrolls.
/// ///
/// Determined by the [axisDirection]. /// Determined by the [axisDirection].
...@@ -797,6 +806,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R ...@@ -797,6 +806,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
final ScrollableDetails details = ScrollableDetails( final ScrollableDetails details = ScrollableDetails(
direction: widget.axisDirection, direction: widget.axisDirection,
controller: _effectiveScrollController, controller: _effectiveScrollController,
clipBehavior: widget.clipBehavior,
); );
result = _configuration.buildScrollbar( result = _configuration.buildScrollbar(
...@@ -812,7 +822,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R ...@@ -812,7 +822,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
state: this, state: this,
position: position, position: position,
registrar: registrar, registrar: registrar,
child: result child: result,
); );
} }
...@@ -1313,6 +1323,7 @@ class ScrollableDetails { ...@@ -1313,6 +1323,7 @@ class ScrollableDetails {
const ScrollableDetails({ const ScrollableDetails({
required this.direction, required this.direction,
required this.controller, required this.controller,
required this.clipBehavior,
}); });
/// The direction in which this widget scrolls. /// The direction in which this widget scrolls.
...@@ -1326,6 +1337,13 @@ class ScrollableDetails { ...@@ -1326,6 +1337,13 @@ class ScrollableDetails {
/// This can be used by [ScrollBehavior] to apply a [Scrollbar] to the associated /// This can be used by [ScrollBehavior] to apply a [Scrollbar] to the associated
/// [Scrollable]. /// [Scrollable].
final ScrollController controller; final ScrollController controller;
/// {@macro flutter.material.Material.clipBehavior}
///
/// This can be used by [MaterialScrollBehavior] to clip [StretchingOverscrollIndicator].
///
/// Cannot be null.
final Clip clipBehavior;
} }
/// With [_ScrollSemantics] certain child [SemanticsNode]s can be /// With [_ScrollSemantics] certain child [SemanticsNode]s can be
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -1265,6 +1266,83 @@ void main() { ...@@ -1265,6 +1266,83 @@ void main() {
expect(find.byType(GlowingOverscrollIndicator), findsNothing); expect(find.byType(GlowingOverscrollIndicator), findsNothing);
}, variant: TargetPlatformVariant.only(TargetPlatform.android)); }, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets(
'ListView clip behavior updates overscroll indicator clip behavior', (WidgetTester tester) async {
Widget buildFrame(Clip clipBehavior) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: Column(
children: <Widget>[
SizedBox(
height: 300,
child: ListView.builder(
itemCount: 20,
clipBehavior: clipBehavior,
itemBuilder: (BuildContext context, int index){
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Index $index'),
);
},
),
),
Opacity(
opacity: 0.5,
child: Container(
color: const Color(0xD0FF0000),
height: 100,
),
),
],
),
);
}
// Test default clip behavior.
await tester.pumpWidget(buildFrame(Clip.hardEdge));
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
expect(find.text('Index 1'), findsOneWidget);
RenderClipRect renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Currently not clipping
expect(renderClip.clipBehavior, equals(Clip.none));
TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Overscroll the start.
await gesture.moveBy(const Offset(0.0, 200.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0));
renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Now clipping
expect(renderClip.clipBehavior, equals(Clip.hardEdge));
await gesture.up();
await tester.pumpAndSettle();
// Test custom clip behavior.
await tester.pumpWidget(buildFrame(Clip.none));
renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Currently not clipping
expect(renderClip.clipBehavior, equals(Clip.none));
gesture = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Overscroll the start.
await gesture.moveBy(const Offset(0.0, 200.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0));
renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Now clipping
expect(renderClip.clipBehavior, equals(Clip.none));
await gesture.up();
await tester.pumpAndSettle();
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('When `useInheritedMediaQuery` is true an existing MediaQuery is used if one is available', (WidgetTester tester) async { testWidgets('When `useInheritedMediaQuery` is true an existing MediaQuery is used if one is available', (WidgetTester tester) async {
late BuildContext capturedContext; late BuildContext capturedContext;
final UniqueKey uniqueKey = UniqueKey(); final UniqueKey uniqueKey = UniqueKey();
......
...@@ -454,6 +454,70 @@ void main() { ...@@ -454,6 +454,70 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
}); });
testWidgets('clipBehavior parameter updates overscroll clipping behavior', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/103491
Widget buildFrame(Clip clipBehavior) {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(size: Size(800.0, 600.0)),
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(overscroll: false),
child: Column(
children: <Widget>[
StretchingOverscrollIndicator(
axisDirection: AxisDirection.down,
clipBehavior: clipBehavior,
child: SizedBox(
height: 300,
child: ListView.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index){
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Index $index'),
);
},
),
),
),
Opacity(
opacity: 0.5,
child: Container(
color: const Color(0xD0FF0000),
height: 100,
),
),
],
),
),
),
);
}
await tester.pumpWidget(buildFrame(Clip.none));
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, 51.0);
RenderClipRect renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Currently not clipping
expect(renderClip.clipBehavior, equals(Clip.none));
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Overscroll the start.
await gesture.moveBy(const Offset(0.0, 200.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0));
renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Now clipping
expect(renderClip.clipBehavior, equals(Clip.none));
await gesture.up();
await tester.pumpAndSettle();
});
testWidgets('Stretch limit', (WidgetTester tester) async { testWidgets('Stretch limit', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/99264 // Regression test for https://github.com/flutter/flutter/issues/99264
await tester.pumpWidget( await tester.pumpWidget(
......
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