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
8e87408f
Unverified
Commit
8e87408f
authored
Feb 11, 2021
by
Michael Goderbauer
Committed by
GitHub
Feb 11, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CupertinoPageTransition Optimizations (#75670)
parent
a4ae59ba
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
103 additions
and
63 deletions
+103
-63
route.dart
packages/flutter/lib/src/cupertino/route.dart
+90
-53
page_test.dart
packages/flutter/test/material/page_test.dart
+11
-8
mock_canvas.dart
packages/flutter/test/rendering/mock_canvas.dart
+2
-2
No files found.
packages/flutter/lib/src/cupertino/route.dart
View file @
8e87408f
...
...
@@ -54,27 +54,6 @@ final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
end:
Offset
.
zero
,
);
// Custom decoration from no shadow to page shadow mimicking iOS page
// transitions using gradients.
final
DecorationTween
_kGradientShadowTween
=
DecorationTween
(
begin:
_CupertinoEdgeShadowDecoration
.
none
,
// No decoration initially.
end:
const
_CupertinoEdgeShadowDecoration
(
edgeGradient:
LinearGradient
(
// Spans 5% of the page.
begin:
AlignmentDirectional
(
0.90
,
0.0
),
end:
AlignmentDirectional
.
centerEnd
,
// Eyeballed gradient used to mimic a drop shadow on the start side only.
colors:
<
Color
>[
Color
(
0x00000000
),
Color
(
0x04000000
),
Color
(
0x12000000
),
Color
(
0x38000000
),
],
stops:
<
double
>[
0.0
,
0.3
,
0.6
,
1.0
],
),
),
);
/// A mixin that replaces the entire screen with an iOS transition for a
/// [PageRoute].
///
...
...
@@ -499,7 +478,7 @@ class CupertinoPageTransition extends StatelessWidget {
parent:
primaryRouteAnimation
,
curve:
Curves
.
linearToEaseOut
,
)
).
drive
(
_
kGradientShadow
Tween
),
).
drive
(
_
CupertinoEdgeShadowDecoration
.
k
Tween
),
super
(
key:
key
);
// When this page is coming in to cover another page.
...
...
@@ -806,24 +785,33 @@ class _CupertinoBackGestureController<T> {
// A custom [Decoration] used to paint an extra shadow on the start edge of the
// box it's decorating. It's like a [BoxDecoration] with only a gradient except
// it paints on the start side of the box instead of behind the box.
//
// The [edgeGradient] will be given a [TextDirection] when its shader is
// created, and so can be direction-sensitive; in this file we set it to a
// gradient that uses an AlignmentDirectional to position the gradient on the
// end edge of the gradient's box (which will be the edge adjacent to the start
// edge of the actual box we're supposed to paint in).
class
_CupertinoEdgeShadowDecoration
extends
Decoration
{
const
_CupertinoEdgeShadowDecoration
({
this
.
edgeGradient
}
);
const
_CupertinoEdgeShadowDecoration
.
_
([
this
.
_colors
]
);
// An edge shadow decoration where the shadow is null. This is used
// for interpolating from no shadow.
static
const
_CupertinoEdgeShadowDecoration
none
=
_CupertinoEdgeShadowDecoration
();
static
DecorationTween
kTween
=
DecorationTween
(
begin:
const
_CupertinoEdgeShadowDecoration
.
_
(),
// No decoration initially.
end:
const
_CupertinoEdgeShadowDecoration
.
_
(
// Eyeballed gradient used to mimic a drop shadow on the start side only.
<
Color
>[
Color
(
0x38000000
),
Color
(
0x12000000
),
Color
(
0x04000000
),
Color
(
0x00000000
),
],
),
);
// A gradient to draw to the left of the box being decorated.
// Alignments are relative to the original box translated one box
// width to the left.
final
LinearGradient
?
edgeGradient
;
// Colors used to paint a gradient at the start edge of the box it is
// decorating.
//
// The first color in the list is used at the start of the gradient, which
// is located at the start edge of the decorated box.
//
// If this is null, no shadow is drawn.
//
// The list must have at least two colors in it (otherwise it would not be a
// gradient).
final
List
<
Color
>?
_colors
;
// Linearly interpolate between two edge shadow decorations decorations.
//
...
...
@@ -850,8 +838,19 @@ class _CupertinoEdgeShadowDecoration extends Decoration {
assert
(
t
!=
null
);
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
_CupertinoEdgeShadowDecoration
(
edgeGradient:
LinearGradient
.
lerp
(
a
?.
edgeGradient
,
b
?.
edgeGradient
,
t
),
if
(
a
==
null
)
return
b
!.
_colors
==
null
?
b
:
_CupertinoEdgeShadowDecoration
.
_
(
b
.
_colors
!.
map
<
Color
>((
Color
color
)
=>
Color
.
lerp
(
null
,
color
,
t
)!).
toList
());
if
(
b
==
null
)
return
a
.
_colors
==
null
?
a
:
_CupertinoEdgeShadowDecoration
.
_
(
a
.
_colors
!.
map
<
Color
>((
Color
color
)
=>
Color
.
lerp
(
null
,
color
,
1.0
-
t
)!).
toList
());
assert
(
b
.
_colors
!=
null
||
a
.
_colors
!=
null
);
// If it ever becomes necessary, we could allow decorations with different
// length' here, similarly to how it is handled in [LinearGradient.lerp].
assert
(
b
.
_colors
==
null
||
a
.
_colors
==
null
||
a
.
_colors
!.
length
==
b
.
_colors
!.
length
);
return
_CupertinoEdgeShadowDecoration
.
_
(
<
Color
>[
for
(
int
i
=
0
;
i
<
b
.
_colors
!.
length
;
i
+=
1
)
Color
.
lerp
(
a
.
_colors
?[
i
],
b
.
_colors
?[
i
],
t
)!,
]
);
}
...
...
@@ -879,16 +878,16 @@ class _CupertinoEdgeShadowDecoration extends Decoration {
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
return
other
is
_CupertinoEdgeShadowDecoration
&&
other
.
edgeGradient
==
edgeGradient
;
&&
other
.
_colors
==
_colors
;
}
@override
int
get
hashCode
=>
edgeGradient
.
hashCode
;
int
get
hashCode
=>
_colors
.
hashCode
;
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
LinearGradient
>(
'edgeGradient'
,
edgeGradient
));
properties
.
add
(
IterableProperty
<
Color
>(
'colors'
,
_colors
));
}
}
...
...
@@ -898,33 +897,71 @@ class _CupertinoEdgeShadowPainter extends BoxPainter {
this
.
_decoration
,
VoidCallback
?
onChange
,
)
:
assert
(
_decoration
!=
null
),
assert
(
_decoration
.
_colors
==
null
||
_decoration
.
_colors
!.
length
>
1
),
super
(
onChange
);
final
_CupertinoEdgeShadowDecoration
_decoration
;
@override
void
paint
(
Canvas
canvas
,
Offset
offset
,
ImageConfiguration
configuration
)
{
final
Li
nearGradient
?
gradient
=
_decoration
.
edgeGradient
;
if
(
gradient
==
null
)
final
Li
st
<
Color
>?
colors
=
_decoration
.
_colors
;
if
(
colors
==
null
)
{
return
;
// The drawable space for the gradient is a rect with the same size as
// its parent box one box width on the start side of the box.
}
// The following code simulates drawing a [LinearGradient] configured as
// follows:
//
// LinearGradient(
// begin: AlignmentDirectional(0.90, 0.0), // Spans 5% of the page.
// colors: _decoration._colors,
// )
//
// A performance evaluation on Feb 8, 2021 showed, that drawing the gradient
// manually as implemented below is more performant than relying on
// [LinearGradient.createShader] because compiling that shader takes a long
// time. On an iPhone XR, the implementation below reduced the worst frame
// time for a cupertino page transition of a newly installed app from ~95ms
// down to ~30ms, mainly because there's no longer a need to compile a
// shader for the LinearGradient.
//
// The implementation below divides the width of the shadow into multiple
// bands of equal width, one for each color interval defined by
// `_decoration._colors`. Band x is filled with a gradient going from
// `_decoration._colors[x]` to `_decoration._colors[x + 1]` by drawing a
// bunch of 1px wide rects. The rects change their color by lerping between
// the two colors that define the interval of the band.
// Shadow spans 5% of the page.
final
double
shadowWidth
=
0.05
*
configuration
.
size
!.
width
;
final
double
shadowHeight
=
configuration
.
size
!.
height
;
final
double
bandWidth
=
shadowWidth
/
(
colors
.
length
-
1
);
final
TextDirection
?
textDirection
=
configuration
.
textDirection
;
assert
(
textDirection
!=
null
);
final
double
deltaX
;
final
double
start
;
final
double
shadowDirection
;
// -1 for ltr, 1 for rtl.
switch
(
textDirection
!)
{
case
TextDirection
.
rtl
:
deltaX
=
configuration
.
size
!.
width
;
start
=
offset
.
dx
+
configuration
.
size
!.
width
;
shadowDirection
=
1
;
break
;
case
TextDirection
.
ltr
:
deltaX
=
-
configuration
.
size
!.
width
;
start
=
offset
.
dx
;
shadowDirection
=
-
1
;
break
;
}
final
Rect
rect
=
(
offset
&
configuration
.
size
!).
translate
(
deltaX
,
0.0
);
final
Paint
paint
=
Paint
()
..
shader
=
gradient
.
createShader
(
rect
,
textDirection:
textDirection
);
canvas
.
drawRect
(
rect
,
paint
);
int
bandColorIndex
=
0
;
for
(
int
dx
=
0
;
dx
<
shadowWidth
;
dx
+=
1
)
{
if
(
dx
~/
bandWidth
!=
bandColorIndex
)
{
bandColorIndex
+=
1
;
}
final
Paint
paint
=
Paint
()
..
color
=
Color
.
lerp
(
colors
[
bandColorIndex
],
colors
[
bandColorIndex
+
1
],
(
dx
%
bandWidth
)
/
bandWidth
)!;
final
double
x
=
start
+
shadowDirection
*
dx
;
canvas
.
drawRect
(
Rect
.
fromLTWH
(
x
-
1.0
,
offset
.
dy
,
1.0
,
shadowHeight
),
paint
);
}
}
}
...
...
packages/flutter/test/material/page_test.dart
View file @
8e87408f
...
...
@@ -103,14 +103,17 @@ void main() {
expect
(
widget1InitialTopLeft
.
dy
==
widget2TopLeft
.
dy
,
true
);
// Page 2 is coming in from the right.
expect
(
widget2TopLeft
.
dx
>
widget1InitialTopLeft
.
dx
,
true
);
// The shadow should be drawn to one screen width to the left of where
// the page 2 box is. `paints` tests relative to the painter's given canvas
// rather than relative to the screen so assert that it's one screen
// width to the left of 0 offset box rect and nothing is drawn inside the
// box's rect.
expect
(
box
,
paints
..
rect
(
rect:
const
Rect
.
fromLTWH
(-
800.0
,
0.0
,
800.0
,
600.0
)
));
// As explained in _CupertinoEdgeShadowPainter.paint the shadow is drawn
// as a bunch of rects. The rects are covering an area to the left of
// where the page 2 box is and a width of 5% of the page 2 box width.
// `paints` tests relative to the painter's given canvas
// rather than relative to the screen so assert that the shadow starts at
// offset.dx = 0.
final
PaintPattern
paintsShadow
=
paints
;
for
(
int
i
=
0
;
i
<
0.05
*
800
;
i
+=
1
)
{
paintsShadow
.
rect
(
rect:
Rect
.
fromLTWH
(-
i
.
toDouble
()
-
1.0
,
0.0
,
1.0
,
600
));
}
expect
(
box
,
paintsShadow
);
await
tester
.
pumpAndSettle
();
...
...
packages/flutter/test/rendering/mock_canvas.dart
View file @
8e87408f
...
...
@@ -797,9 +797,9 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp
Description
describe
(
Description
description
)
{
if
(
_predicates
.
isEmpty
)
return
description
.
add
(
'An object or closure and a paint pattern.'
);
description
.
add
(
'Object or closure painting:
'
);
description
.
add
(
'Object or closure painting:
\n
'
);
return
description
.
addAll
(
''
,
'
,
'
,
''
,
''
,
'
\n
'
,
''
,
_predicates
.
map
<
String
>((
_PaintPredicate
predicate
)
=>
predicate
.
toString
()),
);
}
...
...
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