Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
9ae37030
Unverified
Commit
9ae37030
authored
Jul 22, 2022
by
Jonah Williams
Committed by
GitHub
Jul 22, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use toPictureSync for faster zoom page transition (#106621)
parent
9f7033a9
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
1487 additions
and
86 deletions
+1487
-86
route.dart
packages/flutter/lib/src/cupertino/route.dart
+7
-1
page.dart
packages/flutter/lib/src/material/page.dart
+7
-1
page_transitions_theme.dart
...ages/flutter/lib/src/material/page_transitions_theme.dart
+338
-45
layer.dart
packages/flutter/lib/src/rendering/layer.dart
+81
-11
pages.dart
packages/flutter/lib/src/widgets/pages.dart
+5
-0
raster_widget.dart
packages/flutter/lib/src/widgets/raster_widget.dart
+458
-0
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+18
-0
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
page_test.dart
packages/flutter/test/material/page_test.dart
+31
-26
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+1
-1
layers_test.dart
packages/flutter/test/rendering/layers_test.dart
+47
-0
navigator_test.dart
packages/flutter/test/widgets/navigator_test.dart
+1
-1
raster_widget_test.dart
packages/flutter/test/widgets/raster_widget_test.dart
+477
-0
controller.dart
packages/flutter_test/lib/src/controller.dart
+15
-0
No files found.
packages/flutter/lib/src/cupertino/route.dart
View file @
9ae37030
...
@@ -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
);
}
}
}
}
...
...
packages/flutter/lib/src/material/page.dart
View file @
9ae37030
...
@@ -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
);
...
...
packages/flutter/lib/src/material/page_transitions_theme.dart
View file @
9ae37030
...
@@ -2,8 +2,11 @@
...
@@ -2,8 +2,11 @@
// 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
'dart:ui'
as
ui
;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'colors.dart'
;
import
'colors.dart'
;
import
'theme.dart'
;
import
'theme.dart'
;
...
@@ -153,6 +156,7 @@ class _ZoomPageTransition extends StatelessWidget {
...
@@ -153,6 +156,7 @@ class _ZoomPageTransition extends StatelessWidget {
const
_ZoomPageTransition
({
const
_ZoomPageTransition
({
required
this
.
animation
,
required
this
.
animation
,
required
this
.
secondaryAnimation
,
required
this
.
secondaryAnimation
,
required
this
.
preferRasterization
,
this
.
child
,
this
.
child
,
})
:
assert
(
animation
!=
null
),
})
:
assert
(
animation
!=
null
),
assert
(
secondaryAnimation
!=
null
);
assert
(
secondaryAnimation
!=
null
);
...
@@ -190,6 +194,14 @@ class _ZoomPageTransition extends StatelessWidget {
...
@@ -190,6 +194,14 @@ class _ZoomPageTransition extends StatelessWidget {
/// property when the [_ZoomPageTransition] is used as a page transition.
/// property when the [_ZoomPageTransition] is used as a page transition.
final
Animation
<
double
>
secondaryAnimation
;
final
Animation
<
double
>
secondaryAnimation
;
/// Whether the [RasterWidget] based-rasterized strategy for the zoom page transition
/// will be used.
///
/// Notably, this improves performance by disabling animations on both the outgoing and
/// incoming route. This also implies that ink-splashes or similar animations will
/// not animate during the transition.
final
bool
preferRasterization
;
/// The widget below this widget in the tree.
/// The widget below this widget in the tree.
///
///
/// This widget will transition in and out as driven by [animation] and
/// This widget will transition in and out as driven by [animation] and
...
@@ -207,6 +219,7 @@ class _ZoomPageTransition extends StatelessWidget {
...
@@ -207,6 +219,7 @@ class _ZoomPageTransition extends StatelessWidget {
)
{
)
{
return
_ZoomEnterTransition
(
return
_ZoomEnterTransition
(
animation:
animation
,
animation:
animation
,
preferRasterization:
preferRasterization
,
child:
child
,
child:
child
,
);
);
},
},
...
@@ -217,6 +230,7 @@ class _ZoomPageTransition extends StatelessWidget {
...
@@ -217,6 +230,7 @@ class _ZoomPageTransition extends StatelessWidget {
)
{
)
{
return
_ZoomExitTransition
(
return
_ZoomExitTransition
(
animation:
animation
,
animation:
animation
,
preferRasterization:
preferRasterization
,
reverse:
true
,
reverse:
true
,
child:
child
,
child:
child
,
);
);
...
@@ -230,6 +244,7 @@ class _ZoomPageTransition extends StatelessWidget {
...
@@ -230,6 +244,7 @@ class _ZoomPageTransition extends StatelessWidget {
)
{
)
{
return
_ZoomEnterTransition
(
return
_ZoomEnterTransition
(
animation:
animation
,
animation:
animation
,
preferRasterization:
preferRasterization
,
reverse:
true
,
reverse:
true
,
child:
child
,
child:
child
,
);
);
...
@@ -241,6 +256,7 @@ class _ZoomPageTransition extends StatelessWidget {
...
@@ -241,6 +256,7 @@ class _ZoomPageTransition extends StatelessWidget {
)
{
)
{
return
_ZoomExitTransition
(
return
_ZoomExitTransition
(
animation:
animation
,
animation:
animation
,
preferRasterization:
preferRasterization
,
child:
child
,
child:
child
,
);
);
},
},
...
@@ -250,18 +266,30 @@ class _ZoomPageTransition extends StatelessWidget {
...
@@ -250,18 +266,30 @@ class _ZoomPageTransition extends StatelessWidget {
}
}
}
}
class
_ZoomEnterTransition
extends
State
less
Widget
{
class
_ZoomEnterTransition
extends
State
ful
Widget
{
const
_ZoomEnterTransition
({
const
_ZoomEnterTransition
({
required
this
.
animation
,
required
this
.
animation
,
this
.
reverse
=
false
,
this
.
reverse
=
false
,
required
this
.
preferRasterization
,
this
.
child
,
this
.
child
,
})
:
assert
(
animation
!=
null
),
})
:
assert
(
animation
!=
null
),
assert
(
reverse
!=
null
);
assert
(
reverse
!=
null
);
final
Animation
<
double
>
animation
;
final
Animation
<
double
>
animation
;
final
Widget
?
child
;
final
Widget
?
child
;
final
bool
preferRasterization
;
final
bool
reverse
;
final
bool
reverse
;
@override
State
<
_ZoomEnterTransition
>
createState
()
=>
_ZoomEnterTransitionState
();
}
class
_ZoomEnterTransitionState
extends
State
<
_ZoomEnterTransition
>
with
_ZoomTransitionBase
{
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
bool
get
allowRasterization
=>
!
kIsWeb
&&
widget
.
preferRasterization
;
late
_ZoomEnterTransitionDelegate
delegate
;
static
final
Animatable
<
double
>
_fadeInTransition
=
Tween
<
double
>(
static
final
Animatable
<
double
>
_fadeInTransition
=
Tween
<
double
>(
begin:
0.0
,
begin:
0.0
,
end:
1.00
,
end:
1.00
,
...
@@ -282,60 +310,93 @@ class _ZoomEnterTransition extends StatelessWidget {
...
@@ -282,60 +310,93 @@ class _ZoomEnterTransition extends StatelessWidget {
end:
0.60
,
end:
0.60
,
).
chain
(
CurveTween
(
curve:
const
Interval
(
0.2075
,
0.4175
)));
).
chain
(
CurveTween
(
curve:
const
Interval
(
0.2075
,
0.4175
)));
@override
void
_updateAnimations
()
{
Widget
build
(
BuildContext
context
)
{
fadeTransition
=
widget
.
reverse
double
opacity
=
0
;
// The transition's scrim opacity only increases on the forward transition.
// In the reverse transition, the opacity should always be 0.0.
//
// Therefore, we need to only apply the scrim opacity animation when
// the transition is running forwards.
//
// The reason that we check that the animation's status is not `completed`
// instead of checking that it is `forward` is that this allows
// the interrupted reversal of the forward transition to smoothly fade
// the scrim away. This prevents a disjointed removal of the scrim.
if
(!
reverse
&&
animation
.
status
!=
AnimationStatus
.
completed
)
{
opacity
=
_scrimOpacityTween
.
evaluate
(
animation
)!;
}
final
Animation
<
double
>
fadeTransition
=
reverse
?
kAlwaysCompleteAnimation
?
kAlwaysCompleteAnimation
:
_fadeInTransition
.
animate
(
animation
);
:
_fadeInTransition
.
animate
(
widget
.
animation
);
final
Animation
<
double
>
scaleTransition
=
(
reverse
scaleTransition
=
(
widget
.
reverse
?
_scaleDownTransition
?
_scaleDownTransition
:
_scaleUpTransition
:
_scaleUpTransition
).
animate
(
animation
);
).
animate
(
widget
.
animation
);
return
AnimatedBuilder
(
widget
.
animation
.
addListener
(
onAnimationValueChange
);
animation:
animation
,
widget
.
animation
.
addStatusListener
(
onAnimationStatusChange
);
builder:
(
BuildContext
context
,
Widget
?
child
)
{
}
return
ColoredBox
(
color:
Colors
.
black
.
withOpacity
(
opacity
),
@override
child:
child
,
void
initState
()
{
_updateAnimations
();
delegate
=
_ZoomEnterTransitionDelegate
(
reverse:
widget
.
reverse
,
fade:
fadeTransition
,
scale:
scaleTransition
,
animation:
widget
.
animation
,
);
);
},
super
.
initState
();
child:
FadeTransition
(
}
opacity:
fadeTransition
,
child:
ScaleTransition
(
scale:
scaleTransition
,
child:
child
),
@override
),
void
didUpdateWidget
(
covariant
_ZoomEnterTransition
oldWidget
)
{
if
(
oldWidget
.
reverse
!=
widget
.
reverse
||
oldWidget
.
animation
!=
widget
.
animation
)
{
oldWidget
.
animation
.
removeListener
(
onAnimationValueChange
);
oldWidget
.
animation
.
removeStatusListener
(
onAnimationStatusChange
);
_updateAnimations
();
delegate
.
dispose
();
delegate
=
_ZoomEnterTransitionDelegate
(
reverse:
widget
.
reverse
,
fade:
fadeTransition
,
scale:
scaleTransition
,
animation:
widget
.
animation
,
);
}
super
.
didUpdateWidget
(
oldWidget
);
}
@override
void
dispose
()
{
widget
.
animation
.
removeListener
(
onAnimationValueChange
);
widget
.
animation
.
removeStatusListener
(
onAnimationStatusChange
);
delegate
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
fallback:
delegate
,
mode:
allowRasterization
?
RasterizeMode
.
enabled
:
RasterizeMode
.
fallback
,
child:
widget
.
child
,
);
);
}
}
}
}
class
_ZoomExitTransition
extends
State
less
Widget
{
class
_ZoomExitTransition
extends
State
ful
Widget
{
const
_ZoomExitTransition
({
const
_ZoomExitTransition
({
required
this
.
animation
,
required
this
.
animation
,
this
.
reverse
=
false
,
this
.
reverse
=
false
,
required
this
.
preferRasterization
,
this
.
child
,
this
.
child
,
})
:
assert
(
animation
!=
null
),
})
:
assert
(
animation
!=
null
),
assert
(
reverse
!=
null
);
assert
(
reverse
!=
null
);
final
Animation
<
double
>
animation
;
final
Animation
<
double
>
animation
;
final
bool
preferRasterization
;
final
bool
reverse
;
final
bool
reverse
;
final
Widget
?
child
;
final
Widget
?
child
;
@override
State
<
_ZoomExitTransition
>
createState
()
=>
_ZoomExitTransitionState
();
}
class
_ZoomExitTransitionState
extends
State
<
_ZoomExitTransition
>
with
_ZoomTransitionBase
{
late
_ZoomExitTransitionDelegate
delegate
;
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
bool
get
allowRasterization
=>
!
kIsWeb
&&
widget
.
preferRasterization
;
static
final
Animatable
<
double
>
_fadeOutTransition
=
Tween
<
double
>(
static
final
Animatable
<
double
>
_fadeOutTransition
=
Tween
<
double
>(
begin:
1.0
,
begin:
1.0
,
end:
0.0
,
end:
0.0
,
...
@@ -351,19 +412,62 @@ class _ZoomExitTransition extends StatelessWidget {
...
@@ -351,19 +412,62 @@ class _ZoomExitTransition extends StatelessWidget {
end:
0.90
,
end:
0.90
,
).
chain
(
_ZoomPageTransition
.
_scaleCurveSequence
);
).
chain
(
_ZoomPageTransition
.
_scaleCurveSequence
);
@override
void
_updateAnimations
()
{
Widget
build
(
BuildContext
context
)
{
fadeTransition
=
widget
.
reverse
final
Animation
<
double
>
fadeTransition
=
reverse
?
_fadeOutTransition
.
animate
(
widget
.
animation
)
?
_fadeOutTransition
.
animate
(
animation
)
:
kAlwaysCompleteAnimation
;
:
kAlwaysCompleteAnimation
;
final
Animation
<
double
>
scaleTransition
=
(
reverse
scaleTransition
=
(
widget
.
reverse
?
_scaleDownTransition
?
_scaleDownTransition
:
_scaleUpTransition
:
_scaleUpTransition
).
animate
(
animation
);
).
animate
(
widget
.
animation
);
widget
.
animation
.
addListener
(
onAnimationValueChange
);
widget
.
animation
.
addStatusListener
(
onAnimationStatusChange
);
}
@override
void
initState
()
{
_updateAnimations
();
delegate
=
_ZoomExitTransitionDelegate
(
reverse:
widget
.
reverse
,
fade:
fadeTransition
,
scale:
scaleTransition
,
);
super
.
initState
();
}
@override
void
didUpdateWidget
(
covariant
_ZoomExitTransition
oldWidget
)
{
if
(
oldWidget
.
reverse
!=
widget
.
reverse
||
oldWidget
.
animation
!=
widget
.
animation
)
{
oldWidget
.
animation
.
removeListener
(
onAnimationValueChange
);
oldWidget
.
animation
.
removeStatusListener
(
onAnimationStatusChange
);
_updateAnimations
();
delegate
.
dispose
();
delegate
=
_ZoomExitTransitionDelegate
(
reverse:
widget
.
reverse
,
fade:
fadeTransition
,
scale:
scaleTransition
,
);
}
super
.
didUpdateWidget
(
oldWidget
);
}
return
FadeTransition
(
@override
opacity:
fadeTransition
,
void
dispose
()
{
child:
ScaleTransition
(
scale:
scaleTransition
,
child:
child
),
widget
.
animation
.
removeListener
(
onAnimationValueChange
);
widget
.
animation
.
removeStatusListener
(
onAnimationStatusChange
);
delegate
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
fallback:
delegate
,
mode:
allowRasterization
?
RasterizeMode
.
enabled
:
RasterizeMode
.
fallback
,
child:
widget
.
child
,
);
);
}
}
}
}
...
@@ -498,6 +602,7 @@ class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
...
@@ -498,6 +602,7 @@ class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
return
_ZoomPageTransition
(
return
_ZoomPageTransition
(
animation:
animation
,
animation:
animation
,
secondaryAnimation:
secondaryAnimation
,
secondaryAnimation:
secondaryAnimation
,
preferRasterization:
route
?.
preferRasterization
??
true
,
child:
child
,
child:
child
,
);
);
}
}
...
@@ -629,3 +734,191 @@ class PageTransitionsTheme with Diagnosticable {
...
@@ -629,3 +734,191 @@ class PageTransitionsTheme with Diagnosticable {
);
);
}
}
}
}
// Take an image and draw it centered and scaled. The image is already scaled by the [pixelRatio].
void
_drawImageScaledAndCentered
(
PaintingContext
context
,
ui
.
Image
image
,
double
scale
,
double
opacity
,
double
pixelRatio
)
{
if
(
scale
<=
0.0
||
opacity
<=
0.0
)
{
return
;
}
final
Paint
paint
=
Paint
()
..
filterQuality
=
ui
.
FilterQuality
.
low
..
color
=
Color
.
fromRGBO
(
0
,
0
,
0
,
opacity
);
final
double
logicalWidth
=
image
.
width
/
pixelRatio
;
final
double
logicalHeight
=
image
.
height
/
pixelRatio
;
final
double
scaledLogicalWidth
=
logicalWidth
*
scale
;
final
double
scaledLogicalHeight
=
logicalHeight
*
scale
;
final
double
left
=
(
logicalWidth
-
scaledLogicalWidth
)
/
2
;
final
double
top
=
(
logicalHeight
-
scaledLogicalHeight
)
/
2
;
final
Rect
dst
=
Rect
.
fromLTWH
(
left
,
top
,
scaledLogicalWidth
,
scaledLogicalHeight
);
context
.
canvas
.
drawImageRect
(
image
,
Rect
.
fromLTWH
(
0
,
0
,
image
.
width
.
toDouble
(),
image
.
height
.
toDouble
()),
dst
,
paint
);
}
void
_updateScaledTransform
(
Matrix4
transform
,
double
scale
,
Size
size
)
{
transform
.
setIdentity
();
if
(
scale
==
1.0
)
{
return
;
}
transform
.
scale
(
scale
,
scale
);
final
double
dx
=
((
size
.
width
*
scale
)
-
size
.
width
)
/
2
;
final
double
dy
=
((
size
.
height
*
scale
)
-
size
.
height
)
/
2
;
transform
.
translate
(-
dx
,
-
dy
);
}
mixin
_ZoomTransitionBase
{
// Don't rasterize if:
// 1. Rasterization is disabled by the platform.
// 2. The animation is paused/stopped.
// 3. The values of the scale/fade transition do not
// benefit from rasterization.
final
RasterWidgetController
controller
=
RasterWidgetController
();
late
Animation
<
double
>
fadeTransition
;
late
Animation
<
double
>
scaleTransition
;
void
onAnimationValueChange
()
{
if
((
scaleTransition
.
value
==
1.0
)
&&
(
fadeTransition
.
value
==
0.0
||
fadeTransition
.
value
==
1.0
))
{
controller
.
rasterize
=
false
;
}
else
{
controller
.
rasterize
=
true
;
}
}
void
onAnimationStatusChange
(
AnimationStatus
status
)
{
switch
(
status
)
{
case
AnimationStatus
.
dismissed
:
case
AnimationStatus
.
completed
:
controller
.
rasterize
=
false
;
break
;
case
AnimationStatus
.
forward
:
case
AnimationStatus
.
reverse
:
controller
.
rasterize
=
true
;
break
;
}
}
}
class
_ZoomEnterTransitionDelegate
extends
RasterWidgetDelegate
implements
RasterWidgetFallbackDelegate
{
_ZoomEnterTransitionDelegate
({
required
this
.
reverse
,
required
this
.
scale
,
required
this
.
fade
,
required
this
.
animation
,
})
{
animation
.
addListener
(
notifyListeners
);
scale
.
addListener
(
notifyListeners
);
fade
.
addListener
(
notifyListeners
);
}
final
bool
reverse
;
final
Animation
<
double
>
animation
;
final
Animation
<
double
>
scale
;
final
Animation
<
double
>
fade
;
final
Matrix4
_transform
=
Matrix4
.
zero
();
final
LayerHandle
<
OpacityLayer
>
_opacityHandle
=
LayerHandle
<
OpacityLayer
>();
final
LayerHandle
<
TransformLayer
>
_transformHandler
=
LayerHandle
<
TransformLayer
>();
void
_drawScrim
(
PaintingContext
context
,
Offset
offset
,
Size
size
)
{
double
scrimOpacity
=
0.0
;
// The transition's scrim opacity only increases on the forward transition.
// In the reverse transition, the opacity should always be 0.0.
//
// Therefore, we need to only apply the scrim opacity animation when
// the transition is running forwards.
//
// The reason that we check that the animation's status is not `completed`
// instead of checking that it is `forward` is that this allows
// the interrupted reversal of the forward transition to smoothly fade
// the scrim away. This prevents a disjointed removal of the scrim.
if
(!
reverse
&&
animation
.
status
!=
AnimationStatus
.
completed
)
{
scrimOpacity
=
_ZoomEnterTransitionState
.
_scrimOpacityTween
.
evaluate
(
animation
)!;
}
assert
(!
reverse
||
scrimOpacity
==
0.0
);
if
(
scrimOpacity
>
0.0
)
{
context
.
canvas
.
drawRect
(
offset
&
size
,
Paint
()..
color
=
Colors
.
black
.
withOpacity
(
scrimOpacity
),
);
}
}
@override
void
paintFallback
(
PaintingContext
context
,
ui
.
Offset
offset
,
Size
size
,
PaintingContextCallback
painter
)
{
_drawScrim
(
context
,
offset
,
size
);
_updateScaledTransform
(
_transform
,
scale
.
value
,
size
);
_transformHandler
.
layer
=
context
.
pushTransform
(
true
,
offset
,
_transform
,
(
PaintingContext
context
,
Offset
offset
)
{
_opacityHandle
.
layer
=
context
.
pushOpacity
(
offset
,
(
fade
.
value
*
255
).
round
(),
painter
,
oldLayer:
_opacityHandle
.
layer
);
},
oldLayer:
_transformHandler
.
layer
);
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
,
Size
size
,
ui
.
Image
image
,
double
pixelRatio
)
{
_drawScrim
(
context
,
offset
,
size
);
_drawImageScaledAndCentered
(
context
,
image
,
scale
.
value
,
fade
.
value
,
pixelRatio
);
}
@override
void
dispose
()
{
animation
.
removeListener
(
notifyListeners
);
scale
.
removeListener
(
notifyListeners
);
fade
.
removeListener
(
notifyListeners
);
_opacityHandle
.
layer
=
null
;
_transformHandler
.
layer
=
null
;
super
.
dispose
();
}
@override
bool
shouldRepaint
(
covariant
_ZoomEnterTransitionDelegate
oldDelegate
)
{
return
oldDelegate
.
reverse
!=
reverse
||
oldDelegate
.
animation
.
value
!=
animation
.
value
||
oldDelegate
.
scale
.
value
!=
scale
.
value
||
oldDelegate
.
fade
.
value
!=
fade
.
value
;
}
}
class
_ZoomExitTransitionDelegate
extends
RasterWidgetDelegate
implements
RasterWidgetFallbackDelegate
{
_ZoomExitTransitionDelegate
({
required
this
.
reverse
,
required
this
.
scale
,
required
this
.
fade
,
})
{
scale
.
addListener
(
notifyListeners
);
fade
.
addListener
(
notifyListeners
);
}
final
bool
reverse
;
final
Animation
<
double
>
scale
;
final
Animation
<
double
>
fade
;
final
Matrix4
_transform
=
Matrix4
.
zero
();
final
LayerHandle
<
OpacityLayer
>
_opacityHandle
=
LayerHandle
<
OpacityLayer
>();
final
LayerHandle
<
TransformLayer
>
_transformHandler
=
LayerHandle
<
TransformLayer
>();
@override
void
paint
(
PaintingContext
context
,
Offset
offset
,
Size
size
,
ui
.
Image
image
,
double
pixelRatio
)
{
_drawImageScaledAndCentered
(
context
,
image
,
scale
.
value
,
fade
.
value
,
pixelRatio
);
}
@override
void
paintFallback
(
PaintingContext
context
,
ui
.
Offset
offset
,
Size
size
,
PaintingContextCallback
painter
)
{
_updateScaledTransform
(
_transform
,
scale
.
value
,
size
);
_transformHandler
.
layer
=
context
.
pushTransform
(
true
,
offset
,
_transform
,
(
PaintingContext
context
,
Offset
offset
)
{
_opacityHandle
.
layer
=
context
.
pushOpacity
(
offset
,
(
fade
.
value
*
255
).
round
(),
painter
,
oldLayer:
_opacityHandle
.
layer
);
},
oldLayer:
_transformHandler
.
layer
);
}
@override
bool
shouldRepaint
(
covariant
_ZoomExitTransitionDelegate
oldDelegate
)
{
return
oldDelegate
.
reverse
!=
reverse
||
oldDelegate
.
fade
.
value
!=
fade
.
value
||
oldDelegate
.
scale
.
value
!=
scale
.
value
;
}
@override
void
dispose
()
{
_opacityHandle
.
layer
=
null
;
_transformHandler
.
layer
=
null
;
scale
.
removeListener
(
notifyListeners
);
fade
.
removeListener
(
notifyListeners
);
super
.
dispose
();
}
}
packages/flutter/lib/src/rendering/layer.dart
View file @
9ae37030
...
@@ -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.
...
...
packages/flutter/lib/src/widgets/pages.dart
View file @
9ae37030
...
@@ -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
),
...
...
packages/flutter/lib/src/widgets/raster_widget.dart
0 → 100644
View file @
9ae37030
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:ui'
as
ui
;
import
'package:flutter/rendering.dart'
;
import
'basic.dart'
;
import
'framework.dart'
;
import
'media_query.dart'
;
/// Controls how the [RasterWidget] paints its children via the [RasterWidgetController].
enum
RasterizeMode
{
/// the children are rasterized, but only if all descendants can be rasterized.
///
/// This setting is the default state of the [RasterWidgetController].
///
/// If there is a platform view in the children of a raster widget
/// and a [RasterWidgetFallbackDelegate]h as been provided to the raster_widget,
/// this fallback delegate will be used to render the children instead of the image.
/// If there is no fallback delegate, an excpetion will be thrown
enabled
,
/// The children are rasterized and any child platform views are ignored.
///
/// In this state a [RasterWidgetFallbackDelegate] is never used. Generally this
/// can be useful if there is a platform view descendant that does not need to
/// be included in the raster.
forced
,
/// the children are not rasterized and the [RasterWidgetFallbackDelegate],
/// if provided, is used to draw the children.
///
///
fallback
,
}
/// A controller for the [RasterWidget] that controls when the child image is displayed
/// and when to regenerated the child image.
///
/// When the value of [rasterize] is true, the [RasterWidget] will paint the child
/// widgets based on the [RasterizeMode] of the raster widget.
///
/// To force [RasterWidget] to recreate the child image, call [clear].
class
RasterWidgetController
extends
ChangeNotifier
{
/// Create a new [RasterWidgetController].
///
/// By default, [rasterize] is `false` and cannot be `null`.
RasterWidgetController
({
bool
rasterize
=
false
,
})
:
_rasterize
=
rasterize
;
/// Reset the raster held by any listening [RasterWidget].
///
/// This has no effect if [rasterize] is `false`.
void
clear
()
{
notifyListeners
();
}
/// Whether a rasterized version of this render objects child is drawn in
/// place of the child.
bool
get
rasterize
=>
_rasterize
;
bool
_rasterize
;
set
rasterize
(
bool
value
)
{
if
(
value
==
rasterize
)
{
return
;
}
_rasterize
=
value
;
notifyListeners
();
}
}
/// A widget that replaces its child with a rasterized version of the child.
///
/// By default, the child is drawn as is. The default [delegate] simply scales
/// down the image by the current device pixel ratio and paints it into the
/// canvas. How this image is drawn can be customized by providing a new
/// subclass of [RasterWidgetDelegate] to the [delegate] argument.
///
/// Caveats:
///
/// The contents of platform views cannot be captured by a raster
/// widget. If a platform view is encountered, then the raster widget will
/// determine how to render its children based on the [RasterizeMode]. This
/// defaults to [RasterizeMode.enabled] which will throw an exception if a platform
/// view is encountered.
///
/// This widget is not supported on the HTML backend of Flutter for the web.
class
RasterWidget
extends
SingleChildRenderObjectWidget
{
/// Create a new [RasterWidget].
///
/// The [controller] and [child] arguments are required.
const
RasterWidget
({
super
.
key
,
this
.
delegate
=
const
_RasterDefaultDelegate
(),
this
.
fallback
,
this
.
mode
=
RasterizeMode
.
enabled
,
required
this
.
controller
,
required
super
.
child
});
/// A delegate that allows customization of how the image is painted.
///
/// If not provided, defaults to a delegate which paints the child as is.
final
RasterWidgetDelegate
delegate
;
/// The controller that determines when to display the children as an image.
final
RasterWidgetController
controller
;
/// A fallback delegate which is used if the child layers contains a platform view.
final
RasterWidgetFallbackDelegate
?
fallback
;
/// Configuration that controls how the raster widget decides to draw its children.
///
/// Defaults to [RasterizeMode.enabled], which throws an error when a platform view
/// or other un-rasterizable view is encountered.
///
/// See [RasterizeMode] for more information.
final
RasterizeMode
mode
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
RenderRasterWidget
(
delegate:
delegate
,
controller:
controller
,
fallback:
fallback
,
mode:
mode
,
devicePixelRatio:
MediaQuery
.
maybeOf
(
context
)?.
devicePixelRatio
??
1.0
,
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
covariant
RenderRasterWidget
renderObject
)
{
renderObject
..
delegate
=
delegate
..
controller
=
controller
..
fallback
=
fallback
..
mode
=
mode
..
devicePixelRatio
=
MediaQuery
.
maybeOf
(
context
)?.
devicePixelRatio
??
1.0
;
}
}
/// A delegate which the [RasterWidget] can use to fallback to regular rendering
/// if a platform view is present in the layer tree.
///
/// Consumers of [RasterWidget] should almost never use this delegate. For the most part,
/// the raster widget only functions as a performance improvement. If a platform view is
/// present, the performance improving qualities aren't possible and using this API is
/// pointless.
///
/// Instead, this interface is useful if a generic/reusable widget is being created which
/// may include a platform view and it needs to handle this transparently. For example, the
/// framework uses this for the zoom page transition so that navigating to a page shows the same
/// animation whether or not there is a platform view.
abstract
class
RasterWidgetFallbackDelegate
{
/// const constructor so that subclasses can be const.
const
RasterWidgetFallbackDelegate
();
/// Paint the child via [painter], applying any effects that would have been painted
/// with the [RasterWidgetDelegate].
///
/// The [offset] and [size] are the location and dimensions of the render object.
void
paintFallback
(
PaintingContext
context
,
Offset
offset
,
Size
size
,
PaintingContextCallback
painter
);
}
/// A delegate used to draw the image representing the rasterized child.
///
/// The delegate can call [notifyListeners] to have the raster widget
/// re-paint (re-using the same raster). This allows animations to be connected
/// to the raster and performed without re-rasterization of children. For
/// certain scale or perspective changing transforms, such as a rotation, this
/// can be significantly faster than performing the same animation at the
/// widget level.
///
/// By default, the [RasterWidget] includes a delegate that draws the child raster
/// exactly as the child widgets would have been drawn. Nevertheless, this can
/// also be used to efficiently transform the child raster and apply complex paint
/// effects.
///
/// {@tool snippet}
///
/// The following method shows how to efficiently rotate the child raster.
///
/// ```dart
/// void paint(PaintingContext context, Offset offset, Size size, ui.Image image, double pixelRatio) {
/// const double radians = 0.5; // Could be driven by an animation.
/// final Matrix4 transform = Matrix4.rotationZ(radians);
/// context.canvas.transform(transform.storage);
/// final Rect src = Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble());
/// final Rect dst = Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height);
/// final Paint paint = Paint()
/// ..filterQuality = FilterQuality.low;
/// context.canvas.drawImageRect(image, src, dst, paint);
/// }
/// ```
/// {@end-tool}
abstract
class
RasterWidgetDelegate
extends
ChangeNotifier
{
/// Called whenever the [image] that represents a [RasterWidget]s child should be painted.
///
/// The image is rasterized at the physical pixel resolution and should be scaled down by
/// [pixelRatio] to account for device independent pixels.
///
/// There is no offset given in this paint method, as the parent is an [OffsetLayer] all
/// offsets are [Offset.zero].
///
/// {@tool snippet}
///
/// The follow method shows how the default implementation of the delegate used by the
/// [RasterWidget] paints the child image. This must account for the fact that the image
/// width and height will be given in physical pixels, while the image must be painted with
/// device independent pixels. That is, the width and height of the image is the widget and
/// height of the provided `size`, multiplied by the `pixelRatio`:
///
/// ```dart
/// void paint(PaintingContext context, Offset offset, Size size, ui.Image image, double pixelRatio) {
/// final Rect src = Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble());
/// final Rect dst = Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height);
/// final Paint paint = Paint()
/// ..filterQuality = FilterQuality.low;
/// context.canvas.drawImageRect(image, src, dst, paint);
/// }
/// ```
/// {@end-tool}
void
paint
(
PaintingContext
context
,
Offset
offset
,
Size
size
,
ui
.
Image
image
,
double
pixelRatio
);
/// Called whenever a new instance of the raster widget delegate class is
/// provided to the [RenderRasterWidget] object, or any time that a new
/// [RasterWidgetDelegate] object is created with a new instance of the
/// delegate class (which amounts to the same thing, because the latter is
/// implemented in terms of the former).
///
/// If the new instance represents different information than the old
/// instance, then the method should return true, otherwise it should return
/// false.
///
/// If the method returns false, then the [paint] call might be optimized
/// away.
///
/// It's possible that the [paint] method will get called even if
/// [shouldRepaint] returns false (e.g. if an ancestor or descendant needed to
/// be repainted). It's also possible that the [paint] method will get called
/// without [shouldRepaint] being called at all (e.g. if the box changes
/// size).
///
/// Changing the delegate will not cause the child image retained by the
/// [RenderRasterWidget] to be updated. Instead, [RasterWidgetController.clear] can
/// be used to force the generation of a new image.
///
/// The `oldDelegate` argument will never be null.
bool
shouldRepaint
(
covariant
RasterWidgetDelegate
oldDelegate
);
}
/// A render object that draws its child as a [ui.Image].
class
RenderRasterWidget
extends
RenderProxyBox
{
/// Create a new [RenderRasterWidget].
RenderRasterWidget
({
required
RasterWidgetDelegate
delegate
,
required
double
devicePixelRatio
,
required
RasterWidgetController
controller
,
required
RasterizeMode
mode
,
RasterWidgetFallbackDelegate
?
fallback
,
})
:
_delegate
=
delegate
,
_devicePixelRatio
=
devicePixelRatio
,
_controller
=
controller
,
_fallback
=
fallback
,
_mode
=
mode
;
/// The device pixel ratio used to create the child image.
double
get
devicePixelRatio
=>
_devicePixelRatio
;
double
_devicePixelRatio
;
set
devicePixelRatio
(
double
value
)
{
if
(
value
==
devicePixelRatio
)
{
return
;
}
_devicePixelRatio
=
value
;
markNeedsPaint
();
}
/// Whether a rasterized version of this render objects child is drawn in
/// place of the child.
RasterWidgetController
get
controller
=>
_controller
;
RasterWidgetController
_controller
;
set
controller
(
RasterWidgetController
value
)
{
if
(
value
==
controller
)
{
return
;
}
controller
.
removeListener
(
_onRasterValueChanged
);
final
bool
oldValue
=
controller
.
rasterize
;
_controller
=
value
;
if
(
attached
)
{
controller
.
addListener
(
_onRasterValueChanged
);
if
(
oldValue
!=
controller
.
rasterize
)
{
_onRasterValueChanged
();
}
}
}
/// The delegate used to draw the image representing the child.
RasterWidgetDelegate
get
delegate
=>
_delegate
;
RasterWidgetDelegate
_delegate
;
set
delegate
(
RasterWidgetDelegate
value
)
{
if
(
value
==
delegate
)
{
return
;
}
delegate
.
removeListener
(
markNeedsPaint
);
final
RasterWidgetDelegate
oldDelegate
=
_delegate
;
_delegate
=
value
;
if
(
attached
)
{
delegate
.
addListener
(
markNeedsPaint
);
if
(
delegate
.
shouldRepaint
(
oldDelegate
))
{
markNeedsPaint
();
}
}
}
/// A fallback delegate which is used if the child layers contains a platform view.
RasterWidgetFallbackDelegate
?
get
fallback
=>
_fallback
;
RasterWidgetFallbackDelegate
?
_fallback
;
set
fallback
(
RasterWidgetFallbackDelegate
?
value
)
{
if
(
value
==
fallback
)
{
return
;
}
_fallback
=
value
;
markNeedsPaint
();
}
/// How the raster widget will handle platform views in child layers.
RasterizeMode
get
mode
=>
_mode
;
RasterizeMode
_mode
;
set
mode
(
RasterizeMode
value
)
{
if
(
value
==
_mode
)
{
return
;
}
_mode
=
value
;
markNeedsPaint
();
}
ui
.
Image
?
_childRaster
;
@override
void
attach
(
covariant
PipelineOwner
owner
)
{
delegate
.
addListener
(
markNeedsPaint
);
controller
.
addListener
(
_onRasterValueChanged
);
super
.
attach
(
owner
);
}
@override
void
detach
()
{
delegate
.
removeListener
(
markNeedsPaint
);
controller
.
removeListener
(
_onRasterValueChanged
);
_childRaster
?.
dispose
();
_childRaster
=
null
;
super
.
detach
();
}
@override
void
dispose
()
{
delegate
.
removeListener
(
markNeedsPaint
);
controller
.
removeListener
(
_onRasterValueChanged
);
_childRaster
?.
dispose
();
_childRaster
=
null
;
super
.
dispose
();
}
void
_onRasterValueChanged
()
{
_childRaster
?.
dispose
();
_childRaster
=
null
;
markNeedsPaint
();
}
bool
_hitPlatformView
=
false
;
bool
get
_useFallback
=>
_hitPlatformView
||
mode
==
RasterizeMode
.
fallback
;
// Paint [child] with this painting context, then convert to a raster and detach all
// children from this layer.
ui
.
Image
?
_paintAndDetachToImage
()
{
final
OffsetLayer
offsetLayer
=
OffsetLayer
();
final
PaintingContext
context
=
PaintingContext
(
offsetLayer
,
Offset
.
zero
&
size
);
super
.
paint
(
context
,
Offset
.
zero
);
// This ignore is here because this method is protected by the `PaintingContext`. Adding a new
// method that performs the work of `_paintAndDetachToImage` would avoid the need for this, but
// that would conflict with our goals of minimizing painting context.
// ignore: invalid_use_of_protected_member
context
.
stopRecordingIfNeeded
();
if
(
mode
!=
RasterizeMode
.
forced
&&
!
offsetLayer
.
supportsRasterization
())
{
_hitPlatformView
=
true
;
if
(
fallback
==
null
)
{
assert
(()
{
throw
FlutterError
(
'RasterWidget used with a child that contains a PlatformView.'
);
}());
}
return
null
;
}
final
ui
.
Image
image
=
offsetLayer
.
toImageSync
(
Offset
.
zero
&
size
,
pixelRatio:
devicePixelRatio
);
offsetLayer
.
dispose
();
return
image
;
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
if
(
size
.
isEmpty
)
{
_childRaster
?.
dispose
();
_childRaster
=
null
;
return
;
}
if
(
controller
.
rasterize
)
{
if
(
_useFallback
)
{
fallback
?.
paintFallback
(
context
,
offset
,
size
,
super
.
paint
);
}
else
{
_childRaster
??=
_paintAndDetachToImage
();
if
(
_childRaster
==
null
&&
_useFallback
)
{
fallback
?.
paintFallback
(
context
,
offset
,
size
,
super
.
paint
);
}
else
{
delegate
.
paint
(
context
,
offset
,
size
,
_childRaster
!,
devicePixelRatio
);
}
}
return
;
}
_childRaster
?.
dispose
();
_childRaster
=
null
;
super
.
paint
(
context
,
offset
);
}
}
// A delegate that paints the child widget as is.
class
_RasterDefaultDelegate
implements
RasterWidgetDelegate
{
const
_RasterDefaultDelegate
();
@override
void
paint
(
PaintingContext
context
,
Offset
offset
,
Size
size
,
ui
.
Image
image
,
double
pixelRatio
)
{
final
Rect
src
=
Rect
.
fromLTWH
(
0
,
0
,
image
.
width
.
toDouble
(),
image
.
height
.
toDouble
());
final
Rect
dst
=
Rect
.
fromLTWH
(
offset
.
dx
,
offset
.
dy
,
size
.
width
,
size
.
height
);
final
Paint
paint
=
Paint
()
..
filterQuality
=
FilterQuality
.
low
;
context
.
canvas
.
drawImageRect
(
image
,
src
,
dst
,
paint
);
}
@override
bool
shouldRepaint
(
covariant
RasterWidgetDelegate
oldDelegate
)
=>
false
;
@override
void
addListener
(
ui
.
VoidCallback
listener
)
{
}
@override
void
dispose
()
{
}
@override
bool
get
hasListeners
=>
false
;
@override
void
notifyListeners
()
{
}
@override
void
removeListener
(
ui
.
VoidCallback
listener
)
{
}
}
packages/flutter/lib/src/widgets/routes.dart
View file @
9ae37030
...
@@ -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
...
...
packages/flutter/lib/widgets.dart
View file @
9ae37030
...
@@ -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'
;
...
...
packages/flutter/test/material/page_test.dart
View file @
9ae37030
...
@@ -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
,
builder:
(
BuildContext
context
)
{
if
(
settings
.
name
==
'/'
)
{
return
const
Material
(
child:
Text
(
'Page 1'
));
}
return
const
Material
(
child:
Text
(
'Page 2'
));
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
));
...
...
packages/flutter/test/material/text_field_test.dart
View file @
9ae37030
...
@@ -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
);
...
...
packages/flutter/test/rendering/layers_test.dart
View file @
9ae37030
...
@@ -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
{
...
...
packages/flutter/test/widgets/navigator_test.dart
View file @
9ae37030
...
@@ -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
;
...
...
packages/flutter/test/widgets/raster_widget_test.dart
0 → 100644
View file @
9ae37030
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags
(<
String
>[
'reduced-test-set'
])
import
'dart:ui'
as
ui
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'RasterWidget can rasterize child'
,
(
WidgetTester
tester
)
async
{
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
final
Key
key
=
UniqueKey
();
await
tester
.
pumpWidget
(
RepaintBoundary
(
key:
key
,
child:
Center
(
child:
RasterWidget
(
controller:
controller
,
child:
Container
(
width:
100
,
height:
100
,
color:
const
Color
(
0xFFAABB11
),
),
),
),
));
// Rasterization is not actually complete until a frame has been pumped through
// the engine.
await
tester
.
pumpAndSettle
();
await
expectLater
(
find
.
byKey
(
key
),
matchesGoldenFile
(
'raster_widget.yellow.png'
));
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RasterWidget is not a repaint boundary when rasterizing'
,
(
WidgetTester
tester
)
async
{
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
await
tester
.
pumpWidget
(
RepaintBoundary
(
child:
Center
(
child:
RasterWidget
(
controller:
controller
,
child:
Container
(
width:
100
,
height:
100
,
color:
const
Color
(
0xFFAABB11
),
),
),
),
));
expect
(
tester
.
layers
,
hasLength
(
3
));
expect
(
tester
.
layers
.
last
,
isA
<
PictureLayer
>());
controller
.
rasterize
=
false
;
await
tester
.
pump
();
expect
(
tester
.
layers
,
hasLength
(
3
));
expect
(
tester
.
layers
.
last
,
isA
<
PictureLayer
>());
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RasterWidget repaints when RasterWidgetDelegate notifies listeners'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
await
tester
.
pumpWidget
(
RepaintBoundary
(
child:
Center
(
child:
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
child:
Container
(
width:
100
,
height:
100
,
color:
const
Color
(
0xFFAABB11
),
),
),
),
));
expect
(
delegate
.
count
,
1
);
delegate
.
notify
();
await
tester
.
pump
();
expect
(
delegate
.
count
,
2
);
// Remove widget and verify removal of listeners.
await
tester
.
pumpWidget
(
const
SizedBox
());
delegate
.
notify
();
await
tester
.
pump
();
expect
(
delegate
.
count
,
2
);
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RasterWidget will recreate raster when controller calls clear'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
await
tester
.
pumpWidget
(
RepaintBoundary
(
child:
Center
(
child:
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
child:
Container
(
width:
100
,
height:
100
,
color:
const
Color
(
0xFFAABB11
),
),
),
),
));
expect
(
delegate
.
lastImage
,
isNotNull
);
final
ui
.
Image
lastImage
=
delegate
.
lastImage
!;
await
tester
.
pump
();
// Raster is re-used
expect
(
lastImage
,
equals
(
delegate
.
lastImage
));
controller
.
clear
();
await
tester
.
pump
();
// Raster is re-created.
expect
(
delegate
.
lastImage
,
isNotNull
);
expect
(
lastImage
,
isNot
(
delegate
.
lastImage
));
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RasterWidget can update the delegate'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegateA
=
TestDelegate
();
final
TestDelegate
delegateB
=
TestDelegate
()
..
shouldRepaintValue
=
true
;
TestDelegate
delegate
=
delegateA
;
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
late
void
Function
(
void
Function
())
setStateFn
;
await
tester
.
pumpWidget
(
StatefulBuilder
(
builder:
(
BuildContext
context
,
void
Function
(
void
Function
())
setState
)
{
setStateFn
=
setState
;
return
Center
(
child:
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
child:
Container
(
width:
100
,
height:
100
,
color:
const
Color
(
0xFFAABB11
),
),
),
);
})
);
expect
(
delegateA
.
count
,
1
);
expect
(
delegateB
.
count
,
0
);
setStateFn
(()
{
delegate
=
delegateB
;
});
await
tester
.
pump
();
expect
(
delegateA
.
count
,
1
);
expect
(
delegateB
.
count
,
1
);
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RasterWidget can update the ValueNotifier'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
RasterWidgetController
controllerA
=
RasterWidgetController
(
rasterize:
true
);
final
RasterWidgetController
controllerB
=
RasterWidgetController
();
RasterWidgetController
controller
=
controllerA
;
late
void
Function
(
void
Function
())
setStateFn
;
await
tester
.
pumpWidget
(
StatefulBuilder
(
builder:
(
BuildContext
context
,
void
Function
(
void
Function
())
setState
)
{
setStateFn
=
setState
;
return
Center
(
child:
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
child:
Container
(
width:
100
,
height:
100
,
color:
const
Color
(
0xFFAABB11
),
),
),
);
})
);
expect
(
delegate
.
count
,
1
);
expect
(
tester
.
layers
.
last
,
isA
<
OffsetLayer
>());
setStateFn
(()
{
controller
=
controllerB
;
});
await
tester
.
pump
();
expect
(
delegate
.
count
,
1
);
expect
(
tester
.
layers
.
last
,
isA
<
PictureLayer
>());
// changes to old notifier do not impact widget.
controllerA
.
rasterize
=
false
;
await
tester
.
pump
();
expect
(
delegate
.
count
,
1
);
expect
(
tester
.
layers
.
last
,
isA
<
PictureLayer
>());
await
tester
.
pumpWidget
(
const
SizedBox
());
// changes to notifier do not impact widget after disposal.
controllerB
.
rasterize
=
true
;
await
tester
.
pump
();
expect
(
delegate
.
count
,
1
);
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RenderRasterWidget correctly attaches and detaches delegate callbacks'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
final
RenderRasterWidget
rasterWidget
=
RenderRasterWidget
(
delegate:
delegate
,
controller:
controller
,
devicePixelRatio:
1.0
,
mode:
RasterizeMode
.
enabled
,
);
expect
(
delegate
.
addedListenerCount
,
0
);
expect
(
delegate
.
removedListenerCount
,
0
);
final
PipelineOwner
owner
=
PipelineOwner
();
rasterWidget
.
attach
(
owner
);
expect
(
delegate
.
addedListenerCount
,
1
);
expect
(
delegate
.
removedListenerCount
,
0
);
rasterWidget
.
detach
();
expect
(
delegate
.
addedListenerCount
,
1
);
expect
(
delegate
.
removedListenerCount
,
1
);
final
TestDelegate
updatedDelegate
=
TestDelegate
();
rasterWidget
.
delegate
=
updatedDelegate
;
// No listeners added or removed while not attached.
expect
(
updatedDelegate
.
addedListenerCount
,
0
);
expect
(
updatedDelegate
.
removedListenerCount
,
0
);
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RenderRasterWidget correctly attaches and detaches controller callbacks'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
TestController
controller
=
TestController
();
final
RenderRasterWidget
rasterWidget
=
RenderRasterWidget
(
delegate:
delegate
,
controller:
controller
,
devicePixelRatio:
1.0
,
mode:
RasterizeMode
.
enabled
,
);
expect
(
controller
.
addedListenerCount
,
0
);
expect
(
controller
.
removedListenerCount
,
0
);
final
PipelineOwner
owner
=
PipelineOwner
();
rasterWidget
.
attach
(
owner
);
expect
(
controller
.
addedListenerCount
,
1
);
expect
(
controller
.
removedListenerCount
,
0
);
rasterWidget
.
detach
();
expect
(
controller
.
addedListenerCount
,
1
);
expect
(
controller
.
removedListenerCount
,
1
);
final
TestController
updatedController
=
TestController
();
rasterWidget
.
controller
=
updatedController
;
// No listeners added or removed while not attached.
expect
(
updatedController
.
addedListenerCount
,
0
);
expect
(
updatedController
.
removedListenerCount
,
0
);
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RenderRasterWidget does not error on rasterization of child with empty size'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
await
tester
.
pumpWidget
(
Center
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
child:
const
SizedBox
(),
),
),
),
);
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RenderRasterWidget throws assertion if platform view is encountered'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
await
tester
.
pumpWidget
(
Center
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
child:
const
SizedBox
(
width:
100
,
height:
100
,
child:
TestPlatformView
(),
),
),
),
),
);
expect
(
tester
.
takeException
(),
isA
<
FlutterError
>()
.
having
((
FlutterError
error
)
=>
error
.
message
,
'message'
,
contains
(
'RasterWidget used with a child that contains a PlatformView'
)));
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RenderRasterWidget does not assert if RasterizeMode.forced'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
await
tester
.
pumpWidget
(
Center
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
mode:
RasterizeMode
.
forced
,
child:
const
SizedBox
(
width:
100
,
height:
100
,
child:
TestPlatformView
(),
),
),
),
),
);
expect
(
tester
.
takeException
(),
isNull
);
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RenderRasterWidget fallbacks to delegate if PlatformView is present'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
final
TestFallback
fallback
=
TestFallback
();
await
tester
.
pumpWidget
(
Center
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
fallback:
fallback
,
child:
const
SizedBox
(
width:
100
,
height:
100
,
child:
TestPlatformView
(),
),
),
),
),
);
expect
(
fallback
.
calledFallback
,
1
);
expect
(
delegate
.
count
,
0
);
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
testWidgets
(
'RenderRasterWidget fallbacks to delegate if mode: RasterizeMode.fallback'
,
(
WidgetTester
tester
)
async
{
final
TestDelegate
delegate
=
TestDelegate
();
final
RasterWidgetController
controller
=
RasterWidgetController
(
rasterize:
true
);
final
TestFallback
fallback
=
TestFallback
();
await
tester
.
pumpWidget
(
Center
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
RasterWidget
(
delegate:
delegate
,
controller:
controller
,
fallback:
fallback
,
mode:
RasterizeMode
.
fallback
,
child:
const
SizedBox
(
width:
100
,
height:
100
,
),
),
),
),
);
expect
(
fallback
.
calledFallback
,
1
);
expect
(
delegate
.
count
,
0
);
},
skip:
kIsWeb
);
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
}
class
TestFallback
extends
RasterWidgetFallbackDelegate
{
int
calledFallback
=
0
;
@override
void
paintFallback
(
PaintingContext
context
,
ui
.
Offset
offset
,
ui
.
Size
size
,
PaintingContextCallback
painter
)
{
calledFallback
+=
1
;
}
}
class
TestController
extends
RasterWidgetController
{
int
addedListenerCount
=
0
;
int
removedListenerCount
=
0
;
@override
void
addListener
(
ui
.
VoidCallback
listener
)
{
addedListenerCount
+=
1
;
super
.
addListener
(
listener
);
}
@override
void
removeListener
(
ui
.
VoidCallback
listener
)
{
removedListenerCount
+=
1
;
super
.
removeListener
(
listener
);
}
}
class
TestDelegate
extends
RasterWidgetDelegate
{
int
count
=
0
;
bool
shouldRepaintValue
=
false
;
ui
.
Image
?
lastImage
;
int
addedListenerCount
=
0
;
int
removedListenerCount
=
0
;
@override
void
addListener
(
ui
.
VoidCallback
listener
)
{
addedListenerCount
+=
1
;
super
.
addListener
(
listener
);
}
@override
void
removeListener
(
ui
.
VoidCallback
listener
)
{
removedListenerCount
+=
1
;
super
.
removeListener
(
listener
);
}
void
notify
()
{
notifyListeners
();
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
,
Size
size
,
ui
.
Image
image
,
double
pixelRatio
)
{
count
+=
1
;
lastImage
=
image
;
}
@override
bool
shouldRepaint
(
covariant
RasterWidgetDelegate
oldDelegate
)
=>
shouldRepaintValue
;
}
class
TestPlatformView
extends
SingleChildRenderObjectWidget
{
const
TestPlatformView
({
super
.
key
});
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
RenderTestPlatformView
();
}
}
class
RenderTestPlatformView
extends
RenderProxyBox
{
@override
void
paint
(
PaintingContext
context
,
ui
.
Offset
offset
)
{
context
.
addLayer
(
PlatformViewLayer
(
rect:
offset
&
size
,
viewId:
1
));
}
}
packages/flutter_test/lib/src/controller.dart
View file @
9ae37030
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment