@@ -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 StatelessWidget {
154
- /// Creates a [_ZoomPageTransition] .
155
- ///
156
- /// The [animation] and [secondaryAnimation] argument are required and must
157
- /// not be null.
153
+ class _ZoomPageTransition extends StatefulWidget {
158
154
const _ZoomPageTransition ({
159
155
Key key,
160
- @required this .animation,
161
- @required this .secondaryAnimation,
156
+ this .animation,
157
+ this .secondaryAnimation,
162
158
this .child,
163
- }) : assert (animation != null ),
164
- assert (secondaryAnimation != null ),
165
- super (key: key);
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
+ );
166
166
167
167
// A curve sequence that is similar to the 'fastOutExtraSlowIn' curve used in
168
168
// the native transition.
@@ -179,207 +179,132 @@ class _ZoomPageTransition extends StatelessWidget {
179
179
),
180
180
];
181
181
static final TweenSequence <double > _scaleCurveSequence = TweenSequence <double >(fastOutExtraSlowInTweenSequenceItems);
182
+ static final FlippedTweenSequence _flippedScaleCurveSequence = FlippedTweenSequence (fastOutExtraSlowInTweenSequenceItems);
182
183
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.
189
184
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.
198
185
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] .
204
186
final Widget child;
205
187
206
188
@override
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
- }
189
+ __ZoomPageTransitionState createState () => __ZoomPageTransitionState ();
258
190
}
259
191
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);
269
-
270
- final Animation <double > animation;
271
- final Widget child;
272
- final bool reverse;
273
-
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 )));
278
-
279
- static final Animatable <double > _scaleDownTransition = Tween <double >(
280
- begin: 1.10 ,
281
- end: 1.00 ,
282
- ).chain (_ZoomPageTransition ._scaleCurveSequence);
192
+ class __ZoomPageTransitionState extends State <_ZoomPageTransition > {
193
+ AnimationStatus _currentAnimationStatus;
194
+ AnimationStatus _lastAnimationStatus;
283
195
284
- static final Animatable <double > _scaleUpTransition = Tween <double >(
285
- begin: 0.85 ,
286
- end: 1.00 ,
287
- ).chain (_ZoomPageTransition ._scaleCurveSequence);
196
+ @override
197
+ void initState () {
198
+ super .initState ();
199
+ widget.animation.addStatusListener ((AnimationStatus animationStatus) {
200
+ _lastAnimationStatus = _currentAnimationStatus;
201
+ _currentAnimationStatus = animationStatus;
202
+ });
203
+ }
288
204
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 )));
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
+ }
293
235
294
236
@override
295
237
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
- }
238
+ final Animation <double > _forwardScrimOpacityAnimation = widget.animation.drive (
239
+ _ZoomPageTransition ._scrimOpacityTween
240
+ .chain (CurveTween (curve: const Interval (0.2075 , 0.4175 ))));
310
241
311
- final Animation <double > fadeTransition = reverse
312
- ? kAlwaysCompleteAnimation
313
- : _fadeInTransition. animate (animation );
242
+ final Animation <double > _forwardEndScreenScaleTransition = widget.animation. drive (
243
+ Tween < double >(begin : 0.85 , end : 1.00 )
244
+ . chain ( _ZoomPageTransition ._scaleCurveSequence) );
314
245
315
- final Animation <double > scaleTransition = (reverse
316
- ? _scaleDownTransition
317
- : _scaleUpTransition
318
- ).animate (animation);
246
+ final Animation <double > _forwardStartScreenScaleTransition = widget.secondaryAnimation.drive (
247
+ Tween <double >(begin: 1.00 , end: 1.05 )
248
+ .chain (_ZoomPageTransition ._scaleCurveSequence));
319
249
320
- return AnimatedBuilder (
321
- animation: animation,
322
- builder: (BuildContext context, Widget child) {
323
- return Container (
324
- color: Colors .black.withOpacity (opacity),
325
- child: child,
326
- );
327
- },
328
- child: FadeTransition (
329
- opacity: fadeTransition,
330
- child: ScaleTransition (
331
- scale: scaleTransition,
332
- child: child,
333
- ),
334
- ),
335
- );
336
- }
337
- }
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 ))));
338
253
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);
254
+ final Animation <double > _reverseEndScreenScaleTransition = widget.secondaryAnimation.drive (
255
+ Tween <double >(begin: 1.00 , end: 1.10 )
256
+ .chain (_ZoomPageTransition ._flippedScaleCurveSequence));
348
257
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);
258
+ final Animation <double > _reverseStartScreenScaleTransition = widget.animation.drive (
259
+ Tween <double >(begin: 0.9 , end: 1.0 )
260
+ .chain (_ZoomPageTransition ._flippedScaleCurveSequence));
362
261
363
- static final Animatable <double > _scaleDownTransition = Tween <double >(
364
- begin: 1.00 ,
365
- end: 0.90 ,
366
- ).chain (_ZoomPageTransition ._scaleCurveSequence);
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 ))));
367
265
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,
266
+ return AnimatedBuilder (
267
+ animation: widget.animation,
268
+ 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;
290
+ },
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,
383
308
),
384
309
);
385
310
}
0 commit comments