Skip to content

Commit c12b0de

Browse files
author
Jonah Williams
authored
remove opacity layer at fully opaque (#106351)
1 parent 10fb7b4 commit c12b0de

File tree

4 files changed

+77
-6
lines changed

4 files changed

+77
-6
lines changed

packages/flutter/lib/src/rendering/proxy_box.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,7 @@ class RenderOpacity extends RenderProxyBox {
887887

888888
@override
889889
OffsetLayer updateCompositedLayer({required covariant OpacityLayer? oldLayer}) {
890+
assert(_alpha != 255);
890891
final OpacityLayer updatedLayer = oldLayer ?? OpacityLayer();
891892
updatedLayer.alpha = _alpha;
892893
return updatedLayer;
@@ -1060,7 +1061,7 @@ mixin RenderAnimatedOpacityMixin<T extends RenderObject> on RenderObjectWithChil
10601061
_alpha = ui.Color.getAlphaFromOpacity(opacity.value);
10611062
if (oldAlpha != _alpha) {
10621063
final bool? wasRepaintBoundary = _currentlyIsRepaintBoundary;
1063-
_currentlyIsRepaintBoundary = _alpha! > 0;
1064+
_currentlyIsRepaintBoundary = _alpha! > 0 && _alpha! < 255;
10641065
if (child != null && wasRepaintBoundary != _currentlyIsRepaintBoundary) {
10651066
markNeedsCompositingBitsUpdate();
10661067
}

packages/flutter/test/rendering/proxy_box_test.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ void main() {
272272
expect(renderAnimatedOpacity.needsCompositing, false);
273273
});
274274

275-
test('RenderAnimatedOpacity does composite if it is opaque', () {
275+
test('RenderAnimatedOpacity does not composite if it is opaque', () {
276276
final Animation<double> opacityAnimation = AnimationController(
277277
vsync: FakeTickerProvider(),
278278
)..value = 1.0;
@@ -282,6 +282,20 @@ void main() {
282282
child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
283283
);
284284

285+
layout(renderAnimatedOpacity, phase: EnginePhase.composite);
286+
expect(renderAnimatedOpacity.needsCompositing, false);
287+
});
288+
289+
test('RenderAnimatedOpacity does composite if it is partially opaque', () {
290+
final Animation<double> opacityAnimation = AnimationController(
291+
vsync: FakeTickerProvider(),
292+
)..value = 0.5;
293+
294+
final RenderAnimatedOpacity renderAnimatedOpacity = RenderAnimatedOpacity(
295+
opacity: opacityAnimation,
296+
child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
297+
);
298+
285299
layout(renderAnimatedOpacity, phase: EnginePhase.composite);
286300
expect(renderAnimatedOpacity.needsCompositing, true);
287301
});

packages/flutter/test/rendering/proxy_sliver_test.dart

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ void main() {
9898
expect(renderSliverAnimatedOpacity.needsCompositing, false);
9999
});
100100

101-
test('RenderSliverAnimatedOpacity does composite if it is opaque', () {
101+
test('RenderSliverAnimatedOpacity does composite if it is partially opaque', () {
102102
final Animation<double> opacityAnimation = AnimationController(
103103
vsync: FakeTickerProvider(),
104-
)..value = 1.0;
104+
)..value = 0.5;
105105

106106
final RenderSliverAnimatedOpacity renderSliverAnimatedOpacity = RenderSliverAnimatedOpacity(
107107
opacity: opacityAnimation,
@@ -121,6 +121,29 @@ void main() {
121121
expect(renderSliverAnimatedOpacity.needsCompositing, true);
122122
});
123123

124+
test('RenderSliverAnimatedOpacity does not composite if it is opaque', () {
125+
final Animation<double> opacityAnimation = AnimationController(
126+
vsync: FakeTickerProvider(),
127+
)..value = 1.0;
128+
129+
final RenderSliverAnimatedOpacity renderSliverAnimatedOpacity = RenderSliverAnimatedOpacity(
130+
opacity: opacityAnimation,
131+
sliver: RenderSliverToBoxAdapter(
132+
child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
133+
),
134+
);
135+
136+
final RenderViewport root = RenderViewport(
137+
crossAxisDirection: AxisDirection.right,
138+
offset: ViewportOffset.zero(),
139+
cacheExtent: 250.0,
140+
children: <RenderSliver>[renderSliverAnimatedOpacity],
141+
);
142+
143+
layout(root, phase: EnginePhase.composite);
144+
expect(renderSliverAnimatedOpacity.needsCompositing, false);
145+
});
146+
124147
test('RenderSliverAnimatedOpacity reuses its layer', () {
125148
final Animation<double> opacityAnimation = AnimationController(
126149
vsync: FakeTickerProvider(),

packages/flutter/test/widgets/animated_opacity_repaint_test.dart

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'package:flutter/rendering.dart';
77
import 'package:flutter_test/flutter_test.dart';
88

99
void main() {
10-
testWidgets('RenderAnimatedOpacityMixin avoids repainting child as it animates', (WidgetTester tester) async {
10+
testWidgets('RenderAnimatedOpacityMixin drops layer when animating to 1', (WidgetTester tester) async {
1111
RenderTestObject.paintCount = 0;
1212
final AnimationController controller = AnimationController(vsync: const TestVSync(), duration: const Duration(seconds: 1));
1313
final Tween<double> opacityTween = Tween<double>(begin: 0, end: 1);
@@ -32,6 +32,39 @@ void main() {
3232
await tester.pump();
3333
await tester.pump(const Duration(milliseconds: 500));
3434

35+
expect(RenderTestObject.paintCount, 2);
36+
37+
controller.stop();
38+
await tester.pump();
39+
40+
expect(RenderTestObject.paintCount, 2);
41+
});
42+
43+
testWidgets('RenderAnimatedOpacityMixin avoids repainting child as it animates', (WidgetTester tester) async {
44+
RenderTestObject.paintCount = 0;
45+
final AnimationController controller = AnimationController(vsync: const TestVSync(), duration: const Duration(seconds: 1));
46+
final Tween<double> opacityTween = Tween<double>(begin: 0, end: 0.99); // Layer is dropped at 1
47+
await tester.pumpWidget(
48+
Container(
49+
color: Colors.red,
50+
child: FadeTransition(
51+
opacity: controller.drive(opacityTween),
52+
child: const TestWidget(),
53+
),
54+
)
55+
);
56+
57+
expect(RenderTestObject.paintCount, 0);
58+
controller.forward();
59+
60+
await tester.pump();
61+
await tester.pump(const Duration(milliseconds: 500));
62+
63+
expect(RenderTestObject.paintCount, 1);
64+
65+
await tester.pump();
66+
await tester.pump(const Duration(milliseconds: 500));
67+
3568
expect(RenderTestObject.paintCount, 1);
3669

3770
controller.stop();
@@ -43,7 +76,7 @@ void main() {
4376
testWidgets('RenderAnimatedOpacityMixin allows opacity layer to be disposed when animating to 0 opacity', (WidgetTester tester) async {
4477
RenderTestObject.paintCount = 0;
4578
final AnimationController controller = AnimationController(vsync: const TestVSync(), duration: const Duration(seconds: 1));
46-
final Tween<double> opacityTween = Tween<double>(begin: 1, end: 0);
79+
final Tween<double> opacityTween = Tween<double>(begin: 0.99, end: 0);
4780
await tester.pumpWidget(
4881
Container(
4982
color: Colors.red,

0 commit comments

Comments
 (0)