[framework] avoid compositing with visibility (#111844)
This commit is contained in:
parent
1b583a837d
commit
3a1a25339e
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
@ -168,7 +168,7 @@ class Visibility extends StatelessWidget {
|
|||||||
/// [child] subtree is not trivial then it is significantly cheaper to not
|
/// [child] subtree is not trivial then it is significantly cheaper to not
|
||||||
/// even keep the state (see [maintainState]).
|
/// even keep the state (see [maintainState]).
|
||||||
///
|
///
|
||||||
/// If this property is true, [Opacity] is used instead of [Offstage].
|
/// If this property is false, [Offstage] is used.
|
||||||
///
|
///
|
||||||
/// If this property is false, then [maintainSemantics] and
|
/// If this property is false, then [maintainSemantics] and
|
||||||
/// [maintainInteractivity] must also be false.
|
/// [maintainInteractivity] must also be false.
|
||||||
@ -222,9 +222,9 @@ class Visibility extends StatelessWidget {
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Opacity(
|
return _Visibility(
|
||||||
opacity: visible ? 1.0 : 0.0,
|
visible: visible,
|
||||||
alwaysIncludeSemantics: maintainSemantics,
|
maintainSemantics: maintainSemantics,
|
||||||
child: result,
|
child: result,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -410,8 +410,7 @@ class SliverVisibility extends StatelessWidget {
|
|||||||
/// [sliver] subtree is not trivial then it is significantly cheaper to not
|
/// [sliver] subtree is not trivial then it is significantly cheaper to not
|
||||||
/// even keep the state (see [maintainState]).
|
/// even keep the state (see [maintainState]).
|
||||||
///
|
///
|
||||||
/// If this property is true, [SliverOpacity] is used instead of
|
/// If this property is false, [SliverOffstage] is used.
|
||||||
/// [SliverOffstage].
|
|
||||||
///
|
///
|
||||||
/// If this property is false, then [maintainSemantics] and
|
/// If this property is false, then [maintainSemantics] and
|
||||||
/// [maintainInteractivity] must also be false.
|
/// [maintainInteractivity] must also be false.
|
||||||
@ -460,9 +459,9 @@ class SliverVisibility extends StatelessWidget {
|
|||||||
ignoringSemantics: !visible && !maintainSemantics,
|
ignoringSemantics: !visible && !maintainSemantics,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return SliverOpacity(
|
return _SliverVisibility(
|
||||||
opacity: visible ? 1.0 : 0.0,
|
visible: visible,
|
||||||
alwaysIncludeSemantics: maintainSemantics,
|
maintainSemantics: maintainSemantics,
|
||||||
sliver: result,
|
sliver: result,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -495,3 +494,132 @@ class SliverVisibility extends StatelessWidget {
|
|||||||
properties.add(FlagProperty('maintainInteractivity', value: maintainInteractivity, ifFalse: 'maintainInteractivity'));
|
properties.add(FlagProperty('maintainInteractivity', value: maintainInteractivity, ifFalse: 'maintainInteractivity'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A widget that conditionally hides its child, but without the forced compositing of `Opacity`.
|
||||||
|
//
|
||||||
|
// A fully opaque `Opacity` widget is required to leave its opacity layer in the layer tree. This
|
||||||
|
// forces all parent render objects to also composite, which can break a simple scene into many
|
||||||
|
// different layers. This can be significantly more expensive, so the issue is avoided by a
|
||||||
|
// specialized render object that does not ever force compositing.
|
||||||
|
class _Visibility extends SingleChildRenderObjectWidget {
|
||||||
|
const _Visibility({ required this.visible, required this.maintainSemantics, super.child });
|
||||||
|
|
||||||
|
final bool visible;
|
||||||
|
final bool maintainSemantics;
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
|
return _RenderVisibility(visible, maintainSemantics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {
|
||||||
|
(renderObject as _RenderVisibility)
|
||||||
|
..visible = visible
|
||||||
|
..maintainSemantics = maintainSemantics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RenderVisibility extends RenderProxyBox {
|
||||||
|
_RenderVisibility(this._visible, this._maintainSemantics);
|
||||||
|
|
||||||
|
bool get visible => _visible;
|
||||||
|
bool _visible;
|
||||||
|
set visible(bool value) {
|
||||||
|
if (value == visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_visible = value;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get maintainSemantics => _maintainSemantics;
|
||||||
|
bool _maintainSemantics;
|
||||||
|
set maintainSemantics(bool value) {
|
||||||
|
if (value == maintainSemantics) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_maintainSemantics = value;
|
||||||
|
markNeedsSemanticsUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
||||||
|
if (maintainSemantics || visible) {
|
||||||
|
super.visitChildrenForSemantics(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.paint(context, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A widget that conditionally hides its child, but without the forced compositing of `SliverOpacity`.
|
||||||
|
//
|
||||||
|
// A fully opaque `SliverOpacity` widget is required to leave its opacity layer in the layer tree.
|
||||||
|
// This forces all parent render objects to also composite, which can break a simple scene into many
|
||||||
|
// different layers. This can be significantly more expensive, so the issue is avoided by a
|
||||||
|
// specialized render object that does not ever force compositing.
|
||||||
|
class _SliverVisibility extends SingleChildRenderObjectWidget {
|
||||||
|
const _SliverVisibility({ required this.visible, required this.maintainSemantics, Widget? sliver })
|
||||||
|
: super(child: sliver);
|
||||||
|
|
||||||
|
final bool visible;
|
||||||
|
final bool maintainSemantics;
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
|
return _RenderSliverVisibility(visible, maintainSemantics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {
|
||||||
|
(renderObject as _RenderSliverVisibility)
|
||||||
|
..visible = visible
|
||||||
|
..maintainSemantics = maintainSemantics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RenderSliverVisibility extends RenderProxySliver {
|
||||||
|
_RenderSliverVisibility(this._visible, this._maintainSemantics);
|
||||||
|
|
||||||
|
bool get visible => _visible;
|
||||||
|
bool _visible;
|
||||||
|
set visible(bool value) {
|
||||||
|
if (value == visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_visible = value;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get maintainSemantics => _maintainSemantics;
|
||||||
|
bool _maintainSemantics;
|
||||||
|
set maintainSemantics(bool value) {
|
||||||
|
if (value == maintainSemantics) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_maintainSemantics = value;
|
||||||
|
markNeedsSemanticsUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
||||||
|
if (maintainSemantics || visible) {
|
||||||
|
super.visitChildrenForSemantics(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.paint(context, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
@ -424,4 +425,50 @@ void main() {
|
|||||||
|
|
||||||
semantics.dispose();
|
semantics.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Visibility does not force compositing when visible and maintain*', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Visibility(
|
||||||
|
maintainSize: true,
|
||||||
|
maintainAnimation: true,
|
||||||
|
maintainState: true,
|
||||||
|
child: Text('hello', textDirection: TextDirection.ltr),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Root transform from the tester and then the picture created by the text.
|
||||||
|
expect(tester.layers, hasLength(2));
|
||||||
|
expect(tester.layers, isNot(contains(isA<OpacityLayer>())));
|
||||||
|
expect(tester.layers.last, isA<PictureLayer>());
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SliverVisibility does not force compositing when visible and maintain*', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverVisibility(
|
||||||
|
maintainSize: true,
|
||||||
|
maintainAnimation: true,
|
||||||
|
maintainState: true,
|
||||||
|
sliver: SliverList(
|
||||||
|
delegate: SliverChildListDelegate.fixed(
|
||||||
|
addRepaintBoundaries: false,
|
||||||
|
<Widget>[
|
||||||
|
Text('hello'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
))
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// This requires a lot more layers due to including sliver lists which do manage additional
|
||||||
|
// offset layers. Just trust me this is one fewer layers than before...
|
||||||
|
expect(tester.layers, hasLength(6));
|
||||||
|
expect(tester.layers, isNot(contains(isA<OpacityLayer>())));
|
||||||
|
expect(tester.layers.last, isA<PictureLayer>());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user