[framework] re-rasterize page transition when layout size changes (#115371)
* [framework] autoresize on snapshot widget * ++ * ++ * ++ * use layout to resize
This commit is contained in:
parent
b57910927f
commit
d01874d71e
@ -291,7 +291,6 @@ class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTr
|
|||||||
bool get useSnapshot => !kIsWeb && widget.allowSnapshotting;
|
bool get useSnapshot => !kIsWeb && widget.allowSnapshotting;
|
||||||
|
|
||||||
late _ZoomEnterTransitionPainter delegate;
|
late _ZoomEnterTransitionPainter delegate;
|
||||||
MediaQueryData? mediaQueryData;
|
|
||||||
|
|
||||||
static final Animatable<double> _fadeInTransition = Tween<double>(
|
static final Animatable<double> _fadeInTransition = Tween<double>(
|
||||||
begin: 0.0,
|
begin: 0.0,
|
||||||
@ -356,18 +355,6 @@ class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTr
|
|||||||
super.didUpdateWidget(oldWidget);
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
widget.animation.removeListener(onAnimationValueChange);
|
widget.animation.removeListener(onAnimationValueChange);
|
||||||
@ -382,6 +369,7 @@ class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTr
|
|||||||
painter: delegate,
|
painter: delegate,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
mode: SnapshotMode.permissive,
|
mode: SnapshotMode.permissive,
|
||||||
|
autoresize: true,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -407,7 +395,6 @@ class _ZoomExitTransition extends StatefulWidget {
|
|||||||
|
|
||||||
class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTransitionBase {
|
class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTransitionBase {
|
||||||
late _ZoomExitTransitionPainter delegate;
|
late _ZoomExitTransitionPainter delegate;
|
||||||
MediaQueryData? mediaQueryData;
|
|
||||||
|
|
||||||
// See SnapshotWidget doc comment, this is disabled on web because the HTML backend doesn't
|
// 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
|
// 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);
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
widget.animation.removeListener(onAnimationValueChange);
|
widget.animation.removeListener(onAnimationValueChange);
|
||||||
@ -498,6 +473,7 @@ class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTran
|
|||||||
painter: delegate,
|
painter: delegate,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
mode: SnapshotMode.permissive,
|
mode: SnapshotMode.permissive,
|
||||||
|
autoresize: true,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -830,13 +806,6 @@ mixin _ZoomTransitionBase {
|
|||||||
break;
|
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 {
|
class _ZoomEnterTransitionPainter extends SnapshotPainter {
|
||||||
|
@ -110,6 +110,7 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
this.mode = SnapshotMode.normal,
|
this.mode = SnapshotMode.normal,
|
||||||
this.painter = const _DefaultSnapshotPainter(),
|
this.painter = const _DefaultSnapshotPainter(),
|
||||||
|
this.autoresize = false,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required super.child
|
required super.child
|
||||||
});
|
});
|
||||||
@ -125,6 +126,12 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
|
|||||||
/// See [SnapshotMode] for more information.
|
/// See [SnapshotMode] for more information.
|
||||||
final SnapshotMode mode;
|
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.
|
/// The painter used to paint the child snapshot or child widgets.
|
||||||
final SnapshotPainter painter;
|
final SnapshotPainter painter;
|
||||||
|
|
||||||
@ -136,6 +143,7 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
|
|||||||
mode: mode,
|
mode: mode,
|
||||||
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
|
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
|
||||||
painter: painter,
|
painter: painter,
|
||||||
|
autoresize: autoresize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +154,8 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
|
|||||||
..controller = controller
|
..controller = controller
|
||||||
..mode = mode
|
..mode = mode
|
||||||
..devicePixelRatio = MediaQuery.of(context).devicePixelRatio
|
..devicePixelRatio = MediaQuery.of(context).devicePixelRatio
|
||||||
..painter = painter;
|
..painter = painter
|
||||||
|
..autoresize = autoresize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,10 +168,12 @@ class _RenderSnapshotWidget extends RenderProxyBox {
|
|||||||
required SnapshotController controller,
|
required SnapshotController controller,
|
||||||
required SnapshotMode mode,
|
required SnapshotMode mode,
|
||||||
required SnapshotPainter painter,
|
required SnapshotPainter painter,
|
||||||
|
required bool autoresize,
|
||||||
}) : _devicePixelRatio = devicePixelRatio,
|
}) : _devicePixelRatio = devicePixelRatio,
|
||||||
_controller = controller,
|
_controller = controller,
|
||||||
_mode = mode,
|
_mode = mode,
|
||||||
_painter = painter;
|
_painter = painter,
|
||||||
|
_autoresize = autoresize;
|
||||||
|
|
||||||
/// The device pixel ratio used to create the child image.
|
/// The device pixel ratio used to create the child image.
|
||||||
double get devicePixelRatio => _devicePixelRatio;
|
double get devicePixelRatio => _devicePixelRatio;
|
||||||
@ -230,6 +241,17 @@ class _RenderSnapshotWidget extends RenderProxyBox {
|
|||||||
markNeedsPaint();
|
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;
|
ui.Image? _childRaster;
|
||||||
Size? _childRasterSize;
|
Size? _childRasterSize;
|
||||||
// Set to true if the snapshot mode was not forced and a platform view
|
// 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);
|
final ui.Image image = offsetLayer.toImageSync(Offset.zero & size, pixelRatio: devicePixelRatio);
|
||||||
offsetLayer.dispose();
|
offsetLayer.dispose();
|
||||||
|
_lastCachedSize = size;
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Size? _lastCachedSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
if (size.isEmpty) {
|
if (size.isEmpty) {
|
||||||
@ -310,6 +335,12 @@ class _RenderSnapshotWidget extends RenderProxyBox {
|
|||||||
painter.paint(context, offset, size, super.paint);
|
painter.paint(context, offset, size, super.paint);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (autoresize && size != _lastCachedSize && _lastCachedSize != null) {
|
||||||
|
_childRaster?.dispose();
|
||||||
|
_childRaster = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (_childRaster == null) {
|
if (_childRaster == null) {
|
||||||
_childRaster = _paintAndDetachToImage();
|
_childRaster = _paintAndDetachToImage();
|
||||||
_childRasterSize = size * devicePixelRatio;
|
_childRasterSize = size * devicePixelRatio;
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
@Tags(<String>['reduced-test-set'])
|
@Tags(<String>['reduced-test-set'])
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart' show CupertinoPageRoute;
|
import 'package:flutter/cupertino.dart' show CupertinoPageRoute;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -236,13 +238,17 @@ void main() {
|
|||||||
expect(find.text('Page 2'), findsNothing);
|
expect(find.text('Page 2'), findsNothing);
|
||||||
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
|
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
|
||||||
|
|
||||||
testWidgets('test page transition (_ZoomPageTransition) with rasterization re-rasterizes when window size changes', (WidgetTester tester) async {
|
testWidgets('test page transition (_ZoomPageTransition) with rasterization re-rasterizes when window insets', (WidgetTester tester) async {
|
||||||
// Shrink the window size.
|
|
||||||
late Size oldSize;
|
late Size oldSize;
|
||||||
|
late ui.WindowPadding oldInsets;
|
||||||
try {
|
try {
|
||||||
oldSize = tester.binding.window.physicalSize;
|
oldSize = tester.binding.window.physicalSize;
|
||||||
|
oldInsets = tester.binding.window.viewInsets;
|
||||||
tester.binding.window.physicalSizeTestValue = const Size(1000, 1000);
|
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();
|
final Key key = GlobalKey();
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
RepaintBoundary(
|
RepaintBoundary(
|
||||||
@ -251,7 +257,9 @@ void main() {
|
|||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
builder: (BuildContext context) {
|
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'));
|
await expectLater(find.byKey(key), matchesGoldenFile('zoom_page_transition.small.png'));
|
||||||
|
|
||||||
// Increase the window size.
|
// Change the view insets
|
||||||
tester.binding.window.physicalSizeTestValue = const Size(1000, 2000);
|
tester.binding.window.viewInsetsTestValue = const TestWindowPadding(left: 0, top: 0, right: 0, bottom: 500);
|
||||||
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(milliseconds: 50));
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
@ -274,6 +282,7 @@ void main() {
|
|||||||
await expectLater(find.byKey(key), matchesGoldenFile('zoom_page_transition.big.png'));
|
await expectLater(find.byKey(key), matchesGoldenFile('zoom_page_transition.big.png'));
|
||||||
} finally {
|
} finally {
|
||||||
tester.binding.window.physicalSizeTestValue = oldSize;
|
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.
|
}, 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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user