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
64727975
Unverified
Commit
64727975
authored
Jun 30, 2021
by
Jim Graham
Committed by
GitHub
Jun 30, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add AnimatedScale and AnimatedRotation widgets (#83428)
parent
d5710dfa
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
431 additions
and
1 deletion
+431
-1
implicit_animations.dart
packages/flutter/lib/src/widgets/implicit_animations.dart
+261
-0
implicit_animations_test.dart
packages/flutter/test/widgets/implicit_animations_test.dart
+170
-1
No files found.
packages/flutter/lib/src/widgets/implicit_animations.dart
View file @
64727975
...
...
@@ -255,6 +255,8 @@ class TextStyleTween extends Tween<TextStyle> {
/// [Container].
/// * [AnimatedDefaultTextStyle], which is an implicitly animated version of
/// [DefaultTextStyle].
/// * [AnimatedScale], which is an implicitly animated version of [Transform.scale].
/// * [AnimatedRotation], which is an implicitly animated version of [Transform.rotate].
/// * [AnimatedOpacity], which is an implicitly animated version of [Opacity].
/// * [AnimatedPadding], which is an implicitly animated version of [Padding].
/// * [AnimatedPhysicalModel], which is an implicitly animated version of
...
...
@@ -1410,6 +1412,265 @@ class _AnimatedPositionedDirectionalState extends AnimatedWidgetBaseState<Animat
}
}
/// Animated version of [Transform.scale] which automatically transitions the child's
/// scale over a given duration whenever the given scale changes.
///
/// {@tool snippet}
///
/// This code defines a widget that uses [AnimatedScale] to change the size
/// of [FlutterLogo] gradually to a new scale whenever the button is pressed.
///
/// ```dart
/// class LogoScale extends StatefulWidget {
/// const LogoScale({Key? key}) : super(key: key);
///
/// @override
/// State<LogoScale> createState() => LogoScaleState();
/// }
///
/// class LogoScaleState extends State<LogoScale> {
/// double scale = 1.0;
///
/// void _changeScale() {
/// setState(() => scale = scale == 1.0 ? 3.0 : 1.0);
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Column(
/// mainAxisAlignment: MainAxisAlignment.center,
/// children: <Widget>[
/// ElevatedButton(
/// child: const Text('Scale Logo'),
/// onPressed: _changeScale,
/// ),
/// Padding(
/// padding: const EdgeInsets.all(50),
/// child: AnimatedScale(
/// scale: scale,
/// duration: const Duration(seconds: 2),
/// child: const FlutterLogo(),
/// ),
/// ),
/// ],
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [AnimatedRotation], for animating the rotation of a child.
/// * [AnimatedSize], for animating the resize of a child based on changes
/// in layout.
/// * [ScaleTransition], an explicitly animated version of this widget, where
/// an [Animation] is provided by the caller instead of being built in.
class
AnimatedScale
extends
ImplicitlyAnimatedWidget
{
/// Creates a widget that animates its scale implicitly.
///
/// The [scale] argument must not be null.
/// The [curve] and [duration] arguments must not be null.
const
AnimatedScale
({
Key
?
key
,
this
.
child
,
required
this
.
scale
,
this
.
alignment
=
Alignment
.
center
,
this
.
filterQuality
,
Curve
curve
=
Curves
.
linear
,
required
Duration
duration
,
VoidCallback
?
onEnd
,
})
:
assert
(
scale
!=
null
),
super
(
key:
key
,
curve:
curve
,
duration:
duration
,
onEnd:
onEnd
);
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final
Widget
?
child
;
/// The target scale.
///
/// The scale must not be null.
final
double
scale
;
/// The alignment of the origin of the coordinate system in which the scale
/// takes place, relative to the size of the box.
///
/// For example, to set the origin of the scale to bottom middle, you can use
/// an alignment of (0.0, 1.0).
final
Alignment
alignment
;
/// The filter quality with which to apply the transform as a bitmap operation.
///
/// {@macro flutter.widgets.Transform.optional.FilterQuality}
final
FilterQuality
?
filterQuality
;
@override
ImplicitlyAnimatedWidgetState
<
AnimatedScale
>
createState
()
=>
_AnimatedScaleState
();
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DoubleProperty
(
'scale'
,
scale
));
properties
.
add
(
DiagnosticsProperty
<
Alignment
>(
'alignment'
,
alignment
,
defaultValue:
Alignment
.
center
));
properties
.
add
(
EnumProperty
<
FilterQuality
>(
'filterQuality'
,
filterQuality
,
defaultValue:
null
));
}
}
class
_AnimatedScaleState
extends
ImplicitlyAnimatedWidgetState
<
AnimatedScale
>
{
Tween
<
double
>?
_scale
;
late
Animation
<
double
>
_scaleAnimation
;
@override
void
forEachTween
(
TweenVisitor
<
dynamic
>
visitor
)
{
_scale
=
visitor
(
_scale
,
widget
.
scale
,
(
dynamic
value
)
=>
Tween
<
double
>(
begin:
value
as
double
))
as
Tween
<
double
>?;
}
@override
void
didUpdateTweens
()
{
_scaleAnimation
=
animation
.
drive
(
_scale
!);
}
@override
Widget
build
(
BuildContext
context
)
{
return
ScaleTransition
(
scale:
_scaleAnimation
,
alignment:
widget
.
alignment
,
filterQuality:
widget
.
filterQuality
,
child:
widget
.
child
,
);
}
}
/// Animated version of [Transform.rotate] which automatically transitions the child's
/// rotation over a given duration whenever the given rotation changes.
///
/// {@tool snippet}
///
/// This code defines a widget that uses [AnimatedRotation] to rotate a [FlutterLogo]
/// gradually by an eighth of a turn (45 degrees) with each press of the button.
///
/// ```dart
/// class LogoRotate extends StatefulWidget {
/// const LogoRotate({Key? key}) : super(key: key);
///
/// @override
/// State<LogoRotate> createState() => LogoRotateState();
/// }
///
/// class LogoRotateState extends State<LogoRotate> {
/// double turns = 0.0;
///
/// void _changeRotation() {
/// setState(() => turns += 1.0 / 8.0);
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Column(
/// mainAxisAlignment: MainAxisAlignment.center,
/// children: <Widget>[
/// ElevatedButton(
/// child: const Text('Rotate Logo'),
/// onPressed: _changeRotation,
/// ),
/// Padding(
/// padding: const EdgeInsets.all(50),
/// child: AnimatedRotation(
/// turns: turns,
/// duration: const Duration(seconds: 1),
/// child: const FlutterLogo(),
/// ),
/// ),
/// ],
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [AnimatedScale], for animating the scale of a child.
/// * [RotationTransition], an explicitly animated version of this widget, where
/// an [Animation] is provided by the caller instead of being built in.
class
AnimatedRotation
extends
ImplicitlyAnimatedWidget
{
/// Creates a widget that animates its rotation implicitly.
///
/// The [turns] argument must not be null.
/// The [curve] and [duration] arguments must not be null.
const
AnimatedRotation
({
Key
?
key
,
this
.
child
,
required
this
.
turns
,
this
.
alignment
=
Alignment
.
center
,
this
.
filterQuality
,
Curve
curve
=
Curves
.
linear
,
required
Duration
duration
,
VoidCallback
?
onEnd
,
})
:
assert
(
turns
!=
null
),
super
(
key:
key
,
curve:
curve
,
duration:
duration
,
onEnd:
onEnd
);
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final
Widget
?
child
;
/// The animation that controls the rotation of the child.
///
/// If the current value of the turns animation is v, the child will be
/// rotated v * 2 * pi radians before being painted.
final
double
turns
;
/// The alignment of the origin of the coordinate system in which the rotation
/// takes place, relative to the size of the box.
///
/// For example, to set the origin of the rotation to bottom middle, you can use
/// an alignment of (0.0, 1.0).
final
Alignment
alignment
;
/// The filter quality with which to apply the transform as a bitmap operation.
///
/// {@macro flutter.widgets.Transform.optional.FilterQuality}
final
FilterQuality
?
filterQuality
;
@override
ImplicitlyAnimatedWidgetState
<
AnimatedRotation
>
createState
()
=>
_AnimatedRotationState
();
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DoubleProperty
(
'turns'
,
turns
));
properties
.
add
(
DiagnosticsProperty
<
Alignment
>(
'alignment'
,
alignment
,
defaultValue:
Alignment
.
center
));
properties
.
add
(
EnumProperty
<
FilterQuality
>(
'filterQuality'
,
filterQuality
,
defaultValue:
null
));
}
}
class
_AnimatedRotationState
extends
ImplicitlyAnimatedWidgetState
<
AnimatedRotation
>
{
Tween
<
double
>?
_turns
;
late
Animation
<
double
>
_turnsAnimation
;
@override
void
forEachTween
(
TweenVisitor
<
dynamic
>
visitor
)
{
_turns
=
visitor
(
_turns
,
widget
.
turns
,
(
dynamic
value
)
=>
Tween
<
double
>(
begin:
value
as
double
))
as
Tween
<
double
>?;
}
@override
void
didUpdateTweens
()
{
_turnsAnimation
=
animation
.
drive
(
_turns
!);
}
@override
Widget
build
(
BuildContext
context
)
{
return
RotationTransition
(
turns:
_turnsAnimation
,
alignment:
widget
.
alignment
,
filterQuality:
widget
.
filterQuality
,
child:
widget
.
child
,
);
}
}
/// Animated version of [Opacity] which automatically transitions the child's
/// opacity over a given duration whenever the given opacity changes.
///
...
...
packages/flutter/test/widgets/implicit_animations_test.dart
View file @
64727975
...
...
@@ -171,6 +171,122 @@ void main() {
expect
(
mockOnEndFunction
.
called
,
1
);
});
testWidgets
(
'AnimatedScale onEnd callback test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
wrap
(
child:
TestAnimatedWidget
(
callback:
mockOnEndFunction
.
handler
,
switchKey:
switchKey
,
state:
_TestAnimatedScaleWidgetState
(),
),
));
final
Finder
widgetFinder
=
find
.
byKey
(
switchKey
);
await
tester
.
tap
(
widgetFinder
);
await
tester
.
pump
();
expect
(
mockOnEndFunction
.
called
,
0
);
await
tester
.
pump
(
animationDuration
);
expect
(
mockOnEndFunction
.
called
,
0
);
await
tester
.
pump
(
additionalDelay
);
expect
(
mockOnEndFunction
.
called
,
1
);
});
testWidgets
(
'AnimatedScale transition test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
wrap
(
child:
TestAnimatedWidget
(
switchKey:
switchKey
,
state:
_TestAnimatedScaleWidgetState
(),
),
));
final
RebuildCountingState
<
StatefulWidget
>
state
=
tester
.
widget
<
TestAnimatedWidget
>(
find
.
byType
(
TestAnimatedWidget
)
).
rebuildState
!;
final
Finder
switchFinder
=
find
.
byKey
(
switchKey
);
final
ScaleTransition
scaleWidget
=
tester
.
widget
<
ScaleTransition
>(
find
.
ancestor
(
of:
find
.
byType
(
Placeholder
),
matching:
find
.
byType
(
ScaleTransition
),
).
first
,
);
expect
(
state
.
builds
,
equals
(
1
));
await
tester
.
tap
(
switchFinder
);
expect
(
state
.
builds
,
equals
(
1
));
await
tester
.
pump
();
expect
(
scaleWidget
.
scale
.
value
,
equals
(
1.0
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
expect
(
scaleWidget
.
scale
.
value
,
equals
(
1.5
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
250
));
expect
(
scaleWidget
.
scale
.
value
,
equals
(
1.75
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
250
));
expect
(
scaleWidget
.
scale
.
value
,
equals
(
2.0
));
expect
(
state
.
builds
,
equals
(
2
));
});
testWidgets
(
'AnimatedRotation onEnd callback test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
wrap
(
child:
TestAnimatedWidget
(
callback:
mockOnEndFunction
.
handler
,
switchKey:
switchKey
,
state:
_TestAnimatedRotationWidgetState
(),
),
));
final
Finder
widgetFinder
=
find
.
byKey
(
switchKey
);
await
tester
.
tap
(
widgetFinder
);
await
tester
.
pump
();
expect
(
mockOnEndFunction
.
called
,
0
);
await
tester
.
pump
(
animationDuration
);
expect
(
mockOnEndFunction
.
called
,
0
);
await
tester
.
pump
(
additionalDelay
);
expect
(
mockOnEndFunction
.
called
,
1
);
});
testWidgets
(
'AnimatedRotation transition test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
wrap
(
child:
TestAnimatedWidget
(
switchKey:
switchKey
,
state:
_TestAnimatedRotationWidgetState
(),
),
));
final
RebuildCountingState
<
StatefulWidget
>
state
=
tester
.
widget
<
TestAnimatedWidget
>(
find
.
byType
(
TestAnimatedWidget
)
).
rebuildState
!;
final
Finder
switchFinder
=
find
.
byKey
(
switchKey
);
final
RotationTransition
rotationWidget
=
tester
.
widget
<
RotationTransition
>(
find
.
ancestor
(
of:
find
.
byType
(
Placeholder
),
matching:
find
.
byType
(
RotationTransition
),
).
first
,
);
expect
(
state
.
builds
,
equals
(
1
));
await
tester
.
tap
(
switchFinder
);
expect
(
state
.
builds
,
equals
(
1
));
await
tester
.
pump
();
expect
(
rotationWidget
.
turns
.
value
,
equals
(
0.0
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
expect
(
rotationWidget
.
turns
.
value
,
equals
(
0.75
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
250
));
expect
(
rotationWidget
.
turns
.
value
,
equals
(
1.125
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
250
));
expect
(
rotationWidget
.
turns
.
value
,
equals
(
1.5
));
expect
(
state
.
builds
,
equals
(
2
));
});
testWidgets
(
'AnimatedOpacity onEnd callback test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
wrap
(
child:
TestAnimatedWidget
(
...
...
@@ -199,6 +315,9 @@ void main() {
),
));
final
RebuildCountingState
<
StatefulWidget
>
state
=
tester
.
widget
<
TestAnimatedWidget
>(
find
.
byType
(
TestAnimatedWidget
)
).
rebuildState
!;
final
Finder
switchFinder
=
find
.
byKey
(
switchKey
);
final
FadeTransition
opacityWidget
=
tester
.
widget
<
FadeTransition
>(
find
.
ancestor
(
...
...
@@ -207,16 +326,23 @@ void main() {
).
first
,
);
expect
(
state
.
builds
,
equals
(
1
));
await
tester
.
tap
(
switchFinder
);
expect
(
state
.
builds
,
equals
(
1
));
await
tester
.
pump
();
expect
(
opacityWidget
.
opacity
.
value
,
equals
(
0.0
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
expect
(
opacityWidget
.
opacity
.
value
,
equals
(
0.5
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
250
));
expect
(
opacityWidget
.
opacity
.
value
,
equals
(
0.75
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
250
));
expect
(
opacityWidget
.
opacity
.
value
,
equals
(
1.0
));
expect
(
state
.
builds
,
equals
(
2
));
});
...
...
@@ -247,6 +373,9 @@ void main() {
),
));
final
RebuildCountingState
<
StatefulWidget
>
state
=
tester
.
widget
<
TestAnimatedWidget
>(
find
.
byType
(
TestAnimatedWidget
)
).
rebuildState
!;
final
Finder
switchFinder
=
find
.
byKey
(
switchKey
);
final
SliverFadeTransition
opacityWidget
=
tester
.
widget
<
SliverFadeTransition
>(
find
.
ancestor
(
...
...
@@ -255,16 +384,23 @@ void main() {
).
first
,
);
expect
(
state
.
builds
,
equals
(
1
));
await
tester
.
tap
(
switchFinder
);
expect
(
state
.
builds
,
equals
(
1
));
await
tester
.
pump
();
expect
(
opacityWidget
.
opacity
.
value
,
equals
(
0.0
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
expect
(
opacityWidget
.
opacity
.
value
,
equals
(
0.5
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
250
));
expect
(
opacityWidget
.
opacity
.
value
,
equals
(
0.75
));
expect
(
state
.
builds
,
equals
(
2
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
250
));
expect
(
opacityWidget
.
opacity
.
value
,
equals
(
1.0
));
expect
(
state
.
builds
,
equals
(
2
));
});
testWidgets
(
'AnimatedDefaultTextStyle onEnd callback test'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -414,6 +550,10 @@ Widget wrap({required Widget child}) {
);
}
abstract
class
RebuildCountingState
<
T
extends
StatefulWidget
>
extends
State
<
T
>
{
int
builds
=
0
;
}
class
TestAnimatedWidget
extends
StatefulWidget
{
const
TestAnimatedWidget
({
Key
?
key
,
...
...
@@ -425,11 +565,14 @@ class TestAnimatedWidget extends StatefulWidget {
final
Key
switchKey
;
final
State
<
StatefulWidget
>
state
;
RebuildCountingState
<
StatefulWidget
>?
get
rebuildState
=>
state
is
RebuildCountingState
<
StatefulWidget
>
?
state
as
RebuildCountingState
<
StatefulWidget
>
:
null
;
@override
State
<
StatefulWidget
>
createState
()
=>
state
;
// ignore: no_logic_in_create_state, this test predates the lint
}
abstract
class
_TestAnimatedWidgetState
extends
State
<
TestAnimatedWidget
>
{
abstract
class
_TestAnimatedWidgetState
extends
RebuildCounting
State
<
TestAnimatedWidget
>
{
bool
toggle
=
false
;
final
Widget
child
=
const
Placeholder
();
final
Duration
duration
=
animationDuration
;
...
...
@@ -444,6 +587,7 @@ abstract class _TestAnimatedWidgetState extends State<TestAnimatedWidget> {
@override
Widget
build
(
BuildContext
context
)
{
builds
++;
final
Widget
animatedWidget
=
getAnimatedWidget
();
return
Stack
(
...
...
@@ -516,6 +660,30 @@ class _TestAnimatedPositionedDirectionalWidgetState extends _TestAnimatedWidgetS
}
}
class
_TestAnimatedScaleWidgetState
extends
_TestAnimatedWidgetState
{
@override
Widget
getAnimatedWidget
()
{
return
AnimatedScale
(
duration:
duration
,
onEnd:
widget
.
callback
,
scale:
toggle
?
2.0
:
1.0
,
child:
child
,
);
}
}
class
_TestAnimatedRotationWidgetState
extends
_TestAnimatedWidgetState
{
@override
Widget
getAnimatedWidget
()
{
return
AnimatedRotation
(
duration:
duration
,
onEnd:
widget
.
callback
,
turns:
toggle
?
1.5
:
0.0
,
child:
child
,
);
}
}
class
_TestAnimatedOpacityWidgetState
extends
_TestAnimatedWidgetState
{
@override
Widget
getAnimatedWidget
()
{
...
...
@@ -541,6 +709,7 @@ class _TestSliverAnimatedOpacityWidgetState extends _TestAnimatedWidgetState {
@override
Widget
build
(
BuildContext
context
)
{
builds
++;
final
Widget
animatedWidget
=
getAnimatedWidget
();
return
Material
(
...
...
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