[framework] elide ImageFilter layers when animation is stopped (#101731)
This commit is contained in:
parent
88246030ae
commit
ea19a77bff
@ -274,6 +274,9 @@ class ScaleTransition extends AnimatedWidget {
|
||||
|
||||
/// The filter quality with which to apply the transform as a bitmap operation.
|
||||
///
|
||||
/// When the animation is stopped (either in [AnimationStatus.dismissed] or
|
||||
/// [AnimationStatus.completed]), the filter quality argument will be ignored.
|
||||
///
|
||||
/// {@macro flutter.widgets.Transform.optional.FilterQuality}
|
||||
final FilterQuality? filterQuality;
|
||||
|
||||
@ -284,10 +287,25 @@ class ScaleTransition extends AnimatedWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// The ImageFilter layer created by setting filterQuality will introduce
|
||||
// a saveLayer call. This is usually worthwhile when animating the layer,
|
||||
// but leaving it in the layer tree before the animation has started or after
|
||||
// it has finished significantly hurts performance.
|
||||
final bool useFilterQuality;
|
||||
switch (scale.status) {
|
||||
case AnimationStatus.dismissed:
|
||||
case AnimationStatus.completed:
|
||||
useFilterQuality = false;
|
||||
break;
|
||||
case AnimationStatus.forward:
|
||||
case AnimationStatus.reverse:
|
||||
useFilterQuality = true;
|
||||
break;
|
||||
}
|
||||
return Transform.scale(
|
||||
scale: scale.value,
|
||||
alignment: alignment,
|
||||
filterQuality: filterQuality,
|
||||
filterQuality: useFilterQuality ? filterQuality : null,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
@ -340,6 +358,9 @@ class RotationTransition extends AnimatedWidget {
|
||||
|
||||
/// The filter quality with which to apply the transform as a bitmap operation.
|
||||
///
|
||||
/// When the animation is stopped (either in [AnimationStatus.dismissed] or
|
||||
/// [AnimationStatus.completed]), the filter quality argument will be ignored.
|
||||
///
|
||||
/// {@macro flutter.widgets.Transform.optional.FilterQuality}
|
||||
final FilterQuality? filterQuality;
|
||||
|
||||
@ -350,10 +371,25 @@ class RotationTransition extends AnimatedWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// The ImageFilter layer created by setting filterQuality will introduce
|
||||
// a saveLayer call. This is usually worthwhile when animating the layer,
|
||||
// but leaving it in the layer tree before the animation has started or after
|
||||
// it has finished significantly hurts performance.
|
||||
final bool useFilterQuality;
|
||||
switch (turns.status) {
|
||||
case AnimationStatus.dismissed:
|
||||
case AnimationStatus.completed:
|
||||
useFilterQuality = false;
|
||||
break;
|
||||
case AnimationStatus.forward:
|
||||
case AnimationStatus.reverse:
|
||||
useFilterQuality = true;
|
||||
break;
|
||||
}
|
||||
return Transform.rotate(
|
||||
angle: turns.value * math.pi * 2.0,
|
||||
alignment: alignment,
|
||||
filterQuality: filterQuality,
|
||||
filterQuality: useFilterQuality ? filterQuality : null,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
@ -446,4 +446,110 @@ void main() {
|
||||
expect(_getOpacity(tester, 'Fade In'), 1.0);
|
||||
});
|
||||
});
|
||||
|
||||
group('ScaleTransition', () {
|
||||
testWidgets('uses ImageFilter when provided with FilterQuality argument', (WidgetTester tester) async {
|
||||
final AnimationController controller = AnimationController(vsync: const TestVSync());
|
||||
final Animation<double> animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
|
||||
final Widget widget = Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: ScaleTransition(
|
||||
scale: animation,
|
||||
filterQuality: FilterQuality.none,
|
||||
child: const Text('Scale Transition'),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(widget);
|
||||
|
||||
// Validate that expensive layer is not left in tree before animation has started.
|
||||
expect(tester.layers, isNot(contains(isA<ImageFilterLayer>())));
|
||||
|
||||
controller.value = 0.25;
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.layers, contains(isA<ImageFilterLayer>().having(
|
||||
(ImageFilterLayer layer) => layer.imageFilter.toString(),
|
||||
'image filter',
|
||||
startsWith('ImageFilter.matrix('),
|
||||
)));
|
||||
|
||||
controller.value = 0.5;
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.layers, contains(isA<ImageFilterLayer>().having(
|
||||
(ImageFilterLayer layer) => layer.imageFilter.toString(),
|
||||
'image filter',
|
||||
startsWith('ImageFilter.matrix('),
|
||||
)));
|
||||
|
||||
controller.value = 0.75;
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.layers, contains(isA<ImageFilterLayer>().having(
|
||||
(ImageFilterLayer layer) => layer.imageFilter.toString(),
|
||||
'image filter',
|
||||
startsWith('ImageFilter.matrix('),
|
||||
)));
|
||||
|
||||
controller.value = 1;
|
||||
await tester.pump();
|
||||
|
||||
// Validate that expensive layer is not left in tree after animation has finished.
|
||||
expect(tester.layers, isNot(contains(isA<ImageFilterLayer>())));
|
||||
});
|
||||
});
|
||||
|
||||
group('RotationTransition', () {
|
||||
testWidgets('uses ImageFilter when provided with FilterQuality argument', (WidgetTester tester) async {
|
||||
final AnimationController controller = AnimationController(vsync: const TestVSync());
|
||||
final Animation<double> animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
|
||||
final Widget widget = Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: RotationTransition(
|
||||
turns: animation,
|
||||
filterQuality: FilterQuality.none,
|
||||
child: const Text('Scale Transition'),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(widget);
|
||||
|
||||
// Validate that expensive layer is not left in tree before animation has started.
|
||||
expect(tester.layers, isNot(contains(isA<ImageFilterLayer>())));
|
||||
|
||||
controller.value = 0.25;
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.layers, contains(isA<ImageFilterLayer>().having(
|
||||
(ImageFilterLayer layer) => layer.imageFilter.toString(),
|
||||
'image filter',
|
||||
startsWith('ImageFilter.matrix('),
|
||||
)));
|
||||
|
||||
controller.value = 0.5;
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.layers, contains(isA<ImageFilterLayer>().having(
|
||||
(ImageFilterLayer layer) => layer.imageFilter.toString(),
|
||||
'image filter',
|
||||
startsWith('ImageFilter.matrix('),
|
||||
)));
|
||||
|
||||
controller.value = 0.75;
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.layers, contains(isA<ImageFilterLayer>().having(
|
||||
(ImageFilterLayer layer) => layer.imageFilter.toString(),
|
||||
'image filter',
|
||||
startsWith('ImageFilter.matrix('),
|
||||
)));
|
||||
|
||||
controller.value = 1;
|
||||
await tester.pump();
|
||||
|
||||
// Validate that expensive layer is not left in tree after animation has finished.
|
||||
expect(tester.layers, isNot(contains(isA<ImageFilterLayer>())));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user