diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 676bfbf740..dd9bf7c6a0 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -1099,7 +1099,7 @@ class RenderBackdropFilter extends RenderProxyBox { /// * [ClipOval], which can be customized with a [CustomClipper]. /// * [ClipPath], which can be customized with a [CustomClipper]. /// * [ShapeBorderClipper], for specifying a clip path using a [ShapeBorder]. -abstract class CustomClipper { +abstract class CustomClipper extends Listenable { /// Creates a custom clipper. /// /// The clipper will update its clip whenever [reclip] notifies its listeners. @@ -1107,6 +1107,23 @@ abstract class CustomClipper { final Listenable _reclip; + /// Register a closure to be notified when it is time to reclip. + /// + /// The [CustomClipper] implementation merely forwards to the same method on + /// the [Listenable] provided to the constructor in the `reclip` argument, if + /// it was not null. + @override + void addListener(VoidCallback listener) => _reclip?.addListener(listener); + + /// Remove a previously registered closure from the list of closures that the + /// object notifies when it is time to reclip. + /// + /// The [CustomClipper] implementation merely forwards to the same method on + /// the [Listenable] provided to the constructor in the `reclip` argument, if + /// it was not null. + @override + void removeListener(VoidCallback listener) => _reclip?.removeListener(listener); + /// Returns a description of the clip given that the render object being /// clipped is of the given size. T getClip(Size size); @@ -1207,20 +1224,20 @@ abstract class _RenderCustomClip extends RenderProxyBox { _markNeedsClip(); } if (attached) { - oldClipper?._reclip?.removeListener(_markNeedsClip); - newClipper?._reclip?.addListener(_markNeedsClip); + oldClipper?.removeListener(_markNeedsClip); + newClipper?.addListener(_markNeedsClip); } } @override void attach(PipelineOwner owner) { super.attach(owner); - _clipper?._reclip?.addListener(_markNeedsClip); + _clipper?.addListener(_markNeedsClip); } @override void detach() { - _clipper?._reclip?.removeListener(_markNeedsClip); + _clipper?.removeListener(_markNeedsClip); super.detach(); } diff --git a/packages/flutter/test/widgets/clip_test.dart b/packages/flutter/test/widgets/clip_test.dart index 2136bb44af..1370318787 100644 --- a/packages/flutter/test/widgets/clip_test.dart +++ b/packages/flutter/test/widgets/clip_test.dart @@ -40,6 +40,18 @@ class ValueClipper extends CustomClipper { } } +class NotifyClipper extends CustomClipper { + NotifyClipper({this.clip}) : super(reclip: clip); + + final ValueNotifier clip; + + @override + T getClip(Size size) => clip.value; + + @override + bool shouldReclip(NotifyClipper oldClipper) => clip != oldClipper.clip; +} + class _UpdateCountedClipRect extends ClipRect { const _UpdateCountedClipRect({Clip clipBehavior = Clip.antiAlias}) : super(clipBehavior: clipBehavior); @@ -837,4 +849,37 @@ void main() { 'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr', ]); }); + + testWidgets('CustomClipper reclips when notified', (WidgetTester tester) async { + final ValueNotifier clip = ValueNotifier(const Rect.fromLTWH(50.0, 50.0, 100.0, 100.0)); + + await tester.pumpWidget( + ClipRect( + child: const Placeholder(), + clipper: NotifyClipper(clip: clip), + ), + ); + + expect(tester.renderObject(find.byType(ClipRect)).paint, paints + ..save() + ..clipRect(rect: const Rect.fromLTWH(50.0, 50.0, 100.0, 100.0)) + ..save() + ..path() // Placeholder + ..restore() + ..restore(), + ); + + expect(tester.renderObject(find.byType(ClipRect)).debugNeedsPaint, isFalse); + clip.value = const Rect.fromLTWH(50.0, 50.0, 150.0, 100.0); + expect(tester.renderObject(find.byType(ClipRect)).debugNeedsPaint, isTrue); + + expect(tester.renderObject(find.byType(ClipRect)).paint, paints + ..save() + ..clipRect(rect: const Rect.fromLTWH(50.0, 50.0, 150.0, 100.0)) + ..save() + ..path() // Placeholder + ..restore() + ..restore(), + ); + }); }