diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 28b5990eff9..e593f59641b 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -843,7 +843,7 @@ class RenderOpacity extends RenderProxyBox { super(child); @override - bool get isRepaintBoundary => child != null && (_alpha > 0); + bool get alwaysNeedsCompositing => child != null && (_alpha > 0); @override OffsetLayer updateCompositedLayer({required covariant OpacityLayer? oldLayer}) { @@ -871,13 +871,13 @@ class RenderOpacity extends RenderProxyBox { assert(value >= 0.0 && value <= 1.0); if (_opacity == value) return; - final bool wasRepaintBoundary = isRepaintBoundary; + final bool didNeedCompositing = alwaysNeedsCompositing; final bool wasVisible = _alpha != 0; _opacity = value; _alpha = ui.Color.getAlphaFromOpacity(_opacity); - if (wasRepaintBoundary != isRepaintBoundary) + if (didNeedCompositing != alwaysNeedsCompositing) markNeedsCompositingBitsUpdate(); - markNeedsCompositedLayerUpdate(); + markNeedsPaint(); if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) markNeedsSemanticsUpdate(); } @@ -898,10 +898,19 @@ class RenderOpacity extends RenderProxyBox { @override void paint(PaintingContext context, Offset offset) { - if (_alpha == 0) { - return; + if (child != null) { + if (_alpha == 0) { + // No need to keep the layer. We'll create a new one if necessary. + layer = null; + return; + } + assert(needsCompositing); + layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer?); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } - super.paint(context, offset); } @override diff --git a/packages/flutter/test/widgets/opacity_repaint_test.dart b/packages/flutter/test/widgets/opacity_repaint_test.dart deleted file mode 100644 index 00ca2964d3e..00000000000 --- a/packages/flutter/test/widgets/opacity_repaint_test.dart +++ /dev/null @@ -1,275 +0,0 @@ -// 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 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('RenderOpacity acts as a repaint boundary for changes above the widget when partially opaque', (WidgetTester tester) async { - RenderTestObject.paintCount = 0; - await tester.pumpWidget( - Container( - color: Colors.red, - child: const Opacity( - opacity: 0.5, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - - await tester.pumpWidget( - Container( - color: Colors.blue, - child: const Opacity( - opacity: 0.5, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - }); - - testWidgets('RenderOpacity acts as a repaint boundary for changes above the widget when fully opaque', (WidgetTester tester) async { - RenderTestObject.paintCount = 0; - await tester.pumpWidget( - Container( - color: Colors.red, - child: const Opacity( - opacity: 1, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - - await tester.pumpWidget( - Container( - color: Colors.blue, - child: const Opacity( - opacity: 1, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - }); - - testWidgets('RenderOpacity can update its opacity without repainting its child - partially opaque to partially opaque', (WidgetTester tester) async { - RenderTestObject.paintCount = 0; - await tester.pumpWidget( - Container( - color: Colors.red, - child: const Opacity( - opacity: 0.5, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - - await tester.pumpWidget( - Container( - color: Colors.blue, - child: const Opacity( - opacity: 0.9, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - }); - - testWidgets('RenderOpacity can update its opacity without repainting its child - partially opaque to fully opaque', (WidgetTester tester) async { - RenderTestObject.paintCount = 0; - await tester.pumpWidget( - Container( - color: Colors.red, - child: const Opacity( - opacity: 0.5, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - - await tester.pumpWidget( - Container( - color: Colors.blue, - child: const Opacity( - opacity: 1, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - }); - - testWidgets('RenderOpacity can update its opacity without repainting its child - fully opaque to partially opaque', (WidgetTester tester) async { - RenderTestObject.paintCount = 0; - await tester.pumpWidget( - Container( - color: Colors.red, - child: const Opacity( - opacity: 1, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - - await tester.pumpWidget( - Container( - color: Colors.blue, - child: const Opacity( - opacity: 0.5, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - }); - - testWidgets('RenderOpacity can update its opacity without repainting its child - fully opaque to fully transparent', (WidgetTester tester) async { - RenderTestObject.paintCount = 0; - await tester.pumpWidget( - Container( - color: Colors.red, - child: const Opacity( - opacity: 1, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - - await tester.pumpWidget( - Container( - color: Colors.blue, - child: const Opacity( - opacity: 0, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - }); - - testWidgets('RenderOpacity must paint child - fully transparent to partially opaque', (WidgetTester tester) async { - RenderTestObject.paintCount = 0; - await tester.pumpWidget( - Container( - color: Colors.red, - child: const Opacity( - opacity: 0, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 0); - - await tester.pumpWidget( - Container( - color: Colors.blue, - child: const Opacity( - opacity: 0.5, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - }); - - testWidgets('RenderOpacity allows child to update without updating parent', (WidgetTester tester) async { - RenderTestObject.paintCount = 0; - await tester.pumpWidget( - TestWidget( - child: Opacity( - opacity: 0.5, - child: Container( - color: Colors.red, - ), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - - await tester.pumpWidget( - TestWidget( - child: Opacity( - opacity: 0.5, - child: Container( - color: Colors.blue, - ), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - }); - - testWidgets('RenderOpacity disposes of opacity layer when opacity is updated to 0', (WidgetTester tester) async { - RenderTestObject.paintCount = 0; - await tester.pumpWidget( - Container( - color: Colors.red, - child: const Opacity( - opacity: 0.5, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - expect(tester.layers, contains(isA())); - - await tester.pumpWidget( - Container( - color: Colors.blue, - child: const Opacity( - opacity: 0, - child: TestWidget(), - ), - ) - ); - - expect(RenderTestObject.paintCount, 1); - expect(tester.layers, isNot(contains(isA()))); - }); -} - -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); - } -}