Skip to content

Commit f15295c

Browse files
author
Jonah Williams
authored
[framework] allow disabling image filter layer (#102085)
1 parent 0411a6f commit f15295c

2 files changed

Lines changed: 63 additions & 4 deletions

File tree

packages/flutter/lib/src/widgets/image_filter.dart

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ import 'framework.dart';
1111

1212
/// Applies an [ImageFilter] to its child.
1313
///
14+
/// An image filter will always apply its filter operation to the child widget,
15+
/// even if said filter is conceptually a "no-op", such as an ImageFilter.blur
16+
/// with a radius of 0 or an ImageFilter.matrix with an identity matrix. Setting
17+
/// [ImageFiltered.enabled] to `false` is a more efficient manner of disabling
18+
/// an image filter.
19+
///
20+
/// The framework does not attempt to optimize out "no-op" filters because it
21+
/// cannot tell the difference between an intentional no-op and a filter that is
22+
/// only incidentally a no-op. Consider an ImageFilter.matrix that is animated
23+
/// and happens to pass through the identity matrix. If the framework identified it
24+
/// as a no-op it would drop and then recreate the layer during the animation which
25+
/// would be more expensive than keeping it around.
26+
///
1427
/// {@youtube 560 315 https://www.youtube.com/watch?v=7Lftorq4i2o}
1528
///
1629
/// See also:
@@ -27,17 +40,27 @@ class ImageFiltered extends SingleChildRenderObjectWidget {
2740
super.key,
2841
required this.imageFilter,
2942
super.child,
43+
this.enabled = true,
3044
}) : assert(imageFilter != null);
3145

3246
/// The image filter to apply to the child of this widget.
3347
final ImageFilter imageFilter;
3448

49+
/// Whether or not to apply the image filter opation to the child of this
50+
/// widget.
51+
///
52+
/// Prefer setting enabled to `false` instead of creating a "no-op" filter
53+
/// type for performance reasons.
54+
final bool enabled;
55+
3556
@override
36-
RenderObject createRenderObject(BuildContext context) => _ImageFilterRenderObject(imageFilter);
57+
RenderObject createRenderObject(BuildContext context) => _ImageFilterRenderObject(imageFilter, enabled);
3758

3859
@override
3960
void updateRenderObject(BuildContext context, RenderObject renderObject) {
40-
(renderObject as _ImageFilterRenderObject).imageFilter = imageFilter;
61+
(renderObject as _ImageFilterRenderObject)
62+
..enabled = enabled
63+
..imageFilter = imageFilter;
4164
}
4265

4366
@override
@@ -48,7 +71,17 @@ class ImageFiltered extends SingleChildRenderObjectWidget {
4871
}
4972

5073
class _ImageFilterRenderObject extends RenderProxyBox {
51-
_ImageFilterRenderObject(this._imageFilter);
74+
_ImageFilterRenderObject(this._imageFilter, this._enabled);
75+
76+
bool get enabled => _enabled;
77+
bool _enabled;
78+
set enabled(bool value) {
79+
if (enabled == value) {
80+
return;
81+
}
82+
_enabled = value;
83+
markNeedsPaint();
84+
}
5285

5386
ImageFilter get imageFilter => _imageFilter;
5487
ImageFilter _imageFilter;
@@ -61,11 +94,16 @@ class _ImageFilterRenderObject extends RenderProxyBox {
6194
}
6295

6396
@override
64-
bool get alwaysNeedsCompositing => child != null;
97+
bool get alwaysNeedsCompositing => child != null && enabled;
6598

6699
@override
67100
void paint(PaintingContext context, Offset offset) {
68101
assert(imageFilter != null);
102+
if (!enabled) {
103+
layer = null;
104+
return super.paint(context, offset);
105+
}
106+
69107
if (layer == null) {
70108
layer = ImageFilterLayer(imageFilter: imageFilter);
71109
} else {

packages/flutter/test/widgets/image_filter_test.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,25 @@ void main() {
8686
await pumpWithSigma(10.0);
8787
expect(renderObject.debugLayer, same(originalLayer));
8888
});
89+
90+
testWidgets('Image filter - enabled and disabled', (WidgetTester tester) async {
91+
Future<void> pumpWithEnabledStaet(bool enabled) async {
92+
await tester.pumpWidget(
93+
RepaintBoundary(
94+
child: ImageFiltered(
95+
enabled: enabled,
96+
imageFilter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
97+
child: const Placeholder(),
98+
),
99+
),
100+
);
101+
}
102+
103+
await pumpWithEnabledStaet(false);
104+
expect(tester.layers, isNot(contains(isA<ImageFilterLayer>())));
105+
106+
107+
await pumpWithEnabledStaet(true);
108+
expect(tester.layers, contains(isA<ImageFilterLayer>()));
109+
});
89110
}

0 commit comments

Comments
 (0)