From c12b0de00416c19d26b4a795c693a89a81e772e2 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 21 Jun 2022 11:05:24 -0700 Subject: [PATCH] remove opacity layer at fully opaque (#106351) --- .../flutter/lib/src/rendering/proxy_box.dart | 3 +- .../test/rendering/proxy_box_test.dart | 16 +++++++- .../test/rendering/proxy_sliver_test.dart | 27 +++++++++++++- .../animated_opacity_repaint_test.dart | 37 ++++++++++++++++++- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index a4a0e23170a..2af1c06238b 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -887,6 +887,7 @@ class RenderOpacity extends RenderProxyBox { @override OffsetLayer updateCompositedLayer({required covariant OpacityLayer? oldLayer}) { + assert(_alpha != 255); final OpacityLayer updatedLayer = oldLayer ?? OpacityLayer(); updatedLayer.alpha = _alpha; return updatedLayer; @@ -1060,7 +1061,7 @@ mixin RenderAnimatedOpacityMixin on RenderObjectWithChil _alpha = ui.Color.getAlphaFromOpacity(opacity.value); if (oldAlpha != _alpha) { final bool? wasRepaintBoundary = _currentlyIsRepaintBoundary; - _currentlyIsRepaintBoundary = _alpha! > 0; + _currentlyIsRepaintBoundary = _alpha! > 0 && _alpha! < 255; if (child != null && wasRepaintBoundary != _currentlyIsRepaintBoundary) { markNeedsCompositingBitsUpdate(); } diff --git a/packages/flutter/test/rendering/proxy_box_test.dart b/packages/flutter/test/rendering/proxy_box_test.dart index 25498b52237..3077057e3e2 100644 --- a/packages/flutter/test/rendering/proxy_box_test.dart +++ b/packages/flutter/test/rendering/proxy_box_test.dart @@ -272,7 +272,7 @@ void main() { expect(renderAnimatedOpacity.needsCompositing, false); }); - test('RenderAnimatedOpacity does composite if it is opaque', () { + test('RenderAnimatedOpacity does not composite if it is opaque', () { final Animation opacityAnimation = AnimationController( vsync: FakeTickerProvider(), )..value = 1.0; @@ -282,6 +282,20 @@ void main() { child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter ); + layout(renderAnimatedOpacity, phase: EnginePhase.composite); + expect(renderAnimatedOpacity.needsCompositing, false); + }); + + test('RenderAnimatedOpacity does composite if it is partially opaque', () { + final Animation opacityAnimation = AnimationController( + vsync: FakeTickerProvider(), + )..value = 0.5; + + final RenderAnimatedOpacity renderAnimatedOpacity = RenderAnimatedOpacity( + opacity: opacityAnimation, + child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter + ); + layout(renderAnimatedOpacity, phase: EnginePhase.composite); expect(renderAnimatedOpacity.needsCompositing, true); }); diff --git a/packages/flutter/test/rendering/proxy_sliver_test.dart b/packages/flutter/test/rendering/proxy_sliver_test.dart index 83320b3acf1..c4c7e1ddaf3 100644 --- a/packages/flutter/test/rendering/proxy_sliver_test.dart +++ b/packages/flutter/test/rendering/proxy_sliver_test.dart @@ -98,7 +98,30 @@ void main() { expect(renderSliverAnimatedOpacity.needsCompositing, false); }); - test('RenderSliverAnimatedOpacity does composite if it is opaque', () { + test('RenderSliverAnimatedOpacity does composite if it is partially opaque', () { + final Animation opacityAnimation = AnimationController( + vsync: FakeTickerProvider(), + )..value = 0.5; + + final RenderSliverAnimatedOpacity renderSliverAnimatedOpacity = RenderSliverAnimatedOpacity( + opacity: opacityAnimation, + sliver: RenderSliverToBoxAdapter( + child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter + ), + ); + + final RenderViewport root = RenderViewport( + crossAxisDirection: AxisDirection.right, + offset: ViewportOffset.zero(), + cacheExtent: 250.0, + children: [renderSliverAnimatedOpacity], + ); + + layout(root, phase: EnginePhase.composite); + expect(renderSliverAnimatedOpacity.needsCompositing, true); + }); + + test('RenderSliverAnimatedOpacity does not composite if it is opaque', () { final Animation opacityAnimation = AnimationController( vsync: FakeTickerProvider(), )..value = 1.0; @@ -118,7 +141,7 @@ void main() { ); layout(root, phase: EnginePhase.composite); - expect(renderSliverAnimatedOpacity.needsCompositing, true); + expect(renderSliverAnimatedOpacity.needsCompositing, false); }); test('RenderSliverAnimatedOpacity reuses its layer', () { diff --git a/packages/flutter/test/widgets/animated_opacity_repaint_test.dart b/packages/flutter/test/widgets/animated_opacity_repaint_test.dart index 664a118df25..555ee521951 100644 --- a/packages/flutter/test/widgets/animated_opacity_repaint_test.dart +++ b/packages/flutter/test/widgets/animated_opacity_repaint_test.dart @@ -7,7 +7,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('RenderAnimatedOpacityMixin avoids repainting child as it animates', (WidgetTester tester) async { + testWidgets('RenderAnimatedOpacityMixin drops layer when animating to 1', (WidgetTester tester) async { RenderTestObject.paintCount = 0; final AnimationController controller = AnimationController(vsync: const TestVSync(), duration: const Duration(seconds: 1)); final Tween opacityTween = Tween(begin: 0, end: 1); @@ -32,6 +32,39 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); + expect(RenderTestObject.paintCount, 2); + + controller.stop(); + await tester.pump(); + + expect(RenderTestObject.paintCount, 2); + }); + + testWidgets('RenderAnimatedOpacityMixin avoids repainting child as it animates', (WidgetTester tester) async { + RenderTestObject.paintCount = 0; + final AnimationController controller = AnimationController(vsync: const TestVSync(), duration: const Duration(seconds: 1)); + final Tween opacityTween = Tween(begin: 0, end: 0.99); // Layer is dropped at 1 + await tester.pumpWidget( + Container( + color: Colors.red, + child: FadeTransition( + opacity: controller.drive(opacityTween), + child: const TestWidget(), + ), + ) + ); + + expect(RenderTestObject.paintCount, 0); + controller.forward(); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + + expect(RenderTestObject.paintCount, 1); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + expect(RenderTestObject.paintCount, 1); controller.stop(); @@ -43,7 +76,7 @@ void main() { testWidgets('RenderAnimatedOpacityMixin allows opacity layer to be disposed when animating to 0 opacity', (WidgetTester tester) async { RenderTestObject.paintCount = 0; final AnimationController controller = AnimationController(vsync: const TestVSync(), duration: const Duration(seconds: 1)); - final Tween opacityTween = Tween(begin: 1, end: 0); + final Tween opacityTween = Tween(begin: 0.99, end: 0); await tester.pumpWidget( Container( color: Colors.red,