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
fe15d1e7
Unverified
Commit
fe15d1e7
authored
Jun 17, 2020
by
Alex Vincent
Committed by
GitHub
Jun 17, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[PageTransitionsBuilder] Fix 'ZoomPageTransition' built more than once (#58686)
parent
e0ed12c7
Changes
5
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
743 additions
and
116 deletions
+743
-116
page_transitions_theme.dart
...ages/flutter/lib/src/material/page_transitions_theme.dart
+191
-116
dual_transition_builder.dart
...ages/flutter/lib/src/widgets/dual_transition_builder.dart
+203
-0
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
page_transitions_theme_test.dart
...es/flutter/test/material/page_transitions_theme_test.dart
+46
-0
dual_transition_builder_test.dart
...es/flutter/test/widgets/dual_transition_builder_test.dart
+302
-0
No files found.
packages/flutter/lib/src/material/page_transitions_theme.dart
View file @
fe15d1e7
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/widgets/dual_transition_builder.dart
0 → 100644
View file @
fe15d1e7
// 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.
// @dart = 2.8
import
'basic.dart'
;
import
'framework.dart'
;
/// Builder callback used by [DualTransitionBuilder].
///
/// The builder is expected to return a transition powered by the provided
/// `animation` and wrapping the provided `child`.
///
/// The `animation` provided to the builder always runs forward from 0.0 to 1.0.
typedef
AnimatedTransitionBuilder
=
Widget
Function
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
);
/// A transition builder that animates its [child] based on the
/// [AnimationStatus] of the provided [animation].
///
/// This widget can be used to specify different enter and exit transitions for
/// a [child].
///
/// While the [animation] runs forward, the [child] is animated according to
/// [forwardBuilder] and while the [animation] is running in reverse, it is
/// animated according to [reverseBuilder].
///
/// Using this builder allows the widget tree to maintain its shape by nesting
/// the enter and exit transitions. This ensures that no state information of
/// any descendant widget is lost when the transition starts or completes.
class
DualTransitionBuilder
extends
StatefulWidget
{
/// Creates a [DualTransitionBuilder].
///
/// The [animation], [forwardBuilder], and [reverseBuilder] arguments are
/// required and must not be null.
const
DualTransitionBuilder
({
Key
key
,
@required
this
.
animation
,
@required
this
.
forwardBuilder
,
@required
this
.
reverseBuilder
,
this
.
child
,
})
:
assert
(
animation
!=
null
),
assert
(
forwardBuilder
!=
null
),
assert
(
reverseBuilder
!=
null
),
super
(
key:
key
);
/// The animation that drives the [child]'s transition.
///
/// When this animation runs forward, the [child] transitions as specified by
/// [forwardBuilder]. When it runs in reverse, the child transitions according
/// to [reverseBuilder].
final
Animation
<
double
>
animation
;
/// A builder for the transition that makes [child] appear on screen.
///
/// The [child] should be fully visible when the provided `animation` reaches
/// 1.0.
///
/// The `animation` provided to this builder is running forward from 0.0 to
/// 1.0 when [animation] runs _forward_. When [animation] runs in reverse,
/// the given `animation` is set to [kAlwaysCompleteAnimation].
///
/// See also:
///
/// * [reverseBuilder], which builds the transition for making the [child]
/// disappear from the screen.
final
AnimatedTransitionBuilder
forwardBuilder
;
/// A builder for a transition that makes [child] disappear from the screen.
///
/// The [child] should be fully invisible when the provided `animation`
/// reaches 1.0.
///
/// The `animation` provided to this builder is running forward from 0.0 to
/// 1.0 when [animation] runs in _reverse_. When [animation] runs forward,
/// the given `animation` is set to [kAlwaysDismissedAnimation].
///
/// See also:
///
/// * [forwardBuilder], which builds the transition for making the [child]
/// appear on screen.
final
AnimatedTransitionBuilder
reverseBuilder
;
/// The widget below this [DualTransitionBuilder] in the tree.
///
/// This child widget will be wrapped by the transitions built by
/// [forwardBuilder] and [reverseBuilder].
final
Widget
child
;
@override
State
<
DualTransitionBuilder
>
createState
()
=>
_DualTransitionBuilderState
();
}
class
_DualTransitionBuilderState
extends
State
<
DualTransitionBuilder
>
{
AnimationStatus
_effectiveAnimationStatus
;
final
ProxyAnimation
_forwardAnimation
=
ProxyAnimation
();
final
ProxyAnimation
_reverseAnimation
=
ProxyAnimation
();
@override
void
initState
()
{
super
.
initState
();
_effectiveAnimationStatus
=
widget
.
animation
.
status
;
widget
.
animation
.
addStatusListener
(
_animationListener
);
_updateAnimations
();
}
void
_animationListener
(
AnimationStatus
animationStatus
)
{
final
AnimationStatus
oldEffective
=
_effectiveAnimationStatus
;
_effectiveAnimationStatus
=
_calculateEffectiveAnimationStatus
(
lastEffective:
_effectiveAnimationStatus
,
current:
animationStatus
,
);
if
(
oldEffective
!=
_effectiveAnimationStatus
)
{
_updateAnimations
();
}
}
@override
void
didUpdateWidget
(
DualTransitionBuilder
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
oldWidget
.
animation
!=
widget
.
animation
)
{
oldWidget
.
animation
.
removeStatusListener
(
_animationListener
);
widget
.
animation
.
addStatusListener
(
_animationListener
);
_animationListener
(
widget
.
animation
.
status
);
}
}
// When a transition is interrupted midway we just want to play the ongoing
// animation in reverse. Switching to the actual reverse transition would
// yield a disjoint experience since the forward and reverse transitions are
// very different.
AnimationStatus
_calculateEffectiveAnimationStatus
({
@required
AnimationStatus
lastEffective
,
@required
AnimationStatus
current
,
})
{
assert
(
current
!=
null
);
assert
(
lastEffective
!=
null
);
switch
(
current
)
{
case
AnimationStatus
.
dismissed
:
case
AnimationStatus
.
completed
:
return
current
;
case
AnimationStatus
.
forward
:
switch
(
lastEffective
)
{
case
AnimationStatus
.
dismissed
:
case
AnimationStatus
.
completed
:
case
AnimationStatus
.
forward
:
return
current
;
case
AnimationStatus
.
reverse
:
return
lastEffective
;
}
break
;
case
AnimationStatus
.
reverse
:
switch
(
lastEffective
)
{
case
AnimationStatus
.
dismissed
:
case
AnimationStatus
.
completed
:
case
AnimationStatus
.
reverse
:
return
current
;
case
AnimationStatus
.
forward
:
return
lastEffective
;
}
break
;
}
return
null
;
// unreachable
}
void
_updateAnimations
()
{
switch
(
_effectiveAnimationStatus
)
{
case
AnimationStatus
.
dismissed
:
case
AnimationStatus
.
forward
:
_forwardAnimation
.
parent
=
widget
.
animation
;
_reverseAnimation
.
parent
=
kAlwaysDismissedAnimation
;
break
;
case
AnimationStatus
.
reverse
:
case
AnimationStatus
.
completed
:
_forwardAnimation
.
parent
=
kAlwaysCompleteAnimation
;
_reverseAnimation
.
parent
=
ReverseAnimation
(
widget
.
animation
);
break
;
}
}
@override
void
dispose
()
{
widget
.
animation
.
removeStatusListener
(
_animationListener
);
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
widget
.
forwardBuilder
(
context
,
_forwardAnimation
,
widget
.
reverseBuilder
(
context
,
_reverseAnimation
,
widget
.
child
,
),
);
}
}
packages/flutter/lib/widgets.dart
View file @
fe15d1e7
...
@@ -37,6 +37,7 @@ export 'src/widgets/dismissible.dart';
...
@@ -37,6 +37,7 @@ export 'src/widgets/dismissible.dart';
export
'src/widgets/disposable_build_context.dart'
;
export
'src/widgets/disposable_build_context.dart'
;
export
'src/widgets/drag_target.dart'
;
export
'src/widgets/drag_target.dart'
;
export
'src/widgets/draggable_scrollable_sheet.dart'
;
export
'src/widgets/draggable_scrollable_sheet.dart'
;
export
'src/widgets/dual_transition_builder.dart'
;
export
'src/widgets/editable_text.dart'
;
export
'src/widgets/editable_text.dart'
;
export
'src/widgets/fade_in_image.dart'
;
export
'src/widgets/fade_in_image.dart'
;
export
'src/widgets/focus_manager.dart'
;
export
'src/widgets/focus_manager.dart'
;
...
...
packages/flutter/test/material/page_transitions_theme_test.dart
View file @
fe15d1e7
...
@@ -161,4 +161,50 @@ void main() {
...
@@ -161,4 +161,50 @@ void main() {
expect
(
find
.
text
(
'page b'
),
findsOneWidget
);
expect
(
find
.
text
(
'page b'
),
findsOneWidget
);
expect
(
findZoomPageTransition
(),
findsOneWidget
);
expect
(
findZoomPageTransition
(),
findsOneWidget
);
},
variant:
TargetPlatformVariant
.
only
(
TargetPlatform
.
android
));
},
variant:
TargetPlatformVariant
.
only
(
TargetPlatform
.
android
));
testWidgets
(
'_ZoomPageTransition only cause child widget built once'
,
(
WidgetTester
tester
)
async
{
// Regression test for https://github.com/flutter/flutter/issues/58345
int
builtCount
=
0
;
final
Map
<
String
,
WidgetBuilder
>
routes
=
<
String
,
WidgetBuilder
>{
'/'
:
(
BuildContext
context
)
=>
Material
(
child:
FlatButton
(
child:
const
Text
(
'push'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/b'
);
},
),
),
'/b'
:
(
BuildContext
context
)
=>
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
builtCount
++;
// Increase [builtCount] each time the widget build
return
FlatButton
(
child:
const
Text
(
'pop'
),
onPressed:
()
{
Navigator
.
pop
(
context
);
},
);
},
),
};
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
(
pageTransitionsTheme:
const
PageTransitionsTheme
(
builders:
<
TargetPlatform
,
PageTransitionsBuilder
>{
TargetPlatform
.
android
:
ZoomPageTransitionsBuilder
(),
// creates a _ZoomPageTransition
},
),
),
routes:
routes
,
),
);
// No matter push or pop was called, the child widget should built only once.
await
tester
.
tap
(
find
.
text
(
'push'
));
await
tester
.
pumpAndSettle
();
expect
(
builtCount
,
1
);
await
tester
.
tap
(
find
.
text
(
'pop'
));
await
tester
.
pumpAndSettle
();
expect
(
builtCount
,
1
);
},
variant:
TargetPlatformVariant
.
only
(
TargetPlatform
.
android
));
}
}
packages/flutter/test/widgets/dual_transition_builder_test.dart
0 → 100644
View file @
fe15d1e7
// 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.
// @dart = 2.8
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/src/widgets/dual_transition_builder.dart'
;
void
main
(
)
{
testWidgets
(
'runs animations'
,
(
WidgetTester
tester
)
async
{
final
AnimationController
controller
=
AnimationController
(
vsync:
const
TestVSync
(),
duration:
const
Duration
(
milliseconds:
300
),
);
await
tester
.
pumpWidget
(
Center
(
child:
DualTransitionBuilder
(
animation:
controller
,
forwardBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
ScaleTransition
(
scale:
animation
,
child:
child
,
);
},
reverseBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
FadeTransition
(
opacity:
Tween
<
double
>(
begin:
1.0
,
end:
0.0
).
animate
(
animation
),
child:
child
,
);
},
child:
Container
(
color:
Colors
.
green
,
height:
100
,
width:
100
,
),
),
));
expect
(
_getScale
(
tester
),
0.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
controller
.
forward
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
_getScale
(
tester
),
0.5
);
expect
(
_getOpacity
(
tester
),
1.0
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
await
tester
.
pumpAndSettle
();
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
controller
.
reverse
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
0.5
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
0.0
);
await
tester
.
pumpAndSettle
();
expect
(
_getScale
(
tester
),
0.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
});
testWidgets
(
'keeps state'
,
(
WidgetTester
tester
)
async
{
final
AnimationController
controller
=
AnimationController
(
vsync:
const
TestVSync
(),
duration:
const
Duration
(
milliseconds:
300
),
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
DualTransitionBuilder
(
animation:
controller
,
forwardBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
ScaleTransition
(
scale:
animation
,
child:
child
,
);
},
reverseBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
FadeTransition
(
opacity:
Tween
<
double
>(
begin:
1.0
,
end:
0.0
).
animate
(
animation
),
child:
child
,
);
},
child:
const
_StatefulTestWidget
(
name:
'Foo'
),
),
),
));
final
State
<
StatefulWidget
>
state
=
tester
.
state
(
find
.
byType
(
_StatefulTestWidget
));
expect
(
state
,
isNotNull
);
controller
.
forward
();
await
tester
.
pump
();
expect
(
state
,
same
(
tester
.
state
(
find
.
byType
(
_StatefulTestWidget
))));
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
state
,
same
(
tester
.
state
(
find
.
byType
(
_StatefulTestWidget
))));
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
state
,
same
(
tester
.
state
(
find
.
byType
(
_StatefulTestWidget
))));
await
tester
.
pumpAndSettle
();
expect
(
state
,
same
(
tester
.
state
(
find
.
byType
(
_StatefulTestWidget
))));
controller
.
reverse
();
await
tester
.
pump
();
expect
(
state
,
same
(
tester
.
state
(
find
.
byType
(
_StatefulTestWidget
))));
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
state
,
same
(
tester
.
state
(
find
.
byType
(
_StatefulTestWidget
))));
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
state
,
same
(
tester
.
state
(
find
.
byType
(
_StatefulTestWidget
))));
await
tester
.
pumpAndSettle
();
expect
(
state
,
same
(
tester
.
state
(
find
.
byType
(
_StatefulTestWidget
))));
});
testWidgets
(
'does not jump when interrupted - forward'
,
(
WidgetTester
tester
)
async
{
final
AnimationController
controller
=
AnimationController
(
vsync:
const
TestVSync
(),
duration:
const
Duration
(
milliseconds:
300
),
);
await
tester
.
pumpWidget
(
Center
(
child:
DualTransitionBuilder
(
animation:
controller
,
forwardBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
ScaleTransition
(
scale:
animation
,
child:
child
,
);
},
reverseBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
FadeTransition
(
opacity:
Tween
<
double
>(
begin:
1.0
,
end:
0.0
).
animate
(
animation
),
child:
child
,
);
},
child:
Container
(
color:
Colors
.
green
,
height:
100
,
width:
100
,
),
),
));
expect
(
_getScale
(
tester
),
0.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
controller
.
forward
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
_getScale
(
tester
),
0.5
);
expect
(
_getOpacity
(
tester
),
1.0
);
controller
.
reverse
();
expect
(
_getScale
(
tester
),
0.5
);
expect
(
_getOpacity
(
tester
),
1.0
);
await
tester
.
pump
();
expect
(
_getScale
(
tester
),
0.5
);
expect
(
_getOpacity
(
tester
),
1.0
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
75
));
expect
(
_getScale
(
tester
),
0.25
);
expect
(
_getOpacity
(
tester
),
1.0
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
75
));
expect
(
_getScale
(
tester
),
0.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
await
tester
.
pumpAndSettle
();
expect
(
_getScale
(
tester
),
0.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
});
testWidgets
(
'does not jump when interrupted - reverse'
,
(
WidgetTester
tester
)
async
{
final
AnimationController
controller
=
AnimationController
(
value:
1.0
,
vsync:
const
TestVSync
(),
duration:
const
Duration
(
milliseconds:
300
),
);
await
tester
.
pumpWidget
(
Center
(
child:
DualTransitionBuilder
(
animation:
controller
,
forwardBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
ScaleTransition
(
scale:
animation
,
child:
child
,
);
},
reverseBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
FadeTransition
(
opacity:
Tween
<
double
>(
begin:
1.0
,
end:
0.0
).
animate
(
animation
),
child:
child
,
);
},
child:
Container
(
color:
Colors
.
green
,
height:
100
,
width:
100
,
),
),
));
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
controller
.
reverse
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
0.5
);
controller
.
forward
();
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
0.5
);
await
tester
.
pump
();
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
0.5
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
75
));
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
0.75
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
75
));
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
await
tester
.
pumpAndSettle
();
expect
(
_getScale
(
tester
),
1.0
);
expect
(
_getOpacity
(
tester
),
1.0
);
});
}
double
_getScale
(
WidgetTester
tester
)
{
final
ScaleTransition
scale
=
tester
.
widget
(
find
.
byType
(
ScaleTransition
));
return
scale
.
scale
.
value
;
}
double
_getOpacity
(
WidgetTester
tester
)
{
final
FadeTransition
scale
=
tester
.
widget
(
find
.
byType
(
FadeTransition
));
return
scale
.
opacity
.
value
;
}
class
_StatefulTestWidget
extends
StatefulWidget
{
const
_StatefulTestWidget
({
Key
key
,
this
.
name
})
:
super
(
key:
key
);
final
String
name
;
@override
State
<
_StatefulTestWidget
>
createState
()
=>
_StatefulTestWidgetState
();
}
class
_StatefulTestWidgetState
extends
State
<
_StatefulTestWidget
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
Text
(
widget
.
name
);
}
}
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