From e0ac5da1647aed8e4113d80d11b663fa161ad5cd Mon Sep 17 00:00:00 2001 From: TheBiirb <67576036+TheBiirb@users.noreply.github.com> Date: Fri, 18 Sep 2020 01:22:10 +0300 Subject: [PATCH] Added clipBehavior to Overlay, Flow, AnimatedSize and AndroidView (#65910) --- .../lib/src/rendering/animated_size.dart | 21 ++++++- packages/flutter/lib/src/rendering/flow.dart | 25 +++++++- .../lib/src/rendering/platform_view.dart | 23 +++++++- .../lib/src/widgets/animated_size.dart | 13 ++++- packages/flutter/lib/src/widgets/basic.dart | 12 +++- packages/flutter/lib/src/widgets/overlay.dart | 39 +++++++++++-- .../lib/src/widgets/platform_view.dart | 13 +++++ .../test/widgets/animated_size_test.dart | 36 ++++++++++++ packages/flutter/test/widgets/flow_test.dart | 58 +++++++++++++++++++ .../flutter/test/widgets/overlay_test.dart | 38 +++++++++++- .../test/widgets/platform_view_test.dart | 46 +++++++++++++++ 11 files changed, 309 insertions(+), 15 deletions(-) diff --git a/packages/flutter/lib/src/rendering/animated_size.dart b/packages/flutter/lib/src/rendering/animated_size.dart index 65cd4afbf9..4a69949448 100644 --- a/packages/flutter/lib/src/rendering/animated_size.dart +++ b/packages/flutter/lib/src/rendering/animated_size.dart @@ -81,10 +81,13 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { AlignmentGeometry alignment = Alignment.center, TextDirection? textDirection, RenderBox? child, + Clip clipBehavior = Clip.hardEdge, }) : assert(vsync != null), assert(duration != null), assert(curve != null), + assert(clipBehavior != null), _vsync = vsync, + _clipBehavior = clipBehavior, super(child: child, alignment: alignment, textDirection: textDirection) { _controller = AnimationController( vsync: vsync, @@ -139,6 +142,20 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { _animation.curve = value; } + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + /// Whether the size is being currently animated towards the child's size. /// /// See [RenderAnimatedSizeState] for situations when we may not be animating @@ -275,9 +292,9 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { @override void paint(PaintingContext context, Offset offset) { - if (child != null && _hasVisualOverflow) { + if (child != null && _hasVisualOverflow && clipBehavior != Clip.none) { final Rect rect = Offset.zero & size; - context.pushClipRect(needsCompositing, offset, rect, super.paint); + context.pushClipRect(needsCompositing, offset, rect, super.paint, clipBehavior: clipBehavior); } else { super.paint(context, offset); } diff --git a/packages/flutter/lib/src/rendering/flow.dart b/packages/flutter/lib/src/rendering/flow.dart index 2cc8dd9366..f25d0ce9a8 100644 --- a/packages/flutter/lib/src/rendering/flow.dart +++ b/packages/flutter/lib/src/rendering/flow.dart @@ -181,8 +181,11 @@ class RenderFlow extends RenderBox RenderFlow({ List? children, required FlowDelegate delegate, + Clip clipBehavior = Clip.hardEdge, }) : assert(delegate != null), - _delegate = delegate { + assert(clipBehavior != null), + _delegate = delegate, + _clipBehavior = clipBehavior { addAll(children); } @@ -221,6 +224,20 @@ class RenderFlow extends RenderBox } } + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + @override void attach(PipelineOwner owner) { super.attach(owner); @@ -365,7 +382,11 @@ class RenderFlow extends RenderBox @override void paint(PaintingContext context, Offset offset) { - context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintWithDelegate); + if (clipBehavior == Clip.none) { + _paintWithDelegate(context, offset); + } else { + context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintWithDelegate, clipBehavior: clipBehavior); + } } @override diff --git a/packages/flutter/lib/src/rendering/platform_view.dart b/packages/flutter/lib/src/rendering/platform_view.dart index 857c185cee..667df604b1 100644 --- a/packages/flutter/lib/src/rendering/platform_view.dart +++ b/packages/flutter/lib/src/rendering/platform_view.dart @@ -83,10 +83,13 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin { required AndroidViewController viewController, required PlatformViewHitTestBehavior hitTestBehavior, required Set> gestureRecognizers, + Clip clipBehavior = Clip.hardEdge, }) : assert(viewController != null), assert(hitTestBehavior != null), assert(gestureRecognizers != null), - _viewController = viewController { + assert(clipBehavior != null), + _viewController = viewController, + _clipBehavior = clipBehavior { _viewController.pointTransformer = (Offset offset) => globalToLocal(offset); updateGestureRecognizers(gestureRecognizers); _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated); @@ -115,6 +118,20 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin { _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated); } + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + void _onPlatformViewCreated(int id) { markNeedsSemanticsUpdate(); } @@ -188,8 +205,8 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin { // Clip the texture if it's going to paint out of the bounds of the renter box // (see comment in _paintTexture for an explanation of when this happens). - if (size.width < _currentAndroidViewSize.width || size.height < _currentAndroidViewSize.height) { - context.pushClipRect(true, offset, offset & size, _paintTexture); + if (size.width < _currentAndroidViewSize.width || size.height < _currentAndroidViewSize.height && clipBehavior != Clip.none) { + context.pushClipRect(true, offset, offset & size, _paintTexture, clipBehavior: clipBehavior); return; } diff --git a/packages/flutter/lib/src/widgets/animated_size.dart b/packages/flutter/lib/src/widgets/animated_size.dart index 0e8ed33c4d..fe9ce552eb 100644 --- a/packages/flutter/lib/src/widgets/animated_size.dart +++ b/packages/flutter/lib/src/widgets/animated_size.dart @@ -61,7 +61,9 @@ class AnimatedSize extends SingleChildRenderObjectWidget { required this.duration, this.reverseDuration, required this.vsync, - }) : super(key: key, child: child); + this.clipBehavior = Clip.hardEdge, + }) : assert(clipBehavior != null), + super(key: key, child: child); /// The alignment of the child within the parent when the parent is not yet /// the same size as the child. @@ -101,6 +103,11 @@ class AnimatedSize extends SingleChildRenderObjectWidget { /// The [TickerProvider] for this widget. final TickerProvider vsync; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + final Clip clipBehavior; + @override RenderAnimatedSize createRenderObject(BuildContext context) { return RenderAnimatedSize( @@ -110,6 +117,7 @@ class AnimatedSize extends SingleChildRenderObjectWidget { curve: curve, vsync: vsync, textDirection: Directionality.of(context), + clipBehavior: clipBehavior, ); } @@ -121,7 +129,8 @@ class AnimatedSize extends SingleChildRenderObjectWidget { ..reverseDuration = reverseDuration ..curve = curve ..vsync = vsync - ..textDirection = Directionality.of(context); + ..textDirection = Directionality.of(context) + ..clipBehavior = clipBehavior; } @override diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 80ad1c551d..4ec3039d11 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -5091,7 +5091,9 @@ class Flow extends MultiChildRenderObjectWidget { Key? key, required this.delegate, List children = const [], + this.clipBehavior = Clip.hardEdge, }) : assert(delegate != null), + assert(clipBehavior != null), super(key: key, children: RepaintBoundary.wrapAll(children)); // https://github.com/dart-lang/sdk/issues/29277 @@ -5106,18 +5108,26 @@ class Flow extends MultiChildRenderObjectWidget { Key? key, required this.delegate, List children = const [], + this.clipBehavior = Clip.hardEdge, }) : assert(delegate != null), + assert(clipBehavior != null), super(key: key, children: children); /// The delegate that controls the transformation matrices of the children. final FlowDelegate delegate; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.none], and must not be null. + final Clip clipBehavior; + @override - RenderFlow createRenderObject(BuildContext context) => RenderFlow(delegate: delegate); + RenderFlow createRenderObject(BuildContext context) => RenderFlow(delegate: delegate, clipBehavior: clipBehavior); @override void updateRenderObject(BuildContext context, RenderFlow renderObject) { renderObject.delegate = delegate; + renderObject.clipBehavior = clipBehavior; } } diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index 5641227981..05e705ee48 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -213,7 +213,9 @@ class Overlay extends StatefulWidget { const Overlay({ Key? key, this.initialEntries = const [], + this.clipBehavior = Clip.hardEdge, }) : assert(initialEntries != null), + assert(clipBehavior != null), super(key: key); /// The entries to include in the overlay initially. @@ -231,6 +233,11 @@ class Overlay extends StatefulWidget { /// To remove an entry from an [Overlay], use [OverlayEntry.remove]. final List initialEntries; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + final Clip clipBehavior; + /// The state from the closest instance of this class that encloses the given context. /// /// In debug mode, if the `debugRequiredFor` argument is provided then this @@ -470,6 +477,7 @@ class OverlayState extends State with TickerProviderStateMixin { return _Theatre( skipCount: children.length - onstageCount, children: children.reversed.toList(growable: false), + clipBehavior: widget.clipBehavior, ); } @@ -490,15 +498,19 @@ class _Theatre extends MultiChildRenderObjectWidget { _Theatre({ Key? key, this.skipCount = 0, + this.clipBehavior = Clip.hardEdge, List children = const [], }) : assert(skipCount != null), assert(skipCount >= 0), assert(children != null), assert(children.length >= skipCount), + assert(clipBehavior != null), super(key: key, children: children); final int skipCount; + final Clip clipBehavior; + @override _TheatreElement createElement() => _TheatreElement(this); @@ -507,6 +519,7 @@ class _Theatre extends MultiChildRenderObjectWidget { return _RenderTheatre( skipCount: skipCount, textDirection: Directionality.of(context)!, + clipBehavior: clipBehavior, ); } @@ -514,7 +527,8 @@ class _Theatre extends MultiChildRenderObjectWidget { void updateRenderObject(BuildContext context, _RenderTheatre renderObject) { renderObject ..skipCount = skipCount - ..textDirection = Directionality.of(context)!; + ..textDirection = Directionality.of(context)! + ..clipBehavior = clipBehavior; } @override @@ -545,11 +559,14 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin? children, required TextDirection textDirection, int skipCount = 0, + Clip clipBehavior = Clip.hardEdge, }) : assert(skipCount != null), assert(skipCount >= 0), assert(textDirection != null), + assert(clipBehavior != null), _textDirection = textDirection, - _skipCount = skipCount { + _skipCount = skipCount, + _clipBehavior = clipBehavior { addAll(children); } @@ -593,6 +610,20 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + RenderBox? get _firstOnstageChild { if (skipCount == super.childCount) { return null; @@ -724,8 +755,8 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin? creationParamsCodec; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + final Clip clipBehavior; + @override State createState() => _AndroidViewState(); } @@ -434,6 +441,7 @@ class _AndroidViewState extends State { controller: _controller, hitTestBehavior: widget.hitTestBehavior, gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet, + clipBehavior: widget.clipBehavior, ), ); } @@ -643,14 +651,17 @@ class _AndroidPlatformView extends LeafRenderObjectWidget { required this.controller, required this.hitTestBehavior, required this.gestureRecognizers, + this.clipBehavior = Clip.hardEdge, }) : assert(controller != null), assert(hitTestBehavior != null), assert(gestureRecognizers != null), + assert(clipBehavior != null), super(key: key); final AndroidViewController controller; final PlatformViewHitTestBehavior hitTestBehavior; final Set> gestureRecognizers; + final Clip clipBehavior; @override RenderObject createRenderObject(BuildContext context) => @@ -658,6 +669,7 @@ class _AndroidPlatformView extends LeafRenderObjectWidget { viewController: controller, hitTestBehavior: hitTestBehavior, gestureRecognizers: gestureRecognizers, + clipBehavior: clipBehavior, ); @override @@ -665,6 +677,7 @@ class _AndroidPlatformView extends LeafRenderObjectWidget { renderObject.viewController = controller; renderObject.hitTestBehavior = hitTestBehavior; renderObject.updateGestureRecognizers(gestureRecognizers); + renderObject.clipBehavior = clipBehavior; } } diff --git a/packages/flutter/test/widgets/animated_size_test.dart b/packages/flutter/test/widgets/animated_size_test.dart index dd61ddc075..17a2879ae5 100644 --- a/packages/flutter/test/widgets/animated_size_test.dart +++ b/packages/flutter/test/widgets/animated_size_test.dart @@ -281,5 +281,41 @@ void main() { await tester.pump(const Duration(milliseconds: 10)); } }); + + testWidgets('can set and update clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget( + Center( + child: AnimatedSize( + duration: const Duration(milliseconds: 200), + vsync: tester, + child: const SizedBox( + width: 100.0, + height: 100.0, + ), + ), + ), + ); + + // By default, clipBehavior should be Clip.hardEdge + final RenderAnimatedSize renderObject = tester.renderObject(find.byType(AnimatedSize)); + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + for(final Clip clip in Clip.values) { + await tester.pumpWidget( + Center( + child: AnimatedSize( + duration: const Duration(milliseconds: 200), + vsync: tester, + clipBehavior: clip, + child: const SizedBox( + width: 100.0, + height: 100.0, + ), + ), + ), + ); + expect(renderObject.clipBehavior, clip); + } + }); }); } diff --git a/packages/flutter/test/widgets/flow_test.dart b/packages/flutter/test/widgets/flow_test.dart index d91724cf2b..6c8cca4046 100644 --- a/packages/flutter/test/widgets/flow_test.dart +++ b/packages/flutter/test/widgets/flow_test.dart @@ -157,4 +157,62 @@ void main() { expect(opacityLayer.alpha, equals(opacity * 255)); expect(layer.firstChild, isA()); }); + + testWidgets('Flow can set and update clipBehavior', (WidgetTester tester) async { + const double opacity = 0.2; + await tester.pumpWidget( + Flow( + delegate: OpacityFlowDelegate(opacity), + children: const [ + SizedBox(width: 100.0, height: 100.0), + ], + ), + ); + + // By default, clipBehavior should be Clip.hardEdge + final RenderFlow renderObject = tester.renderObject(find.byType(Flow)); + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + for(final Clip clip in Clip.values) { + await tester.pumpWidget( + Flow( + delegate: OpacityFlowDelegate(opacity), + children: const [ + SizedBox(width: 100.0, height: 100.0), + ], + clipBehavior: clip, + ), + ); + expect(renderObject.clipBehavior, clip); + } + }); + + testWidgets('Flow.unwrapped can set and update clipBehavior', (WidgetTester tester) async { + const double opacity = 0.2; + await tester.pumpWidget( + Flow.unwrapped( + delegate: OpacityFlowDelegate(opacity), + children: const [ + SizedBox(width: 100.0, height: 100.0), + ], + ), + ); + + // By default, clipBehavior should be Clip.hardEdge + final RenderFlow renderObject = tester.renderObject(find.byType(Flow)); + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + for(final Clip clip in Clip.values) { + await tester.pumpWidget( + Flow.unwrapped( + delegate: OpacityFlowDelegate(opacity), + children: const [ + SizedBox(width: 100.0, height: 100.0), + ], + clipBehavior: clip, + ), + ); + expect(renderObject.clipBehavior, clip); + } + }); } diff --git a/packages/flutter/test/widgets/overlay_test.dart b/packages/flutter/test/widgets/overlay_test.dart index 874e000073..c29fefd8b0 100644 --- a/packages/flutter/test/widgets/overlay_test.dart +++ b/packages/flutter/test/widgets/overlay_test.dart @@ -1002,7 +1002,7 @@ void main() { semantics.dispose(); }); - testWidgets('Can used Positioned within OverlayEntry', (WidgetTester tester) async { + testWidgets('Can use Positioned within OverlayEntry', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -1024,6 +1024,42 @@ void main() { expect(tester.getTopLeft(find.text('positioned child')), const Offset(145, 123)); }); + + testWidgets('Overlay can set and update clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: [ + OverlayEntry( + builder: (BuildContext context) => Container(), + ), + ], + ), + ), + ); + + // By default, clipBehavior should be Clip.hardEdge + final dynamic renderObject = tester.renderObject(find.byType(Overlay)); + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + for(final Clip clip in Clip.values) { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: [ + OverlayEntry( + builder: (BuildContext context) => Container(), + ), + ], + clipBehavior: clip, + ), + ), + ); + expect(renderObject.clipBehavior, clip); + } + }); } class StatefulTestWidget extends StatefulWidget { diff --git a/packages/flutter/test/widgets/platform_view_test.dart b/packages/flutter/test/widgets/platform_view_test.dart index 252eed0307..4850d7e648 100644 --- a/packages/flutter/test/widgets/platform_view_test.dart +++ b/packages/flutter/test/widgets/platform_view_test.dart @@ -1103,6 +1103,52 @@ void main() { expect(viewsController.lastClearedFocusViewId, currentViewId + 1); }); + + testWidgets('can set and update clipBehavior', (WidgetTester tester) async { + final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController(); + viewsController.registerViewType('webview'); + + await tester.pumpWidget( + const Center( + child: SizedBox( + width: 200.0, + height: 100.0, + child: AndroidView( + viewType: 'webview', + layoutDirection: TextDirection.ltr, + ), + ), + ), + ); + + // By default, clipBehavior should be Clip.hardEdge + final RenderAndroidView renderObject = tester.renderObject( + find.descendant( + of: find.byType(AndroidView), + matching: find.byWidgetPredicate( + (Widget widget) => widget.runtimeType.toString() == '_AndroidPlatformView', + ), + ), + ); + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + for(final Clip clip in Clip.values) { + await tester.pumpWidget( + Center( + child: SizedBox( + width: 200.0, + height: 100.0, + child: AndroidView( + viewType: 'webview', + layoutDirection: TextDirection.ltr, + clipBehavior: clip, + ), + ), + ), + ); + expect(renderObject.clipBehavior, clip); + } + }); }); group('AndroidViewSurface', () {