Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 13 additions & 14 deletions packages/flutter/lib/src/rendering/proxy_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,9 @@ class RenderOpacity extends RenderProxyBox {
@override
bool get alwaysNeedsCompositing => child != null && _alpha > 0;

@override
bool get isRepaintBoundary => alwaysNeedsCompositing;

int _alpha;

/// The fraction to scale the child's alpha value.
Expand Down Expand Up @@ -917,7 +920,7 @@ class RenderOpacity extends RenderProxyBox {
if (didNeedCompositing != alwaysNeedsCompositing) {
markNeedsCompositingBitsUpdate();
}
markNeedsPaint();
markNeedsCompositedLayerUpdate();
if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) {
markNeedsSemanticsUpdate();
}
Expand All @@ -944,23 +947,19 @@ class RenderOpacity extends RenderProxyBox {
return _alpha > 0;
}

@override
OffsetLayer updateCompositedLayer({required covariant OpacityLayer? oldLayer}) {
final OpacityLayer layer = oldLayer ?? OpacityLayer();
layer.alpha = _alpha;
return layer;
}

@override
void paint(PaintingContext context, Offset offset) {
if (child == null) {
return;
}
if (_alpha == 0) {
// No need to keep the layer. We'll create a new one if necessary.
layer = null;
if (child == null || _alpha == 0) {
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
Expand Down
96 changes: 96 additions & 0 deletions packages/flutter/test/widgets/opacity_repaint_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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 avoids repainting and does not drop layer at fully opaque', (WidgetTester tester) async {
RenderTestObject.paintCount = 0;
await tester.pumpWidget(
Container(
color: Colors.red,
child: const Opacity(
opacity: 0.0,
child: TestWidget(),
),
)
);

expect(RenderTestObject.paintCount, 0);

await tester.pumpWidget(
Container(
color: Colors.red,
child: const Opacity(
opacity: 0.1,
child: TestWidget(),
),
)
);

expect(RenderTestObject.paintCount, 1);

await tester.pumpWidget(
Container(
color: Colors.red,
child: const Opacity(
opacity: 1,
child: TestWidget(),
),
)
);

expect(RenderTestObject.paintCount, 1);
});

testWidgets('RenderOpacity allows opacity layer to be dropped at 0 opacity', (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.red,
child: const Opacity(
opacity: 0.0,
child: TestWidget(),
),
)
);

expect(RenderTestObject.paintCount, 1);
expect(tester.layers, isNot(contains(isA<OpacityLayer>())));
});
}

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);
}
}