您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

646 行
26 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.widgets {
public delegate Tween<Rect> CreateRectTween(Rect begin, Rect end);
public delegate Widget HeroPlaceholderBuilder(
BuildContext context,
Size heroSize,
Widget child
);
public delegate Widget HeroFlightShuttleBuilder(
BuildContext flightContext,
Animation<float> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext
);
delegate void _OnFlightEnded(_HeroFlight flight);
public enum HeroFlightDirection {
push,
pop
}
class HeroUtils {
public static Rect _boundingBoxFor(BuildContext context, BuildContext ancestorContext = null) {
RenderBox box = context.findRenderObject() as RenderBox;
D.assert(box != null && box.hasSize);
return MatrixUtils.transformRect(
box.getTransformTo(ancestorContext?.findRenderObject()),
Offset.zero & box.size
);
}
}
public class Hero : StatefulWidget {
public Hero(
Key key = null,
object tag = null,
CreateRectTween createRectTween = null,
HeroFlightShuttleBuilder flightShuttleBuilder = null,
HeroPlaceholderBuilder placeholderBuilder = null,
bool transitionOnUserGestures = false,
Widget child = null
) : base(key: key) {
D.assert(tag != null);
D.assert(child != null);
this.tag = tag;
this.createRectTween = createRectTween;
this.child = child;
this.flightShuttleBuilder = flightShuttleBuilder;
this.placeholderBuilder = placeholderBuilder;
this.transitionOnUserGestures = transitionOnUserGestures;
}
public readonly object tag;
public readonly CreateRectTween createRectTween;
public readonly Widget child;
public readonly HeroFlightShuttleBuilder flightShuttleBuilder;
public readonly HeroPlaceholderBuilder placeholderBuilder;
public readonly bool transitionOnUserGestures;
internal static Dictionary<object, _HeroState> _allHeroesFor(
BuildContext context,
bool isUserGestureTransition,
NavigatorState navigator) {
D.assert(context != null);
D.assert(navigator != null);
Dictionary<object, _HeroState> result = new Dictionary<object, _HeroState> { };
void inviteHero(StatefulElement hero, object tag) {
D.assert(()=> {
if (result.ContainsKey(tag)) {
throw new UIWidgetsError(
new List<DiagnosticsNode>()
{
new ErrorSummary("There are multiple heroes that share the same tag within a subtree."),
new ErrorDescription(
"Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), "+
"each Hero must have a unique non-null tag.\n"+
$"In this case, multiple heroes had the following tag: {tag}\n"
),
new DiagnosticsProperty<StatefulElement>("Here is the subtree for one of the offending heroes", hero, linePrefix: "# ", style: DiagnosticsTreeStyle.dense),
});
}
return true;
});
Hero heroWidget = hero.widget as Hero;
_HeroState heroState = hero.state as _HeroState;
if (!isUserGestureTransition || heroWidget.transitionOnUserGestures) {
result[tag] = heroState;
} else {
heroState.ensurePlaceholderIsHidden();
}
}
void visitor(Element element) {
Widget widget = element.widget;
if (widget is Hero) {
StatefulElement hero = element as StatefulElement;
object tag = ((Hero)widget).tag;
D.assert(tag != null);
if (Navigator.of(hero) == navigator) {
inviteHero(hero, tag);
} else {
ModalRoute heroRoute = ModalRoute.of(hero);
if (heroRoute != null && heroRoute is PageRoute && heroRoute.isCurrent) {
inviteHero(hero, tag);
}
}
}
element.visitChildren(visitor);
}
context.visitChildElements(visitor);
return result;
}
public override State createState() {
return new _HeroState();
}
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<object>("tag", tag));
}
}
class _HeroState : State<Hero> {
GlobalKey _key = GlobalKey.key();
Size _placeholderSize;
bool _shouldIncludeChild = true;
public void startFlight( bool shouldIncludedChildInPlaceholder = false ) {
_shouldIncludeChild = shouldIncludedChildInPlaceholder;
D.assert(mounted);
RenderBox box = (RenderBox) context.findRenderObject();
D.assert(box != null && box.hasSize);
setState(() => { _placeholderSize = box.size; });
}
public void ensurePlaceholderIsHidden() {
if (mounted) {
setState(() => { _placeholderSize = null; });
}
}
public void endFlight(bool keepPlaceholder = false ) {
if (!keepPlaceholder) {
ensurePlaceholderIsHidden();
}
}
public override Widget build(BuildContext context) {
D.assert(
context.findAncestorWidgetOfExactType<Hero>() == null,
() => "A Hero widget cannot be the descendant of another Hero widget.");
bool showPlaceholder = _placeholderSize != null;
if (showPlaceholder && widget.placeholderBuilder != null) {
return widget.placeholderBuilder(context, _placeholderSize, widget.child);
}
if (showPlaceholder && !_shouldIncludeChild) {
return new SizedBox(
width: _placeholderSize.width,
height: _placeholderSize.height
);
}
return new SizedBox(
width: _placeholderSize?.width,
height: _placeholderSize?.height,
child: new Offstage(
offstage: showPlaceholder,
child: new TickerMode(
enabled: !showPlaceholder,
child: new KeyedSubtree(key: _key, child: widget.child)
)
)
);
}
}
class _HeroFlightManifest {
public _HeroFlightManifest(
HeroFlightDirection type = default,
OverlayState overlay = null ,
Rect navigatorRect = null,
PageRoute fromRoute = null,
PageRoute toRoute = null,
_HeroState fromHero = null,
_HeroState toHero = null,
CreateRectTween createRectTween = null,
HeroFlightShuttleBuilder shuttleBuilder = null,
bool? isUserGestureTransition = null,
bool? isDiverted = null
) {
D.assert(fromHero.widget.tag.Equals(toHero.widget.tag));
this.type = type;
this.overlay = overlay;
this.navigatorRect = navigatorRect;
this.fromRoute = fromRoute;
this.toRoute = toRoute;
this.fromHero = fromHero;
this.toHero = toHero;
this.createRectTween = createRectTween;
this.shuttleBuilder = shuttleBuilder;
this.isUserGestureTransition = isUserGestureTransition ?? false;
this.isDiverted = isDiverted ?? false;
}
public readonly HeroFlightDirection type;
public readonly OverlayState overlay;
public readonly Rect navigatorRect;
public readonly PageRoute fromRoute;
public readonly PageRoute toRoute;
public readonly _HeroState fromHero;
public readonly _HeroState toHero;
public readonly CreateRectTween createRectTween;
public readonly HeroFlightShuttleBuilder shuttleBuilder;
public readonly bool isUserGestureTransition;
public readonly bool isDiverted;
public object tag {
get { return fromHero.widget.tag; }
}
public Animation<float> animation {
get {
return new CurvedAnimation(
parent: (type == HeroFlightDirection.push) ? toRoute.animation : fromRoute.animation,
curve: Curves.fastOutSlowIn,
reverseCurve: isDiverted ? null : Curves.fastOutSlowIn.flipped
);
}
}
public override string ToString() {
return $"_HeroFlightManifest({type} tag: {tag} from route: {fromRoute.settings} " +
$"to route: {toRoute.settings} with hero: {fromHero} to {toHero})";
}
}
class _HeroFlight {
public _HeroFlight(_OnFlightEnded onFlightEnded ) {
this.onFlightEnded = onFlightEnded;
_proxyAnimation = new ProxyAnimation();
_proxyAnimation.addStatusListener(_handleAnimationUpdate);
}
public readonly _OnFlightEnded onFlightEnded;
Tween<Rect> heroRectTween;
Widget shuttle;
Animation<float> _heroOpacity = Animations.kAlwaysCompleteAnimation;
public ProxyAnimation _proxyAnimation;
public _HeroFlightManifest manifest;
public OverlayEntry overlayEntry;
bool _aborted = false;
Tween<Rect> _doCreateRectTween(Rect begin, Rect end) {
CreateRectTween createRectTween =
manifest.toHero.widget.createRectTween ?? manifest.createRectTween;
if (createRectTween != null) {
return createRectTween(begin, end);
}
return new RectTween(begin: begin, end: end);
}
static readonly Animatable<float> _reverseTween = new FloatTween(begin: 1.0f, end: 0.0f);
Widget _buildOverlay(BuildContext context) {
D.assert(manifest != null);
shuttle = shuttle ?? manifest.shuttleBuilder(
context,
manifest.animation,
manifest.type,
manifest.fromHero.context,
manifest.toHero.context);
D.assert(shuttle != null);
return new AnimatedBuilder(
animation: _proxyAnimation,
child: shuttle,
builder: (BuildContext _, Widget child) => {
RenderBox toHeroBox = manifest.toHero.context?.findRenderObject() as RenderBox;
if (_aborted || toHeroBox == null || !toHeroBox.attached) {
if (_heroOpacity.isCompleted) {
_heroOpacity = _proxyAnimation.drive(
_reverseTween.chain(
new CurveTween(curve: new Interval(_proxyAnimation.value, 1.0f)))
);
}
}
else if (toHeroBox.hasSize) {
RenderBox finalRouteBox = manifest.toRoute.subtreeContext?.findRenderObject() as RenderBox;
Offset toHeroOrigin = toHeroBox.localToGlobal(Offset.zero, ancestor: finalRouteBox);
if (toHeroOrigin != heroRectTween.end.topLeft) {
Rect heroRectEnd = toHeroOrigin & heroRectTween.end.size;
heroRectTween = _doCreateRectTween(heroRectTween.begin, heroRectEnd);
}
}
Rect rect = heroRectTween.evaluate(_proxyAnimation);
Size size = manifest.navigatorRect.size;
RelativeRect offsets = RelativeRect.fromSize(rect, size);
return new Positioned(
top: offsets.top,
right: offsets.right,
bottom: offsets.bottom,
left: offsets.left,
child: new IgnorePointer(
child: new RepaintBoundary(
child: new Opacity(
opacity: _heroOpacity.value,
child: child
)
)
)
);
}
);
}
public void _handleAnimationUpdate(AnimationStatus status) {
if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) {
_proxyAnimation.parent = null;
D.assert(overlayEntry != null);
overlayEntry.remove();
overlayEntry = null;
manifest.fromHero.endFlight(keepPlaceholder: status == AnimationStatus.completed);
manifest.toHero.endFlight(keepPlaceholder: status == AnimationStatus.dismissed);
onFlightEnded(this);
}
}
public void start(_HeroFlightManifest initialManifest) {
D.assert(!_aborted);
D.assert(() => {
Animation<float> initial = initialManifest.animation;
D.assert(initial != null);
HeroFlightDirection type = initialManifest.type;
switch (type) {
case HeroFlightDirection.pop:
return initial.value == 1.0f &&
initialManifest.isUserGestureTransition
? initial.status == AnimationStatus.completed
: initial.status == AnimationStatus.reverse;
case HeroFlightDirection.push:
return initial.value == 0.0f && initial.status == AnimationStatus.forward;
}
throw new Exception("Unknown type: " + type);
});
manifest = initialManifest;
if (manifest.type == HeroFlightDirection.pop) {
_proxyAnimation.parent = new ReverseAnimation(manifest.animation);
}
else {
_proxyAnimation.parent = manifest.animation;
}
manifest.fromHero.startFlight(shouldIncludedChildInPlaceholder: manifest.type == HeroFlightDirection.push);
manifest.toHero.startFlight();
heroRectTween = _doCreateRectTween(
HeroUtils._boundingBoxFor(manifest.fromHero.context, manifest.fromRoute.subtreeContext),
HeroUtils._boundingBoxFor(manifest.toHero.context, manifest.toRoute.subtreeContext)
);
overlayEntry = new OverlayEntry(builder: _buildOverlay);
manifest.overlay.insert(overlayEntry);
}
public void divert(_HeroFlightManifest newManifest) {
D.assert(manifest.tag == newManifest.tag);
if (manifest.type == HeroFlightDirection.push && newManifest.type == HeroFlightDirection.pop) {
D.assert(newManifest.animation.status == AnimationStatus.reverse);
D.assert(manifest.fromHero == newManifest.toHero);
D.assert(manifest.toHero == newManifest.fromHero);
D.assert(manifest.fromRoute == newManifest.toRoute);
D.assert(manifest.toRoute == newManifest.fromRoute);
_proxyAnimation.parent = new ReverseAnimation(newManifest.animation);
heroRectTween = new ReverseTween<Rect>(heroRectTween);
}
else if (manifest.type == HeroFlightDirection.pop && newManifest.type == HeroFlightDirection.push) {
D.assert(newManifest.animation.status == AnimationStatus.forward);
D.assert(manifest.toHero == newManifest.fromHero);
D.assert(manifest.toRoute == newManifest.fromRoute);
_proxyAnimation.parent = newManifest.animation.drive(
new FloatTween(
begin: manifest.animation.value,
end: 1.0f
)
);
if (manifest.fromHero != newManifest.toHero) {
manifest.fromHero.endFlight(keepPlaceholder: true);
newManifest.toHero.startFlight();
heroRectTween = _doCreateRectTween(
heroRectTween.end,
HeroUtils._boundingBoxFor(newManifest.toHero.context, newManifest.toRoute.subtreeContext)
);
}
else {
heroRectTween = _doCreateRectTween(heroRectTween.end, heroRectTween.begin);
}
}
else {
D.assert(manifest.fromHero != newManifest.fromHero);
D.assert(manifest.toHero != newManifest.toHero);
heroRectTween = _doCreateRectTween(
heroRectTween.evaluate(_proxyAnimation),
HeroUtils._boundingBoxFor(newManifest.toHero.context, newManifest.toRoute.subtreeContext)
);
shuttle = null;
if (newManifest.type == HeroFlightDirection.pop) {
_proxyAnimation.parent = new ReverseAnimation(newManifest.animation);
}
else {
_proxyAnimation.parent = newManifest.animation;
}
manifest.fromHero.endFlight(keepPlaceholder: true);
manifest.toHero.endFlight(keepPlaceholder: true);
// Let the heroes in each of the routes rebuild with their placeholders.
newManifest.fromHero.startFlight(shouldIncludedChildInPlaceholder: newManifest.type == HeroFlightDirection.push);
newManifest.toHero.startFlight();
overlayEntry.markNeedsBuild();
}
_aborted = false;
manifest = newManifest;
}
public void abort() {
_aborted = true;
}
public override string ToString() {
RouteSettings from = manifest.fromRoute.settings;
RouteSettings to = manifest.toRoute.settings;
object tag = manifest.tag;
return $"HeroFlight(for: {tag}, from: {from}, to: {to} {_proxyAnimation.parent})";
}
}
public class HeroController : NavigatorObserver {
public HeroController(CreateRectTween createRectTween = null) {
this.createRectTween = createRectTween;
}
public readonly CreateRectTween createRectTween;
Dictionary<object, _HeroFlight> _flights = new Dictionary<object, _HeroFlight>();
public override void didPush(Route route, Route previousRoute) {
D.assert(navigator != null);
D.assert(route != null);
_maybeStartHeroTransition(previousRoute, route, HeroFlightDirection.push, false);
}
public override void didPop(Route route, Route previousRoute) {
D.assert(navigator != null);
D.assert(route != null);
if (!navigator.userGestureInProgress) {
_maybeStartHeroTransition(route, previousRoute, HeroFlightDirection.pop, false);
}
}
public override void didReplace(Route newRoute = null, Route oldRoute = null) {
D.assert(navigator != null);
if (newRoute?.isCurrent == true) {
_maybeStartHeroTransition(oldRoute, newRoute, HeroFlightDirection.push, false);
}
}
public override void didStartUserGesture(Route route, Route previousRoute) {
D.assert(navigator != null);
D.assert(route != null);
_maybeStartHeroTransition(route, previousRoute, HeroFlightDirection.pop, true);
}
public override void didStopUserGesture() {
if (navigator.userGestureInProgress)
return;
bool isInvalidFlight(_HeroFlight flight) {
return flight.manifest.isUserGestureTransition
&& flight.manifest.type == HeroFlightDirection.pop
&& flight._proxyAnimation.isDismissed;
}
List<_HeroFlight> invalidFlights = _flights.Values
.Where(isInvalidFlight)
.ToList();
foreach ( _HeroFlight flight in invalidFlights) {
flight._handleAnimationUpdate(AnimationStatus.dismissed);
}
}
void _maybeStartHeroTransition(
Route fromRoute,
Route toRoute,
HeroFlightDirection flightType,
bool isUserGestureTransition
) {
if (toRoute != fromRoute && toRoute is PageRoute && fromRoute is PageRoute) {
PageRoute from = (PageRoute) fromRoute;
PageRoute to = (PageRoute) toRoute;
Animation<float> animation = (flightType == HeroFlightDirection.push) ? to.animation : from.animation;
switch (flightType) {
case HeroFlightDirection.pop:
if (animation.value == 0.0f) {
return;
}
break;
case HeroFlightDirection.push:
if (animation.value == 1.0f) {
return;
}
break;
}
if (isUserGestureTransition && flightType == HeroFlightDirection.pop && to.maintainState) {
_startHeroTransition(from, to, animation, flightType, isUserGestureTransition);
}
else {
to.offstage = to.animation.value == 0.0f;
WidgetsBinding.instance.addPostFrameCallback((TimeSpan value) => {
_startHeroTransition(from, to, animation, flightType, isUserGestureTransition);
});
}
}
}
void _startHeroTransition(
PageRoute from,
PageRoute to,
Animation<float> animation,
HeroFlightDirection flightType,
bool isUserGestureTransition
) {
if (navigator == null || from.subtreeContext == null || to.subtreeContext == null) {
to.offstage = false; // in case we set this in _maybeStartHeroTransition
return;
}
Rect navigatorRect = HeroUtils._boundingBoxFor(navigator.context);//_globalBoundingBoxFor(navigator.context);
Dictionary<object, _HeroState> fromHeroes =
Hero._allHeroesFor(from.subtreeContext, isUserGestureTransition, navigator);
Dictionary<object, _HeroState> toHeroes =
Hero._allHeroesFor(to.subtreeContext, isUserGestureTransition, navigator);
to.offstage = false;
foreach (object tag in fromHeroes.Keys) {
if (toHeroes.ContainsKey(tag)) {
HeroFlightShuttleBuilder fromShuttleBuilder = fromHeroes[tag].widget.flightShuttleBuilder;
HeroFlightShuttleBuilder toShuttleBuilder = toHeroes[tag].widget.flightShuttleBuilder;
bool isDiverted = _flights.ContainsKey(tag);
_HeroFlightManifest manifest = new _HeroFlightManifest(
type: flightType,
overlay: navigator.overlay,
navigatorRect: navigatorRect,
fromRoute: from,
toRoute: to,
fromHero: fromHeroes[tag],
toHero: toHeroes[tag],
createRectTween: createRectTween,
shuttleBuilder:
toShuttleBuilder ?? fromShuttleBuilder ?? _defaultHeroFlightShuttleBuilder,
isUserGestureTransition: isUserGestureTransition,
isDiverted: isDiverted
);
//if (_flights.TryGetValue(tag, out var result)) {
if (isDiverted){
_flights[tag].divert(manifest);
}
else {
_flights[tag] = new _HeroFlight(_handleFlightEnded);
_flights[tag].start(manifest);
}
}
else if (_flights.TryGetValue(tag, out var result)) {
result.abort();
}
}
foreach (object tag in toHeroes.Keys) {
_HeroState heroState = new _HeroState();
if (!fromHeroes.TryGetValue(tag, out heroState)) {
toHeroes[tag].ensurePlaceholderIsHidden();
}
}
}
void _handleFlightEnded(_HeroFlight flight) {
_flights.Remove(flight.manifest.tag);
}
static readonly HeroFlightShuttleBuilder _defaultHeroFlightShuttleBuilder = (
BuildContext flightContext,
Animation<float> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext
) => {
Hero toHero = (Hero) toHeroContext.widget;
return toHero.child;
};
}
}