From e065c7fea23bee63a1dea18febed7048091478ca Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Fri, 2 Dec 2022 11:48:00 -0800 Subject: [PATCH] [framework] make ImageFiltered a repaint boundary (#116385) * ++ * ++ * ++ --- .../flutter/lib/src/widgets/image_filter.dart | 32 ++++------- .../animated_image_filtered_repaint_test.dart | 57 +++++++++++++++++++ .../test/widgets/image_filter_test.dart | 8 ++- 3 files changed, 75 insertions(+), 22 deletions(-) create mode 100644 packages/flutter/test/widgets/animated_image_filtered_repaint_test.dart diff --git a/packages/flutter/lib/src/widgets/image_filter.dart b/packages/flutter/lib/src/widgets/image_filter.dart index 60e3c19df1be..4a4f007cfcc2 100644 --- a/packages/flutter/lib/src/widgets/image_filter.dart +++ b/packages/flutter/lib/src/widgets/image_filter.dart @@ -79,7 +79,11 @@ class _ImageFilterRenderObject extends RenderProxyBox { if (enabled == value) { return; } + final bool wasRepaintBoundary = isRepaintBoundary; _enabled = value; + if (isRepaintBoundary != wasRepaintBoundary) { + markNeedsCompositingBitsUpdate(); + } markNeedsPaint(); } @@ -89,32 +93,20 @@ class _ImageFilterRenderObject extends RenderProxyBox { assert(value != null); if (value != _imageFilter) { _imageFilter = value; - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); } } @override bool get alwaysNeedsCompositing => child != null && enabled; - @override - void paint(PaintingContext context, Offset offset) { - assert(imageFilter != null); - if (!enabled) { - layer = null; - return super.paint(context, offset); - } + @override + bool get isRepaintBoundary => alwaysNeedsCompositing; - if (layer == null) { - layer = ImageFilterLayer(imageFilter: imageFilter, offset: offset); - } else { - final ImageFilterLayer filterLayer = layer! as ImageFilterLayer; - filterLayer.imageFilter = imageFilter; - filterLayer.offset = offset; - } - context.pushLayer(layer!, super.paint, Offset.zero); - assert(() { - layer!.debugCreator = debugCreator; - return true; - }()); + @override + OffsetLayer updateCompositedLayer({required covariant ImageFilterLayer? oldLayer}) { + final ImageFilterLayer layer = oldLayer ?? ImageFilterLayer(); + layer.imageFilter = imageFilter; + return layer; } } diff --git a/packages/flutter/test/widgets/animated_image_filtered_repaint_test.dart b/packages/flutter/test/widgets/animated_image_filtered_repaint_test.dart new file mode 100644 index 000000000000..afeecc8ad716 --- /dev/null +++ b/packages/flutter/test/widgets/animated_image_filtered_repaint_test.dart @@ -0,0 +1,57 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('ImageFiltered avoids repainting child as it animates', (WidgetTester tester) async { + RenderTestObject.paintCount = 0; + await tester.pumpWidget( + Container( + color: Colors.red, + child: ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: const TestWidget(), + ), + ) + ); + + expect(RenderTestObject.paintCount, 1); + + await tester.pumpWidget( + Container( + color: Colors.red, + child: ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), + child: const TestWidget(), + ), + ) + ); + + expect(RenderTestObject.paintCount, 1); + }); +} + +class TestWidget extends SingleChildRenderObjectWidget { + const TestWidget({super.key, super.child}); + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderTestObject(); + } +} + +class RenderTestObject extends RenderProxyBox { + static int paintCount = 0; + + @override + void paint(PaintingContext context, Offset offset) { + paintCount += 1; + super.paint(context, offset); + } +} diff --git a/packages/flutter/test/widgets/image_filter_test.dart b/packages/flutter/test/widgets/image_filter_test.dart index fa982da337cf..157cddcb5285 100644 --- a/packages/flutter/test/widgets/image_filter_test.dart +++ b/packages/flutter/test/widgets/image_filter_test.dart @@ -31,8 +31,10 @@ void main() { }); testWidgets('Image filter - blur with offset', (WidgetTester tester) async { + final Key key = GlobalKey(); await tester.pumpWidget( RepaintBoundary( + key: key, child: Transform.translate( offset: const Offset(50, 50), child: ImageFiltered( @@ -43,7 +45,7 @@ void main() { ), ); await expectLater( - find.byType(ImageFiltered), + find.byKey(key), matchesGoldenFile('image_filter_blur_offset.png'), ); }); @@ -119,8 +121,10 @@ void main() { testWidgets('Image filter - matrix with offset', (WidgetTester tester) async { final Matrix4 matrix = Matrix4.rotationZ(pi / 18); final ImageFilter matrixFilter = ImageFilter.matrix(matrix.storage); + final Key key = GlobalKey(); await tester.pumpWidget( RepaintBoundary( + key: key, child: Transform.translate( offset: const Offset(50, 50), child: ImageFiltered( @@ -147,7 +151,7 @@ void main() { ), ); await expectLater( - find.byType(ImageFiltered), + find.byKey(key), matchesGoldenFile('image_filter_matrix_offset.png'), ); });