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
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
...
...
@@ -150,19 +150,19 @@ class _OpenUpwardsPageTransition extends StatelessWidget {
// Zooms and fades a new page in, zooming out the previous page. This transition
// is designed to match the Android 10 activity transition.
class
_ZoomPageTransition
extends
StatefulWidget
{
class
_ZoomPageTransition
extends
StatelessWidget
{
/// Creates a [_ZoomPageTransition].
///
/// The [animation] and [secondaryAnimation] argument are required and must
/// not be null.
const
_ZoomPageTransition
({
Key
key
,
this
.
animation
,
this
.
secondaryAnimation
,
@required
this
.
animation
,
@required
this
.
secondaryAnimation
,
this
.
child
,
})
:
super
(
key:
key
);
// The scrim obscures the old page by becoming increasingly opaque.
static
final
Tween
<
double
>
_scrimOpacityTween
=
Tween
<
double
>(
begin:
0.0
,
end:
0.60
,
);
})
:
assert
(
animation
!=
null
),
assert
(
secondaryAnimation
!=
null
),
super
(
key:
key
);
// A curve sequence that is similar to the 'fastOutExtraSlowIn' curve used in
// the native transition.
...
...
@@ -179,132 +179,207 @@ class _ZoomPageTransition extends StatefulWidget {
),
];
static
final
TweenSequence
<
double
>
_scaleCurveSequence
=
TweenSequence
<
double
>(
fastOutExtraSlowInTweenSequenceItems
);
static
final
FlippedTweenSequence
_flippedScaleCurveSequence
=
FlippedTweenSequence
(
fastOutExtraSlowInTweenSequenceItems
);
/// The animation that drives the [child]'s entrance and exit.
///
/// See also:
///
/// * [TransitionRoute.animation], which is the value given to this property
/// when the [_ZoomPageTransition] is used as a page transition.
final
Animation
<
double
>
animation
;
/// The animation that transitions [child] when new content is pushed on top
/// of it.
///
/// See also:
///
/// * [TransitionRoute.secondaryAnimation], which is the value given to this
// property when the [_ZoomPageTransition] is used as a page transition.
final
Animation
<
double
>
secondaryAnimation
;
/// The widget below this widget in the tree.
///
/// This widget will transition in and out as driven by [animation] and
/// [secondaryAnimation].
final
Widget
child
;
@override
__ZoomPageTransitionState
createState
()
=>
__ZoomPageTransitionState
();
Widget
build
(
BuildContext
context
)
{
return
DualTransitionBuilder
(
animation:
animation
,
forwardBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
_ZoomEnterTransition
(
animation:
animation
,
child:
child
,
);
},
reverseBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
_ZoomExitTransition
(
animation:
animation
,
reverse:
true
,
child:
child
,
);
},
child:
DualTransitionBuilder
(
animation:
ReverseAnimation
(
secondaryAnimation
),
forwardBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
_ZoomEnterTransition
(
animation:
animation
,
reverse:
true
,
child:
child
,
);
},
reverseBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Widget
child
,
)
{
return
_ZoomExitTransition
(
animation:
animation
,
child:
child
,
);
},
child:
child
,
),
);
}
}
class
__ZoomPageTransitionState
extends
State
<
_ZoomPageTransition
>
{
AnimationStatus
_currentAnimationStatus
;
AnimationStatus
_lastAnimationStatus
;
@override
void
initState
()
{
super
.
initState
();
widget
.
animation
.
addStatusListener
((
AnimationStatus
animationStatus
)
{
_lastAnimationStatus
=
_currentAnimationStatus
;
_currentAnimationStatus
=
animationStatus
;
});
}
class
_ZoomEnterTransition
extends
StatelessWidget
{
const
_ZoomEnterTransition
({
Key
key
,
@required
this
.
animation
,
this
.
reverse
=
false
,
this
.
child
,
})
:
assert
(
animation
!=
null
),
assert
(
reverse
!=
null
),
super
(
key:
key
);
// This check ensures that the animation reverses the original animation if
// the transition were interrupted midway. This prevents a disjointed
// experience since the reverse animation uses different fade and scaling
// curves.
bool
get
_transitionWasInterrupted
{
bool
wasInProgress
=
false
;
bool
isInProgress
=
false
;
switch
(
_currentAnimationStatus
)
{
case
AnimationStatus
.
completed
:
case
AnimationStatus
.
dismissed
:
isInProgress
=
false
;
break
;
case
AnimationStatus
.
forward
:
case
AnimationStatus
.
reverse
:
isInProgress
=
true
;
break
;
}
switch
(
_lastAnimationStatus
)
{
case
AnimationStatus
.
completed
:
case
AnimationStatus
.
dismissed
:
wasInProgress
=
false
;
break
;
case
AnimationStatus
.
forward
:
case
AnimationStatus
.
reverse
:
wasInProgress
=
true
;
break
;
}
return
wasInProgress
&&
isInProgress
;
}
final
Animation
<
double
>
animation
;
final
Widget
child
;
final
bool
reverse
;
@override
Widget
build
(
BuildContext
context
)
{
final
Animation
<
double
>
_forwardScrimOpacityAnimation
=
widget
.
animation
.
drive
(
_ZoomPageTransition
.
_scrimOpacityTween
.
chain
(
CurveTween
(
curve:
const
Interval
(
0.2075
,
0.4175
))));
static
final
Animatable
<
double
>
_fadeInTransition
=
Tween
<
double
>(
begin:
0.0
,
end:
1.00
,
).
chain
(
CurveTween
(
curve:
const
Interval
(
0.125
,
0.250
)));
final
Animation
<
double
>
_forwardEndScreenScaleTransition
=
widget
.
animation
.
drive
(
Tween
<
double
>(
begin:
0.85
,
end:
1.00
)
.
chain
(
_ZoomPageTransition
.
_scaleCurveSequence
));
static
final
Animatable
<
double
>
_scaleDownTransition
=
Tween
<
double
>(
begin:
1.10
,
end:
1.00
,
).
chain
(
_ZoomPageTransition
.
_scaleCurveSequence
);
final
Animation
<
double
>
_forwardStartScreenScaleTransition
=
widget
.
secondaryAnimation
.
drive
(
Tween
<
double
>(
begin:
1.00
,
end:
1.05
)
.
chain
(
_ZoomPageTransition
.
_scaleCurveSequence
));
static
final
Animatable
<
double
>
_scaleUpTransition
=
Tween
<
double
>(
begin:
0.85
,
end:
1.00
,
).
chain
(
_ZoomPageTransition
.
_scaleCurveSequence
);
final
Animation
<
double
>
_forwardEndScreenFadeTransition
=
widget
.
animation
.
drive
(
Tween
<
double
>(
begin:
0.0
,
end:
1.00
)
.
chain
(
CurveTween
(
curve:
const
Interval
(
0.125
,
0.250
))));
static
final
Animatable
<
double
>
_scrimOpacityTween
=
Tween
<
double
>(
begin:
0.0
,
end:
0.60
,
).
chain
(
CurveTween
(
curve:
const
Interval
(
0.2075
,
0.4175
)));
final
Animation
<
double
>
_reverseEndScreenScaleTransition
=
widget
.
secondaryAnimation
.
drive
(
Tween
<
double
>(
begin:
1.00
,
end:
1.10
)
.
chain
(
_ZoomPageTransition
.
_flippedScaleCurveSequence
));
@override
Widget
build
(
BuildContext
context
)
{
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
>
_reverseStartScreenScaleTransition
=
widget
.
animation
.
drive
(
Tween
<
double
>(
begin:
0.9
,
end:
1.0
)
.
chain
(
_ZoomPageTransition
.
_flippedScaleCurveSequence
)
);
final
Animation
<
double
>
fadeTransition
=
reverse
?
kAlwaysCompleteAnimation
:
_fadeInTransition
.
animate
(
animation
);
final
Animation
<
double
>
_reverseStartScreenFadeTransition
=
widget
.
animation
.
drive
(
Tween
<
double
>(
begin:
0.0
,
end:
1.00
)
.
chain
(
CurveTween
(
curve:
const
Interval
(
1
-
0.2075
,
1
-
0.0825
))));
final
Animation
<
double
>
scaleTransition
=
(
reverse
?
_scaleDownTransition
:
_scaleUpTransition
).
animate
(
animation
);
return
AnimatedBuilder
(
animation:
widget
.
animation
,
animation:
animation
,
builder:
(
BuildContext
context
,
Widget
child
)
{
if
(
widget
.
animation
.
status
==
AnimationStatus
.
forward
||
_transitionWasInterrupted
)
{
return
Container
(
color:
Colors
.
black
.
withOpacity
(
_forwardScrimOpacityAnimation
.
value
),
child:
FadeTransition
(
opacity:
_forwardEndScreenFadeTransition
,
child:
ScaleTransition
(
scale:
_forwardEndScreenScaleTransition
,
child:
child
,
),
),
);
}
else
if
(
widget
.
animation
.
status
==
AnimationStatus
.
reverse
)
{
return
ScaleTransition
(
scale:
_reverseStartScreenScaleTransition
,
child:
FadeTransition
(
opacity:
_reverseStartScreenFadeTransition
,
child:
child
,
),
);
}
return
child
;
return
Container
(
color:
Colors
.
black
.
withOpacity
(
opacity
),
child:
child
,
);
},
child:
AnimatedBuilder
(
animation:
widget
.
secondaryAnimation
,
builder:
(
BuildContext
context
,
Widget
child
)
{
if
(
widget
.
secondaryAnimation
.
status
==
AnimationStatus
.
forward
||
_transitionWasInterrupted
)
{
return
ScaleTransition
(
scale:
_forwardStartScreenScaleTransition
,
child:
child
,
);
}
else
if
(
widget
.
secondaryAnimation
.
status
==
AnimationStatus
.
reverse
)
{
return
ScaleTransition
(
scale:
_reverseEndScreenScaleTransition
,
child:
child
,
);
}
return
child
;
},
child:
widget
.
child
,
child:
FadeTransition
(
opacity:
fadeTransition
,
child:
ScaleTransition
(
scale:
scaleTransition
,
child:
child
,
),
),
);
}
}
class
_ZoomExitTransition
extends
StatelessWidget
{
const
_ZoomExitTransition
({
Key
key
,
@required
this
.
animation
,
this
.
reverse
=
false
,
this
.
child
,
})
:
assert
(
animation
!=
null
),
assert
(
reverse
!=
null
),
super
(
key:
key
);
final
Animation
<
double
>
animation
;
final
bool
reverse
;
final
Widget
child
;
static
final
Animatable
<
double
>
_fadeOutTransition
=
Tween
<
double
>(
begin:
1.0
,
end:
0.0
,
).
chain
(
CurveTween
(
curve:
const
Interval
(
0.0825
,
0.2075
)));
static
final
Animatable
<
double
>
_scaleUpTransition
=
Tween
<
double
>(
begin:
1.00
,
end:
1.05
,
).
chain
(
_ZoomPageTransition
.
_scaleCurveSequence
);
static
final
Animatable
<
double
>
_scaleDownTransition
=
Tween
<
double
>(
begin:
1.00
,
end:
0.90
,
).
chain
(
_ZoomPageTransition
.
_scaleCurveSequence
);
@override
Widget
build
(
BuildContext
context
)
{
final
Animation
<
double
>
fadeTransition
=
reverse
?
_fadeOutTransition
.
animate
(
animation
)
:
kAlwaysCompleteAnimation
;
final
Animation
<
double
>
scaleTransition
=
(
reverse
?
_scaleDownTransition
:
_scaleUpTransition
).
animate
(
animation
);
return
FadeTransition
(
opacity:
fadeTransition
,
child:
ScaleTransition
(
scale:
scaleTransition
,
child:
child
,
),
);
}
...
...
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';
export
'src/widgets/disposable_build_context.dart'
;
export
'src/widgets/drag_target.dart'
;
export
'src/widgets/draggable_scrollable_sheet.dart'
;
export
'src/widgets/dual_transition_builder.dart'
;
export
'src/widgets/editable_text.dart'
;
export
'src/widgets/fade_in_image.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() {
expect
(
find
.
text
(
'page b'
),
findsOneWidget
);
expect
(
findZoomPageTransition
(),
findsOneWidget
);
},
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