Unverified Commit 9ae37030 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Use toPictureSync for faster zoom page transition (#106621)

parent 9f7033a9
...@@ -342,6 +342,7 @@ class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMi ...@@ -342,6 +342,7 @@ class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMi
super.settings, super.settings,
this.maintainState = true, this.maintainState = true,
super.fullscreenDialog, super.fullscreenDialog,
super.preferRasterization = true,
}) : assert(builder != null), }) : assert(builder != null),
assert(maintainState != null), assert(maintainState != null),
assert(fullscreenDialog != null) { assert(fullscreenDialog != null) {
...@@ -371,6 +372,7 @@ class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMi ...@@ -371,6 +372,7 @@ class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMi
class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMixin<T> { class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMixin<T> {
_PageBasedCupertinoPageRoute({ _PageBasedCupertinoPageRoute({
required CupertinoPage<T> page, required CupertinoPage<T> page,
super.preferRasterization = true,
}) : assert(page != null), }) : assert(page != null),
super(settings: page) { super(settings: page) {
assert(opaque); assert(opaque);
...@@ -417,6 +419,7 @@ class CupertinoPage<T> extends Page<T> { ...@@ -417,6 +419,7 @@ class CupertinoPage<T> extends Page<T> {
this.maintainState = true, this.maintainState = true,
this.title, this.title,
this.fullscreenDialog = false, this.fullscreenDialog = false,
this.preferRasterization = true,
super.key, super.key,
super.name, super.name,
super.arguments, super.arguments,
...@@ -437,9 +440,12 @@ class CupertinoPage<T> extends Page<T> { ...@@ -437,9 +440,12 @@ class CupertinoPage<T> extends Page<T> {
/// {@macro flutter.widgets.PageRoute.fullscreenDialog} /// {@macro flutter.widgets.PageRoute.fullscreenDialog}
final bool fullscreenDialog; final bool fullscreenDialog;
/// {@macro flutter.widgets.TransitionRoute.preferRasterization}
final bool preferRasterization;
@override @override
Route<T> createRoute(BuildContext context) { Route<T> createRoute(BuildContext context) {
return _PageBasedCupertinoPageRoute<T>(page: this); return _PageBasedCupertinoPageRoute<T>(page: this, preferRasterization: preferRasterization);
} }
} }
......
...@@ -39,6 +39,7 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi ...@@ -39,6 +39,7 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi
super.settings, super.settings,
this.maintainState = true, this.maintainState = true,
super.fullscreenDialog, super.fullscreenDialog,
super.preferRasterization = true,
}) : assert(builder != null), }) : assert(builder != null),
assert(maintainState != null), assert(maintainState != null),
assert(fullscreenDialog != null) { assert(fullscreenDialog != null) {
...@@ -157,6 +158,7 @@ class MaterialPage<T> extends Page<T> { ...@@ -157,6 +158,7 @@ class MaterialPage<T> extends Page<T> {
required this.child, required this.child,
this.maintainState = true, this.maintainState = true,
this.fullscreenDialog = false, this.fullscreenDialog = false,
this.preferRasterization = true,
super.key, super.key,
super.name, super.name,
super.arguments, super.arguments,
...@@ -174,9 +176,12 @@ class MaterialPage<T> extends Page<T> { ...@@ -174,9 +176,12 @@ class MaterialPage<T> extends Page<T> {
/// {@macro flutter.widgets.PageRoute.fullscreenDialog} /// {@macro flutter.widgets.PageRoute.fullscreenDialog}
final bool fullscreenDialog; final bool fullscreenDialog;
/// {@macro flutter.widgets.TransitionRoute.preferRasterization}
final bool preferRasterization;
@override @override
Route<T> createRoute(BuildContext context) { Route<T> createRoute(BuildContext context) {
return _PageBasedMaterialPageRoute<T>(page: this); return _PageBasedMaterialPageRoute<T>(page: this, preferRasterization: preferRasterization);
} }
} }
...@@ -187,6 +192,7 @@ class MaterialPage<T> extends Page<T> { ...@@ -187,6 +192,7 @@ class MaterialPage<T> extends Page<T> {
class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> { class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
_PageBasedMaterialPageRoute({ _PageBasedMaterialPageRoute({
required MaterialPage<T> page, required MaterialPage<T> page,
super.preferRasterization,
}) : assert(page != null), }) : assert(page != null),
super(settings: page) { super(settings: page) {
assert(opaque); assert(opaque);
......
...@@ -168,6 +168,18 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin { ...@@ -168,6 +168,18 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
bool _debugMutationsLocked = false; bool _debugMutationsLocked = false;
/// Whether or not this layer, or any child layers, can be rasterized with
/// [Scene.toImage] or [Scene.toImageSync].
///
/// If `false`, calling the above methods may yield an image which is
/// incomplete.
///
/// This value may change throughout the lifetime of the object, as the
/// child layers themselves are added or removed.
bool supportsRasterization() {
return true;
}
/// Describes the clip that would be applied to contents of this layer, /// Describes the clip that would be applied to contents of this layer,
/// if any. /// if any.
Rect? describeClipBounds() => null; Rect? describeClipBounds() => null;
...@@ -875,6 +887,12 @@ class TextureLayer extends Layer { ...@@ -875,6 +887,12 @@ class TextureLayer extends Layer {
/// The identity of the backend texture. /// The identity of the backend texture.
final int textureId; final int textureId;
// TODO(jonahwilliams): remove once https://github.com/flutter/flutter/issues/107576 is fixed.
@override
bool supportsRasterization() {
return false;
}
/// When true the texture will not be updated with new frames. /// When true the texture will not be updated with new frames.
/// ///
/// This is used for resizing embedded Android views: when resizing there /// This is used for resizing embedded Android views: when resizing there
...@@ -925,6 +943,11 @@ class PlatformViewLayer extends Layer { ...@@ -925,6 +943,11 @@ class PlatformViewLayer extends Layer {
/// A UIView with this identifier must have been created by [PlatformViewsService.initUiKitView]. /// A UIView with this identifier must have been created by [PlatformViewsService.initUiKitView].
final int viewId; final int viewId;
@override
bool supportsRasterization() {
return false;
}
@override @override
void addToScene(ui.SceneBuilder builder) { void addToScene(ui.SceneBuilder builder) {
builder.addPlatformView( builder.addPlatformView(
...@@ -1043,6 +1066,16 @@ class ContainerLayer extends Layer { ...@@ -1043,6 +1066,16 @@ class ContainerLayer extends Layer {
/// Returns whether this layer has at least one child layer. /// Returns whether this layer has at least one child layer.
bool get hasChildren => _firstChild != null; bool get hasChildren => _firstChild != null;
@override
bool supportsRasterization() {
for (Layer? child = lastChild; child != null; child = child.previousSibling) {
if (!child.supportsRasterization()) {
return false;
}
}
return true;
}
/// Consider this layer as the root and build a scene (a tree of layers) /// Consider this layer as the root and build a scene (a tree of layers)
/// in the engine. /// in the engine.
// The reason this method is in the `ContainerLayer` class rather than // The reason this method is in the `ContainerLayer` class rather than
...@@ -1385,6 +1418,16 @@ class OffsetLayer extends ContainerLayer { ...@@ -1385,6 +1418,16 @@ class OffsetLayer extends ContainerLayer {
properties.add(DiagnosticsProperty<Offset>('offset', offset)); properties.add(DiagnosticsProperty<Offset>('offset', offset));
} }
ui.Scene _createSceneForImage(Rect bounds, { double pixelRatio = 1.0 }) {
assert(bounds != null);
assert(pixelRatio != null);
final ui.SceneBuilder builder = ui.SceneBuilder();
final Matrix4 transform = Matrix4.diagonal3Values(pixelRatio, pixelRatio, 1);
transform.translate(-(bounds.left + offset.dx), -(bounds.top + offset.dy));
builder.pushTransform(transform.storage);
return buildScene(builder);
}
/// Capture an image of the current state of this layer and its children. /// Capture an image of the current state of this layer and its children.
/// ///
/// The returned [ui.Image] has uncompressed raw RGBA bytes, will be offset /// The returned [ui.Image] has uncompressed raw RGBA bytes, will be offset
...@@ -1397,22 +1440,15 @@ class OffsetLayer extends ContainerLayer { ...@@ -1397,22 +1440,15 @@ class OffsetLayer extends ContainerLayer {
/// (the default) will give you a 1:1 mapping between logical pixels and the /// (the default) will give you a 1:1 mapping between logical pixels and the
/// output pixels in the image. /// output pixels in the image.
/// ///
/// This API functions like [toImageSync], except that it only returns after
/// rasterization is complete.
///
/// See also: /// See also:
/// ///
/// * [RenderRepaintBoundary.toImage] for a similar API at the render object level. /// * [RenderRepaintBoundary.toImage] for a similar API at the render object level.
/// * [dart:ui.Scene.toImage] for more information about the image returned. /// * [dart:ui.Scene.toImage] for more information about the image returned.
Future<ui.Image> toImage(Rect bounds, { double pixelRatio = 1.0 }) async { Future<ui.Image> toImage(Rect bounds, { double pixelRatio = 1.0 }) async {
assert(bounds != null); final ui.Scene scene = _createSceneForImage(bounds, pixelRatio: pixelRatio);
assert(pixelRatio != null);
final ui.SceneBuilder builder = ui.SceneBuilder();
final Matrix4 transform = Matrix4.translationValues(
(-bounds.left - offset.dx) * pixelRatio,
(-bounds.top - offset.dy) * pixelRatio,
0.0,
);
transform.scale(pixelRatio, pixelRatio);
builder.pushTransform(transform.storage);
final ui.Scene scene = buildScene(builder);
try { try {
// Size is rounded up to the next pixel to make sure we don't clip off // Size is rounded up to the next pixel to make sure we don't clip off
...@@ -1425,6 +1461,40 @@ class OffsetLayer extends ContainerLayer { ...@@ -1425,6 +1461,40 @@ class OffsetLayer extends ContainerLayer {
scene.dispose(); scene.dispose();
} }
} }
/// Capture an image of the current state of this layer and its children.
///
/// The returned [ui.Image] has uncompressed raw RGBA bytes, will be offset
/// by the top-left corner of [bounds], and have dimensions equal to the size
/// of [bounds] multiplied by [pixelRatio].
///
/// The [pixelRatio] describes the scale between the logical pixels and the
/// size of the output image. It is independent of the
/// [dart:ui.FlutterView.devicePixelRatio] for the device, so specifying 1.0
/// (the default) will give you a 1:1 mapping between logical pixels and the
/// output pixels in the image.
///
/// This API functions like [toImage], except that rasterization begins eagerly
/// on the raster thread and the image is returned before this is completed.
///
/// See also:
///
/// * [RenderRepaintBoundary.toImage] for a similar API at the render object level.
/// * [dart:ui.Scene.toImage] for more information about the image returned.
ui.Image toImageSync(Rect bounds, { double pixelRatio = 1.0 }) {
final ui.Scene scene = _createSceneForImage(bounds, pixelRatio: pixelRatio);
try {
// Size is rounded up to the next pixel to make sure we don't clip off
// anything.
return scene.toImageSync(
(pixelRatio * bounds.width).ceil(),
(pixelRatio * bounds.height).ceil(),
);
} finally {
scene.dispose();
}
}
} }
/// A composite layer that clips its children using a rectangle. /// A composite layer that clips its children using a rectangle.
......
...@@ -12,6 +12,7 @@ abstract class PageRoute<T> extends ModalRoute<T> { ...@@ -12,6 +12,7 @@ abstract class PageRoute<T> extends ModalRoute<T> {
PageRoute({ PageRoute({
super.settings, super.settings,
this.fullscreenDialog = false, this.fullscreenDialog = false,
this.preferRasterization = true,
}); });
/// {@template flutter.widgets.PageRoute.fullscreenDialog} /// {@template flutter.widgets.PageRoute.fullscreenDialog}
...@@ -24,6 +25,9 @@ abstract class PageRoute<T> extends ModalRoute<T> { ...@@ -24,6 +25,9 @@ abstract class PageRoute<T> extends ModalRoute<T> {
/// {@endtemplate} /// {@endtemplate}
final bool fullscreenDialog; final bool fullscreenDialog;
@override
final bool preferRasterization;
@override @override
bool get opaque => true; bool get opaque => true;
...@@ -62,6 +66,7 @@ class PageRouteBuilder<T> extends PageRoute<T> { ...@@ -62,6 +66,7 @@ class PageRouteBuilder<T> extends PageRoute<T> {
this.barrierLabel, this.barrierLabel,
this.maintainState = true, this.maintainState = true,
super.fullscreenDialog, super.fullscreenDialog,
super.preferRasterization = true,
}) : assert(pageBuilder != null), }) : assert(pageBuilder != null),
assert(transitionsBuilder != null), assert(transitionsBuilder != null),
assert(opaque != null), assert(opaque != null),
......
This diff is collapsed.
...@@ -126,6 +126,21 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -126,6 +126,21 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
/// {@endtemplate} /// {@endtemplate}
bool get opaque; bool get opaque;
/// {@template flutter.widgets.TransitionRoute.preferRasterization}
/// Whether the route transition will prefer to animate a rasterized
/// snapshot of the entering/exiting routes.
///
/// When this value is true, certain route transitions (such as the Android
/// zoom page transition) will rasterize the entering and exiting routes.
/// These textures are then animated in place of the underlying widgets to
/// improve performance of the transition.
///
/// Generally this means that animations that occur on the entering/exiting
/// route while the route animation plays may appear frozen - unless they
/// are a hero animation or something that is drawn in a separate overlay.
/// {@endtemplate}
bool get preferRasterization => true;
// This ensures that if we got to the dismissed state while still current, // This ensures that if we got to the dismissed state while still current,
// we will still be disposed when we are eventually popped. // we will still be disposed when we are eventually popped.
// //
...@@ -1719,6 +1734,9 @@ abstract class PopupRoute<T> extends ModalRoute<T> { ...@@ -1719,6 +1734,9 @@ abstract class PopupRoute<T> extends ModalRoute<T> {
@override @override
bool get maintainState => true; bool get maintainState => true;
@override
bool get preferRasterization => false;
} }
/// A [Navigator] observer that notifies [RouteAware]s of changes to the /// A [Navigator] observer that notifies [RouteAware]s of changes to the
......
...@@ -89,6 +89,7 @@ export 'src/widgets/platform_menu_bar.dart'; ...@@ -89,6 +89,7 @@ export 'src/widgets/platform_menu_bar.dart';
export 'src/widgets/platform_view.dart'; export 'src/widgets/platform_view.dart';
export 'src/widgets/preferred_size.dart'; export 'src/widgets/preferred_size.dart';
export 'src/widgets/primary_scroll_controller.dart'; export 'src/widgets/primary_scroll_controller.dart';
export 'src/widgets/raster_widget.dart';
export 'src/widgets/raw_keyboard_listener.dart'; export 'src/widgets/raw_keyboard_listener.dart';
export 'src/widgets/reorderable_list.dart'; export 'src/widgets/reorderable_list.dart';
export 'src/widgets/restoration.dart'; export 'src/widgets/restoration.dart';
......
...@@ -155,32 +155,33 @@ void main() { ...@@ -155,32 +155,33 @@ void main() {
expect(widget1InitialTopLeft == widget1TransientTopLeft, true); expect(widget1InitialTopLeft == widget1TransientTopLeft, true);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('test page transition (_ZoomPageTransition)', (WidgetTester tester) async { testWidgets('test page transition (_ZoomPageTransition) without rasterization', (WidgetTester tester) async {
Iterable<T> findWidgets<T extends Widget>(Finder of) { Iterable<Layer> findLayers(Finder of) {
return tester.widgetList<T>( return tester.layerListOf(
find.ancestor(of: of, matching: find.byType(T)), find.ancestor(of: of, matching: find.byType(RasterWidget)).first,
); );
} }
FadeTransition findForwardFadeTransition(Finder of) { OpacityLayer findForwardFadeTransition(Finder of) {
return findWidgets<FadeTransition>(of).where( return findLayers(of).whereType<OpacityLayer>().first;
(FadeTransition t) => t.opacity.status == AnimationStatus.forward,
).first;
} }
ScaleTransition findForwardScaleTransition(Finder of) { TransformLayer findForwardScaleTransition(Finder of) {
return findWidgets<ScaleTransition>(of).where( return findLayers(of).whereType<TransformLayer>().first;
(ScaleTransition t) => t.scale.status == AnimationStatus.forward,
).first;
} }
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: const Material(child: Text('Page 1')), onGenerateRoute: (RouteSettings settings) {
routes: <String, WidgetBuilder>{ return MaterialPageRoute<void>(
'/next': (BuildContext context) { preferRasterization: false,
return const Material(child: Text('Page 2')); builder: (BuildContext context) {
}, if (settings.name == '/') {
return const Material(child: Text('Page 1'));
}
return const Material(child: Text('Page 2'));
},
);
}, },
), ),
); );
...@@ -189,16 +190,20 @@ void main() { ...@@ -189,16 +190,20 @@ void main() {
await tester.pump(); await tester.pump();
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
ScaleTransition widget1Scale = findForwardScaleTransition(find.text('Page 1')); TransformLayer widget1Scale = findForwardScaleTransition(find.text('Page 1'));
ScaleTransition widget2Scale = findForwardScaleTransition(find.text('Page 2')); TransformLayer widget2Scale = findForwardScaleTransition(find.text('Page 2'));
FadeTransition widget2Opacity = findForwardFadeTransition(find.text('Page 2')); OpacityLayer widget2Opacity = findForwardFadeTransition(find.text('Page 2'));
double getScale(TransformLayer layer) {
return layer.transform!.storage[0];
}
// Page 1 is enlarging, starts from 1.0. // Page 1 is enlarging, starts from 1.0.
expect(widget1Scale.scale.value, greaterThan(1.0)); expect(getScale(widget1Scale), greaterThan(1.0));
// Page 2 is enlarging from the value less than 1.0. // Page 2 is enlarging from the value less than 1.0.
expect(widget2Scale.scale.value, lessThan(1.0)); expect(getScale(widget2Scale), lessThan(1.0));
// Page 2 is becoming none transparent. // Page 2 is becoming none transparent.
expect(widget2Opacity.opacity.value, lessThan(1.0)); expect(widget2Opacity.alpha, lessThan(255));
await tester.pump(const Duration(milliseconds: 250)); await tester.pump(const Duration(milliseconds: 250));
await tester.pump(const Duration(milliseconds: 1)); await tester.pump(const Duration(milliseconds: 1));
...@@ -216,11 +221,11 @@ void main() { ...@@ -216,11 +221,11 @@ void main() {
widget2Opacity = findForwardFadeTransition(find.text('Page 2')); widget2Opacity = findForwardFadeTransition(find.text('Page 2'));
// Page 1 is narrowing down, but still larger than 1.0. // Page 1 is narrowing down, but still larger than 1.0.
expect(widget1Scale.scale.value, greaterThan(1.0)); expect(getScale(widget1Scale), greaterThan(1.0));
// Page 2 is smaller than 1.0. // Page 2 is smaller than 1.0.
expect(widget2Scale.scale.value, lessThan(1.0)); expect(getScale(widget2Scale), lessThan(1.0));
// Page 2 is becoming transparent. // Page 2 is becoming transparent.
expect(widget2Opacity.opacity.value, lessThan(1.0)); expect(widget2Opacity.alpha, lessThan(255));
await tester.pump(const Duration(milliseconds: 200)); await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 1)); await tester.pump(const Duration(milliseconds: 1));
......
...@@ -1679,7 +1679,7 @@ void main() { ...@@ -1679,7 +1679,7 @@ void main() {
// Wait for context menu to be built. // Wait for context menu to be built.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final RenderBox container = tester.renderObject(find.descendant( final RenderBox container = tester.renderObject(find.descendant(
of: find.byType(FadeTransition), of: find.byType(RasterWidget),
matching: find.byType(SizedBox), matching: find.byType(SizedBox),
).first); ).first);
expect(container.size, Size.zero); expect(container.size, Size.zero);
......
...@@ -553,6 +553,24 @@ void main() { ...@@ -553,6 +553,24 @@ void main() {
parent.buildScene(SceneBuilder()); parent.buildScene(SceneBuilder());
}, skip: isBrowser); // TODO(yjbanov): `toImage` doesn't work on the Web: https://github.com/flutter/flutter/issues/49857 }, skip: isBrowser); // TODO(yjbanov): `toImage` doesn't work on the Web: https://github.com/flutter/flutter/issues/49857
test('ContainerLayer.toImageSync can render interior layer', () {
final OffsetLayer parent = OffsetLayer();
final OffsetLayer child = OffsetLayer();
final OffsetLayer grandChild = OffsetLayer();
child.append(grandChild);
parent.append(child);
// This renders the layers and generates engine layers.
parent.buildScene(SceneBuilder());
// Causes grandChild to pass its engine layer as `oldLayer`
grandChild.toImageSync(const Rect.fromLTRB(0, 0, 10, 10));
// Ensure we can render the same scene again after rendering an interior
// layer.
parent.buildScene(SceneBuilder());
}, skip: isBrowser); // TODO(yjbanov): `toImage` doesn't work on the Web: https://github.com/flutter/flutter/issues/49857
test('PictureLayer does not let you call dispose unless refcount is 0', () { test('PictureLayer does not let you call dispose unless refcount is 0', () {
PictureLayer layer = PictureLayer(Rect.zero); PictureLayer layer = PictureLayer(Rect.zero);
expect(layer.debugHandleCount, 0); expect(layer.debugHandleCount, 0);
...@@ -980,6 +998,35 @@ void main() { ...@@ -980,6 +998,35 @@ void main() {
root.dispose(); root.dispose();
expect(() => callback(), returnsNormally); expect(() => callback(), returnsNormally);
}); });
test('Layer types that support rasterization', () {
// Supported.
final OffsetLayer offsetLayer = OffsetLayer();
final OpacityLayer opacityLayer = OpacityLayer();
final ClipRectLayer clipRectLayer = ClipRectLayer();
final ClipRRectLayer clipRRectLayer = ClipRRectLayer();
final ImageFilterLayer imageFilterLayer = ImageFilterLayer();
final BackdropFilterLayer backdropFilterLayer = BackdropFilterLayer();
final PhysicalModelLayer physicalModelLayer = PhysicalModelLayer();
final ColorFilterLayer colorFilterLayer = ColorFilterLayer();
final ShaderMaskLayer shaderMaskLayer = ShaderMaskLayer();
expect(offsetLayer.supportsRasterization(), true);
expect(opacityLayer.supportsRasterization(), true);
expect(clipRectLayer.supportsRasterization(), true);
expect(clipRRectLayer.supportsRasterization(), true);
expect(imageFilterLayer.supportsRasterization(), true);
expect(backdropFilterLayer.supportsRasterization(), true);
expect(physicalModelLayer.supportsRasterization(), true);
expect(colorFilterLayer.supportsRasterization(), true);
expect(shaderMaskLayer.supportsRasterization(), true);
// Unsupported.
final TextureLayer textureLayer = TextureLayer(rect: Rect.zero, textureId: 1);
final PlatformViewLayer platformViewLayer = PlatformViewLayer(rect: Rect.zero, viewId: 1);
expect(textureLayer.supportsRasterization(), false);
expect(platformViewLayer.supportsRasterization(), false);
});
} }
class FakeEngineLayer extends Fake implements EngineLayer { class FakeEngineLayer extends Fake implements EngineLayer {
......
...@@ -4089,7 +4089,7 @@ class ZeroDurationPage extends Page<void> { ...@@ -4089,7 +4089,7 @@ class ZeroDurationPage extends Page<void> {
class ZeroDurationPageRoute extends PageRoute<void> { class ZeroDurationPageRoute extends PageRoute<void> {
ZeroDurationPageRoute({required ZeroDurationPage page}) ZeroDurationPageRoute({required ZeroDurationPage page})
: super(settings: page); : super(settings: page, preferRasterization: false);
@override @override
Duration get transitionDuration => Duration.zero; Duration get transitionDuration => Duration.zero;
......
This diff is collapsed.
...@@ -92,6 +92,21 @@ abstract class WidgetController { ...@@ -92,6 +92,21 @@ abstract class WidgetController {
}); });
} }
/// Find all layers that are children of the provided [finder].
///
/// The [finder] must match exactly one element.
Iterable<Layer> layerListOf(Finder finder) {
TestAsyncUtils.guardSync();
final Element element = finder.evaluate().single;
final RenderObject object = element.renderObject!;
RenderObject current = object;
while (current.debugLayer == null) {
current = current.parent! as RenderObject;
}
final ContainerLayer layer = current.debugLayer!;
return _walkLayers(layer);
}
/// All elements currently in the widget tree (lazy pre-order traversal). /// All elements currently in the widget tree (lazy pre-order traversal).
/// ///
/// The returned iterable is lazy. It does not walk the entire widget tree /// The returned iterable is lazy. It does not walk the entire widget tree
......
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