@@ -150,19 +150,19 @@ class _OpenUpwardsPageTransition extends StatelessWidget {
150
150
151
151
// Zooms and fades a new page in, zooming out the previous page. This transition
152
152
// is designed to match the Android 10 activity transition.
153
- class _ZoomPageTransition extends StatefulWidget {
153
+ class _ZoomPageTransition extends StatelessWidget {
154
+ /// Creates a [_ZoomPageTransition] .
155
+ ///
156
+ /// The [animation] and [secondaryAnimation] argument are required and must
157
+ /// not be null.
154
158
const _ZoomPageTransition ({
155
159
Key key,
156
- this .animation,
157
- this .secondaryAnimation,
160
+ @required this .animation,
161
+ @required this .secondaryAnimation,
158
162
this .child,
159
- }) : super (key: key);
160
-
161
- // The scrim obscures the old page by becoming increasingly opaque.
162
- static final Tween <double > _scrimOpacityTween = Tween <double >(
163
- begin: 0.0 ,
164
- end: 0.60 ,
165
- );
163
+ }) : assert (animation != null ),
164
+ assert (secondaryAnimation != null ),
165
+ super (key: key);
166
166
167
167
// A curve sequence that is similar to the 'fastOutExtraSlowIn' curve used in
168
168
// the native transition.
@@ -179,132 +179,207 @@ class _ZoomPageTransition extends StatefulWidget {
179
179
),
180
180
];
181
181
static final TweenSequence <double > _scaleCurveSequence = TweenSequence <double >(fastOutExtraSlowInTweenSequenceItems);
182
- static final FlippedTweenSequence _flippedScaleCurveSequence = FlippedTweenSequence (fastOutExtraSlowInTweenSequenceItems);
183
182
183
+ /// The animation that drives the [child] 's entrance and exit.
184
+ ///
185
+ /// See also:
186
+ ///
187
+ /// * [TransitionRoute.animation] , which is the value given to this property
188
+ /// when the [_ZoomPageTransition] is used as a page transition.
184
189
final Animation <double > animation;
190
+
191
+ /// The animation that transitions [child] when new content is pushed on top
192
+ /// of it.
193
+ ///
194
+ /// See also:
195
+ ///
196
+ /// * [TransitionRoute.secondaryAnimation] , which is the value given to this
197
+ // property when the [_ZoomPageTransition] is used as a page transition.
185
198
final Animation <double > secondaryAnimation;
199
+
200
+ /// The widget below this widget in the tree.
201
+ ///
202
+ /// This widget will transition in and out as driven by [animation] and
203
+ /// [secondaryAnimation] .
186
204
final Widget child;
187
205
188
206
@override
189
- __ZoomPageTransitionState createState () => __ZoomPageTransitionState ();
207
+ Widget build (BuildContext context) {
208
+ return DualTransitionBuilder (
209
+ animation: animation,
210
+ forwardBuilder: (
211
+ BuildContext context,
212
+ Animation <double > animation,
213
+ Widget child,
214
+ ) {
215
+ return _ZoomEnterTransition (
216
+ animation: animation,
217
+ child: child,
218
+ );
219
+ },
220
+ reverseBuilder: (
221
+ BuildContext context,
222
+ Animation <double > animation,
223
+ Widget child,
224
+ ) {
225
+ return _ZoomExitTransition (
226
+ animation: animation,
227
+ reverse: true ,
228
+ child: child,
229
+ );
230
+ },
231
+ child: DualTransitionBuilder (
232
+ animation: ReverseAnimation (secondaryAnimation),
233
+ forwardBuilder: (
234
+ BuildContext context,
235
+ Animation <double > animation,
236
+ Widget child,
237
+ ) {
238
+ return _ZoomEnterTransition (
239
+ animation: animation,
240
+ reverse: true ,
241
+ child: child,
242
+ );
243
+ },
244
+ reverseBuilder: (
245
+ BuildContext context,
246
+ Animation <double > animation,
247
+ Widget child,
248
+ ) {
249
+ return _ZoomExitTransition (
250
+ animation: animation,
251
+ child: child,
252
+ );
253
+ },
254
+ child: child,
255
+ ),
256
+ );
257
+ }
190
258
}
191
259
192
- class __ZoomPageTransitionState extends State <_ZoomPageTransition > {
193
- AnimationStatus _currentAnimationStatus;
194
- AnimationStatus _lastAnimationStatus;
195
-
196
- @override
197
- void initState () {
198
- super .initState ();
199
- widget.animation.addStatusListener ((AnimationStatus animationStatus) {
200
- _lastAnimationStatus = _currentAnimationStatus;
201
- _currentAnimationStatus = animationStatus;
202
- });
203
- }
260
+ class _ZoomEnterTransition extends StatelessWidget {
261
+ const _ZoomEnterTransition ({
262
+ Key key,
263
+ @required this .animation,
264
+ this .reverse = false ,
265
+ this .child,
266
+ }) : assert (animation != null ),
267
+ assert (reverse != null ),
268
+ super (key: key);
204
269
205
- // This check ensures that the animation reverses the original animation if
206
- // the transition were interrupted midway. This prevents a disjointed
207
- // experience since the reverse animation uses different fade and scaling
208
- // curves.
209
- bool get _transitionWasInterrupted {
210
- bool wasInProgress = false ;
211
- bool isInProgress = false ;
212
-
213
- switch (_currentAnimationStatus) {
214
- case AnimationStatus .completed:
215
- case AnimationStatus .dismissed:
216
- isInProgress = false ;
217
- break ;
218
- case AnimationStatus .forward:
219
- case AnimationStatus .reverse:
220
- isInProgress = true ;
221
- break ;
222
- }
223
- switch (_lastAnimationStatus) {
224
- case AnimationStatus .completed:
225
- case AnimationStatus .dismissed:
226
- wasInProgress = false ;
227
- break ;
228
- case AnimationStatus .forward:
229
- case AnimationStatus .reverse:
230
- wasInProgress = true ;
231
- break ;
232
- }
233
- return wasInProgress && isInProgress;
234
- }
270
+ final Animation <double > animation;
271
+ final Widget child;
272
+ final bool reverse;
235
273
236
- @override
237
- Widget build (BuildContext context) {
238
- final Animation <double > _forwardScrimOpacityAnimation = widget.animation.drive (
239
- _ZoomPageTransition ._scrimOpacityTween
240
- .chain (CurveTween (curve: const Interval (0.2075 , 0.4175 ))));
274
+ static final Animatable <double > _fadeInTransition = Tween <double >(
275
+ begin: 0.0 ,
276
+ end: 1.00 ,
277
+ ).chain (CurveTween (curve: const Interval (0.125 , 0.250 )));
241
278
242
- final Animation <double > _forwardEndScreenScaleTransition = widget.animation.drive (
243
- Tween <double >(begin: 0.85 , end: 1.00 )
244
- .chain (_ZoomPageTransition ._scaleCurveSequence));
279
+ static final Animatable <double > _scaleDownTransition = Tween <double >(
280
+ begin: 1.10 ,
281
+ end: 1.00 ,
282
+ ).chain (_ZoomPageTransition ._scaleCurveSequence);
245
283
246
- final Animation <double > _forwardStartScreenScaleTransition = widget.secondaryAnimation.drive (
247
- Tween <double >(begin: 1.00 , end: 1.05 )
248
- .chain (_ZoomPageTransition ._scaleCurveSequence));
284
+ static final Animatable <double > _scaleUpTransition = Tween <double >(
285
+ begin: 0.85 ,
286
+ end: 1.00 ,
287
+ ).chain (_ZoomPageTransition ._scaleCurveSequence);
249
288
250
- final Animation <double > _forwardEndScreenFadeTransition = widget.animation.drive (
251
- Tween <double >(begin: 0.0 , end: 1.00 )
252
- .chain (CurveTween (curve: const Interval (0.125 , 0.250 ))));
289
+ static final Animatable <double > _scrimOpacityTween = Tween <double >(
290
+ begin: 0.0 ,
291
+ end: 0.60 ,
292
+ ).chain (CurveTween (curve: const Interval (0.2075 , 0.4175 )));
253
293
254
- final Animation <double > _reverseEndScreenScaleTransition = widget.secondaryAnimation.drive (
255
- Tween <double >(begin: 1.00 , end: 1.10 )
256
- .chain (_ZoomPageTransition ._flippedScaleCurveSequence));
294
+ @override
295
+ Widget build (BuildContext context) {
296
+ double opacity = 0 ;
297
+ // The transition's scrim opacity only increases on the forward transition. In the reverse
298
+ // transition, the opacity should always be 0.0.
299
+ //
300
+ // Therefore, we need to only apply the scrim opacity animation when the transition
301
+ // is running forwards.
302
+ //
303
+ // The reason that we check that the animation's status is not `completed` instead
304
+ // of checking that it is `forward` is that this allows the interrupted reversal of the
305
+ // forward transition to smoothly fade the scrim away. This prevents a disjointed
306
+ // removal of the scrim.
307
+ if (! reverse && animation.status != AnimationStatus .completed) {
308
+ opacity = _scrimOpacityTween.evaluate (animation);
309
+ }
257
310
258
- final Animation <double > _reverseStartScreenScaleTransition = widget.animation. drive (
259
- Tween < double >(begin : 0.9 , end : 1.0 )
260
- . chain ( _ZoomPageTransition ._flippedScaleCurveSequence) );
311
+ final Animation <double > fadeTransition = reverse
312
+ ? kAlwaysCompleteAnimation
313
+ : _fadeInTransition. animate (animation );
261
314
262
- final Animation <double > _reverseStartScreenFadeTransition = widget.animation.drive (
263
- Tween <double >(begin: 0.0 , end: 1.00 )
264
- .chain (CurveTween (curve: const Interval (1 - 0.2075 , 1 - 0.0825 ))));
315
+ final Animation <double > scaleTransition = (reverse
316
+ ? _scaleDownTransition
317
+ : _scaleUpTransition
318
+ ).animate (animation);
265
319
266
320
return AnimatedBuilder (
267
- animation: widget. animation,
321
+ animation: animation,
268
322
builder: (BuildContext context, Widget child) {
269
- if (widget.animation.status == AnimationStatus .forward || _transitionWasInterrupted) {
270
- return Container (
271
- color: Colors .black.withOpacity (_forwardScrimOpacityAnimation.value),
272
- child: FadeTransition (
273
- opacity: _forwardEndScreenFadeTransition,
274
- child: ScaleTransition (
275
- scale: _forwardEndScreenScaleTransition,
276
- child: child,
277
- ),
278
- ),
279
- );
280
- } else if (widget.animation.status == AnimationStatus .reverse) {
281
- return ScaleTransition (
282
- scale: _reverseStartScreenScaleTransition,
283
- child: FadeTransition (
284
- opacity: _reverseStartScreenFadeTransition,
285
- child: child,
286
- ),
287
- );
288
- }
289
- return child;
323
+ return Container (
324
+ color: Colors .black.withOpacity (opacity),
325
+ child: child,
326
+ );
290
327
},
291
- child: AnimatedBuilder (
292
- animation: widget.secondaryAnimation,
293
- builder: (BuildContext context, Widget child) {
294
- if (widget.secondaryAnimation.status == AnimationStatus .forward || _transitionWasInterrupted) {
295
- return ScaleTransition (
296
- scale: _forwardStartScreenScaleTransition,
297
- child: child,
298
- );
299
- } else if (widget.secondaryAnimation.status == AnimationStatus .reverse) {
300
- return ScaleTransition (
301
- scale: _reverseEndScreenScaleTransition,
302
- child: child,
303
- );
304
- }
305
- return child;
306
- },
307
- child: widget.child,
328
+ child: FadeTransition (
329
+ opacity: fadeTransition,
330
+ child: ScaleTransition (
331
+ scale: scaleTransition,
332
+ child: child,
333
+ ),
334
+ ),
335
+ );
336
+ }
337
+ }
338
+
339
+ class _ZoomExitTransition extends StatelessWidget {
340
+ const _ZoomExitTransition ({
341
+ Key key,
342
+ @required this .animation,
343
+ this .reverse = false ,
344
+ this .child,
345
+ }) : assert (animation != null ),
346
+ assert (reverse != null ),
347
+ super (key: key);
348
+
349
+ final Animation <double > animation;
350
+ final bool reverse;
351
+ final Widget child;
352
+
353
+ static final Animatable <double > _fadeOutTransition = Tween <double >(
354
+ begin: 1.0 ,
355
+ end: 0.0 ,
356
+ ).chain (CurveTween (curve: const Interval (0.0825 , 0.2075 )));
357
+
358
+ static final Animatable <double > _scaleUpTransition = Tween <double >(
359
+ begin: 1.00 ,
360
+ end: 1.05 ,
361
+ ).chain (_ZoomPageTransition ._scaleCurveSequence);
362
+
363
+ static final Animatable <double > _scaleDownTransition = Tween <double >(
364
+ begin: 1.00 ,
365
+ end: 0.90 ,
366
+ ).chain (_ZoomPageTransition ._scaleCurveSequence);
367
+
368
+ @override
369
+ Widget build (BuildContext context) {
370
+ final Animation <double > fadeTransition = reverse
371
+ ? _fadeOutTransition.animate (animation)
372
+ : kAlwaysCompleteAnimation;
373
+ final Animation <double > scaleTransition = (reverse
374
+ ? _scaleDownTransition
375
+ : _scaleUpTransition
376
+ ).animate (animation);
377
+
378
+ return FadeTransition (
379
+ opacity: fadeTransition,
380
+ child: ScaleTransition (
381
+ scale: scaleTransition,
382
+ child: child,
308
383
),
309
384
);
310
385
}
0 commit comments