Unverified Commit d01874d7 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[framework] re-rasterize page transition when layout size changes (#115371)

* [framework] autoresize on snapshot widget

* ++

* ++

* ++

* use layout to resize
parent b5791092
......@@ -291,7 +291,6 @@ class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTr
bool get useSnapshot => !kIsWeb && widget.allowSnapshotting;
late _ZoomEnterTransitionPainter delegate;
MediaQueryData? mediaQueryData;
static final Animatable<double> _fadeInTransition = Tween<double>(
begin: 0.0,
......@@ -356,18 +355,6 @@ class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTr
super.didUpdateWidget(oldWidget);
}
@override
void didChangeDependencies() {
// If the screen size changes during the transition, perhaps due to
// a keyboard dismissal, then ensure that contents are re-rasterized once.
final MediaQueryData? data = MediaQuery.maybeOf(context);
if (mediaQueryDataChanged(mediaQueryData, data)) {
controller.clear();
}
mediaQueryData = data;
super.didChangeDependencies();
}
@override
void dispose() {
widget.animation.removeListener(onAnimationValueChange);
......@@ -382,6 +369,7 @@ class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTr
painter: delegate,
controller: controller,
mode: SnapshotMode.permissive,
autoresize: true,
child: widget.child,
);
}
......@@ -407,7 +395,6 @@ class _ZoomExitTransition extends StatefulWidget {
class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTransitionBase {
late _ZoomExitTransitionPainter delegate;
MediaQueryData? mediaQueryData;
// See SnapshotWidget doc comment, this is disabled on web because the HTML backend doesn't
// support this functionality and the canvaskit backend uses a single thread for UI and raster
......@@ -472,18 +459,6 @@ class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTran
super.didUpdateWidget(oldWidget);
}
@override
void didChangeDependencies() {
// If the screen size changes during the transition, perhaps due to
// a keyboard dismissal, then ensure that contents are re-rasterized once.
final MediaQueryData? data = MediaQuery.maybeOf(context);
if (mediaQueryDataChanged(mediaQueryData, data)) {
controller.clear();
}
mediaQueryData = data;
super.didChangeDependencies();
}
@override
void dispose() {
widget.animation.removeListener(onAnimationValueChange);
......@@ -498,6 +473,7 @@ class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTran
painter: delegate,
controller: controller,
mode: SnapshotMode.permissive,
autoresize: true,
child: widget.child,
);
}
......@@ -830,13 +806,6 @@ mixin _ZoomTransitionBase {
break;
}
}
// Whether any of the properties that would impact the page transition
// changed.
bool mediaQueryDataChanged(MediaQueryData? oldData, MediaQueryData? newData) {
return oldData?.size != newData?.size ||
oldData?.viewInsets != newData?.viewInsets;
}
}
class _ZoomEnterTransitionPainter extends SnapshotPainter {
......
......@@ -110,6 +110,7 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
super.key,
this.mode = SnapshotMode.normal,
this.painter = const _DefaultSnapshotPainter(),
this.autoresize = false,
required this.controller,
required super.child
});
......@@ -125,6 +126,12 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
/// See [SnapshotMode] for more information.
final SnapshotMode mode;
/// Whether or not changes in render object size should automatically re-create
/// the snapshot.
///
/// Defaults to false.
final bool autoresize;
/// The painter used to paint the child snapshot or child widgets.
final SnapshotPainter painter;
......@@ -136,6 +143,7 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
mode: mode,
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
painter: painter,
autoresize: autoresize,
);
}
......@@ -146,7 +154,8 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
..controller = controller
..mode = mode
..devicePixelRatio = MediaQuery.of(context).devicePixelRatio
..painter = painter;
..painter = painter
..autoresize = autoresize;
}
}
......@@ -159,10 +168,12 @@ class _RenderSnapshotWidget extends RenderProxyBox {
required SnapshotController controller,
required SnapshotMode mode,
required SnapshotPainter painter,
required bool autoresize,
}) : _devicePixelRatio = devicePixelRatio,
_controller = controller,
_mode = mode,
_painter = painter;
_painter = painter,
_autoresize = autoresize;
/// The device pixel ratio used to create the child image.
double get devicePixelRatio => _devicePixelRatio;
......@@ -230,6 +241,17 @@ class _RenderSnapshotWidget extends RenderProxyBox {
markNeedsPaint();
}
/// Whether or not changes in render object size should automatically re-rasterize.
bool get autoresize => _autoresize;
bool _autoresize;
set autoresize(bool value) {
if (value == autoresize) {
return;
}
_autoresize = value;
markNeedsPaint();
}
ui.Image? _childRaster;
Size? _childRasterSize;
// Set to true if the snapshot mode was not forced and a platform view
......@@ -292,9 +314,12 @@ class _RenderSnapshotWidget extends RenderProxyBox {
}
final ui.Image image = offsetLayer.toImageSync(Offset.zero & size, pixelRatio: devicePixelRatio);
offsetLayer.dispose();
_lastCachedSize = size;
return image;
}
Size? _lastCachedSize;
@override
void paint(PaintingContext context, Offset offset) {
if (size.isEmpty) {
......@@ -310,6 +335,12 @@ class _RenderSnapshotWidget extends RenderProxyBox {
painter.paint(context, offset, size, super.paint);
return;
}
if (autoresize && size != _lastCachedSize && _lastCachedSize != null) {
_childRaster?.dispose();
_childRaster = null;
}
if (_childRaster == null) {
_childRaster = _paintAndDetachToImage();
_childRasterSize = size * devicePixelRatio;
......
......@@ -3,6 +3,8 @@
// found in the LICENSE file.
@Tags(<String>['reduced-test-set'])
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart' show CupertinoPageRoute;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
......@@ -236,13 +238,17 @@ void main() {
expect(find.text('Page 2'), findsNothing);
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('test page transition (_ZoomPageTransition) with rasterization re-rasterizes when window size changes', (WidgetTester tester) async {
// Shrink the window size.
testWidgets('test page transition (_ZoomPageTransition) with rasterization re-rasterizes when window insets', (WidgetTester tester) async {
late Size oldSize;
late ui.WindowPadding oldInsets;
try {
oldSize = tester.binding.window.physicalSize;
oldInsets = tester.binding.window.viewInsets;
tester.binding.window.physicalSizeTestValue = const Size(1000, 1000);
tester.binding.window.viewInsetsTestValue = ui.WindowPadding.zero;
// Intentionally use nested scaffolds to simulate the view insets being
// consumed.
final Key key = GlobalKey();
await tester.pumpWidget(
RepaintBoundary(
......@@ -251,7 +257,9 @@ void main() {
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
builder: (BuildContext context) {
return const Material(child: SizedBox.shrink());
return const Scaffold(body: Scaffold(
body: Material(child: SizedBox.shrink())
));
},
);
},
......@@ -265,8 +273,8 @@ void main() {
await expectLater(find.byKey(key), matchesGoldenFile('zoom_page_transition.small.png'));
// Increase the window size.
tester.binding.window.physicalSizeTestValue = const Size(1000, 2000);
// Change the view insets
tester.binding.window.viewInsetsTestValue = const TestWindowPadding(left: 0, top: 0, right: 0, bottom: 500);
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
......@@ -274,6 +282,7 @@ void main() {
await expectLater(find.byKey(key), matchesGoldenFile('zoom_page_transition.big.png'));
} finally {
tester.binding.window.physicalSizeTestValue = oldSize;
tester.binding.window.viewInsetsTestValue = oldInsets;
}
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web.
......@@ -1236,3 +1245,21 @@ class TestDependencies extends StatelessWidget {
);
}
}
class TestWindowPadding implements ui.WindowPadding {
const TestWindowPadding({
required this.left,
required this.top,
required this.right,
required this.bottom,
});
@override
final double left;
@override
final double top;
@override
final double right;
@override
final double bottom;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment