_placeholderSize = null; if (mounted) { // Tell the widget to rebuild if it's mounted. _placeholderSize has already // been updated. setState(() {}); } }
Widget build(BuildContext context) { assert( context.findAncestorWidgetOfExactType<Hero>() == null, 'A Hero widget cannot be the descendant of another Hero widget.', );
// If we're transitioning between different page routes, start a hero transition // after the toRoute has been laid out with its animation's value at 1.0. void _maybeStartHeroTransition( Route<dynamic>? fromRoute, Route<dynamic>? toRoute, HeroFlightDirection flightType, bool isUserGestureTransition, ) { if (toRoute != fromRoute && toRoute is PageRoute<dynamic> && fromRoute is PageRoute<dynamic>) { final PageRoute<dynamic> from = fromRoute; final PageRoute<dynamic> to = toRoute;
// A user gesture may have already completed the pop, or we might be the initial route switch (flightType) { case HeroFlightDirection.pop: if (from.animation!.value == 0.0) { return; } break; case HeroFlightDirection.push: if (to.animation!.value == 1.0) { return; } break; }
// Putting a route offstage changes its animation value to 1.0. Once this // frame completes, we'll know where the heroes in the `to` route are // going to end up, and the `to` route will go back onstage. to.offstage = to.animation!.value == 0.0;
// Find the matching pairs of heroes in from and to and either start or a new // hero flight, or divert an existing one. void _startHeroTransition( PageRoute<dynamic> from, PageRoute<dynamic> to, HeroFlightDirection flightType, bool isUserGestureTransition, ) { // If the `to` route was offstage, then we're implicitly restoring its // animation value back to what it was before it was "moved" offstage. to.offstage = false;
final NavigatorState? navigator = this.navigator; // 注意这里获取到了OverlayState,用来放置Hero过渡动画 final OverlayState? overlay = navigator?.overlay; // If the navigator or the overlay was removed before this end-of-frame // callback was called, then don't actually start a transition, and we don' // t have to worry about any Hero widget we might have hidden in a previous // flight, or ongoing flights. if (navigator == null || overlay == null) return;
final RenderObject? navigatorRenderObject = navigator.context.findRenderObject();
if (navigatorRenderObject is! RenderBox) { assert(false, 'Navigator $navigator has an invalid RenderObject type ${navigatorRenderObject.runtimeType}.'); return; } assert(navigatorRenderObject.hasSize);
// At this point, the toHeroes may have been built and laid out for the first time. // // If `fromSubtreeContext` is null, call endFlight on all toHeroes, for good measure. // If `toSubtreeContext` is null abort existingFlights. final BuildContext? fromSubtreeContext = from.subtreeContext; finalMap<Object, _HeroState> fromHeroes = fromSubtreeContext != null ? Hero._allHeroesFor(fromSubtreeContext, isUserGestureTransition, navigator) : const <Object, _HeroState>{}; final BuildContext? toSubtreeContext = to.subtreeContext; finalMap<Object, _HeroState> toHeroes = toSubtreeContext != null ? Hero._allHeroesFor(toSubtreeContext, isUserGestureTransition, navigator) : const <Object, _HeroState>{};
for (final MapEntry<Object, _HeroState> fromHeroEntry in fromHeroes.entries) { finalObject tag = fromHeroEntry.key; final _HeroState fromHero = fromHeroEntry.value; final _HeroState? toHero = toHeroes[tag]; final _HeroFlight? existingFlight = _flights[tag]; final _HeroFlightManifest? manifest = toHero == null ? null : _HeroFlightManifest( type: flightType, overlay: overlay, navigatorSize: navigatorRenderObject.size, fromRoute: from, toRoute: to, fromHero: fromHero, toHero: toHero, createRectTween: createRectTween, // 优先使用toHero、fromHero指定的flightShuttleBuilder,没有的话 // 使用默认的shuttleBuilder,也就是toHero.child shuttleBuilder: toHero.widget.flightShuttleBuilder ?? fromHero.widget.flightShuttleBuilder ?? _defaultHeroFlightShuttleBuilder, isUserGestureTransition: isUserGestureTransition, isDiverted: existingFlight != null, );
// Only proceed with a valid manifest. Otherwise abort the existing // flight, and call endFlight when this for loop finishes. if (manifest != null && manifest.isValid) { toHeroes.remove(tag); if (existingFlight != null) { // 如果已经存在Hero过渡动画,则将其转到新的方向 existingFlight.divert(manifest); } else { // 开始全新的Hero过渡动画 _flights[tag] = _HeroFlight(_handleFlightEnded)..start(manifest); } } else { existingFlight?.abort(); } }
// The remaining entries in toHeroes are those failed to participate in a // new flight (for not having a valid manifest). // // This can happen in a route pop transition when a fromHero is no longer // mounted, or kept alive by the [KeepAlive] mechanism but no longer visible. // TODO(LongCatIsLooong): resume aborted flights: https://github.com/flutter/flutter/issues/72947 for (final _HeroState toHero in toHeroes.values) toHero.endFlight(); }
class_HeroFlight{ // The simple case: we're either starting a push or a pop animation. void start(_HeroFlightManifest initialManifest) { assert(!_aborted); assert(() { final Animation<double> initial = initialManifest.animation; assert(initial != null); final HeroFlightDirection type = initialManifest.type; assert(type != null); switch (type) { case HeroFlightDirection.pop: return initial.value == 1.0 && initialManifest.isUserGestureTransition // During user gesture transitions, the animation controller isn't // driving the reverse transition, but should still be in a previously // completed stage with the initial value at 1.0. ? initial.status == AnimationStatus.completed : initial.status == AnimationStatus.reverse; case HeroFlightDirection.push: return initial.value == 0.0 && initial.status == AnimationStatus.forward; } }());