dual_transition_builder.dart 6.63 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'basic.dart';
import 'framework.dart';

/// Builder callback used by [DualTransitionBuilder].
///
/// The builder is expected to return a transition powered by the provided
/// `animation` and wrapping the provided `child`.
///
/// The `animation` provided to the builder always runs forward from 0.0 to 1.0.
typedef AnimatedTransitionBuilder = Widget Function(
  BuildContext context,
16
  Animation<double> animation,
17
  Widget? child,
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
);

/// A transition builder that animates its [child] based on the
/// [AnimationStatus] of the provided [animation].
///
/// This widget can be used to specify different enter and exit transitions for
/// a [child].
///
/// While the [animation] runs forward, the [child] is animated according to
/// [forwardBuilder] and while the [animation] is running in reverse, it is
/// animated according to [reverseBuilder].
///
/// Using this builder allows the widget tree to maintain its shape by nesting
/// the enter and exit transitions. This ensures that no state information of
/// any descendant widget is lost when the transition starts or completes.
class DualTransitionBuilder extends StatefulWidget {
  /// Creates a [DualTransitionBuilder].
  ///
  /// The [animation], [forwardBuilder], and [reverseBuilder] arguments are
  /// required and must not be null.
  const DualTransitionBuilder({
39 40 41 42
    Key? key,
    required this.animation,
    required this.forwardBuilder,
    required this.reverseBuilder,
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
    this.child,
  }) : assert(animation != null),
       assert(forwardBuilder != null),
       assert(reverseBuilder != null),
       super(key: key);

  /// The animation that drives the [child]'s transition.
  ///
  /// When this animation runs forward, the [child] transitions as specified by
  /// [forwardBuilder]. When it runs in reverse, the child transitions according
  /// to [reverseBuilder].
  final Animation<double> animation;

  /// A builder for the transition that makes [child] appear on screen.
  ///
  /// The [child] should be fully visible when the provided `animation` reaches
  /// 1.0.
  ///
  /// The `animation` provided to this builder is running forward from 0.0 to
  /// 1.0 when [animation] runs _forward_. When [animation] runs in reverse,
  /// the given `animation` is set to [kAlwaysCompleteAnimation].
  ///
  /// See also:
  ///
  ///  * [reverseBuilder], which builds the transition for making the [child]
  ///   disappear from the screen.
  final AnimatedTransitionBuilder forwardBuilder;

  /// A builder for a transition that makes [child] disappear from the screen.
  ///
  /// The [child] should be fully invisible when the provided `animation`
  /// reaches 1.0.
  ///
  /// The `animation` provided to this builder is running forward from 0.0 to
  /// 1.0 when [animation] runs in _reverse_. When [animation] runs forward,
  /// the given `animation` is set to [kAlwaysDismissedAnimation].
  ///
  /// See also:
  ///
  ///  * [forwardBuilder], which builds the transition for making the [child]
  ///    appear on screen.
  final AnimatedTransitionBuilder reverseBuilder;

  /// The widget below this [DualTransitionBuilder] in the tree.
  ///
  /// This child widget will be wrapped by the transitions built by
  /// [forwardBuilder] and [reverseBuilder].
90
  final Widget? child;
91 92 93 94 95 96

  @override
  State<DualTransitionBuilder> createState() => _DualTransitionBuilderState();
}

class _DualTransitionBuilderState extends State<DualTransitionBuilder> {
97
  late AnimationStatus _effectiveAnimationStatus;
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
  final ProxyAnimation _forwardAnimation = ProxyAnimation();
  final ProxyAnimation _reverseAnimation = ProxyAnimation();

  @override
  void initState() {
    super.initState();
    _effectiveAnimationStatus = widget.animation.status;
    widget.animation.addStatusListener(_animationListener);
    _updateAnimations();
  }

  void _animationListener(AnimationStatus animationStatus) {
    final AnimationStatus oldEffective = _effectiveAnimationStatus;
    _effectiveAnimationStatus = _calculateEffectiveAnimationStatus(
      lastEffective: _effectiveAnimationStatus,
      current: animationStatus,
    );
    if (oldEffective != _effectiveAnimationStatus) {
      _updateAnimations();
    }
  }

  @override
  void didUpdateWidget(DualTransitionBuilder oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.animation != widget.animation) {
      oldWidget.animation.removeStatusListener(_animationListener);
      widget.animation.addStatusListener(_animationListener);
      _animationListener(widget.animation.status);
    }
  }

  // When a transition is interrupted midway we just want to play the ongoing
  // animation in reverse. Switching to the actual reverse transition would
  // yield a disjoint experience since the forward and reverse transitions are
  // very different.
  AnimationStatus _calculateEffectiveAnimationStatus({
135 136
    required AnimationStatus lastEffective,
    required AnimationStatus current,
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
  }) {
    assert(current != null);
    assert(lastEffective != null);
    switch (current) {
      case AnimationStatus.dismissed:
      case AnimationStatus.completed:
        return current;
      case AnimationStatus.forward:
        switch (lastEffective) {
          case AnimationStatus.dismissed:
          case AnimationStatus.completed:
          case AnimationStatus.forward:
            return current;
          case AnimationStatus.reverse:
            return lastEffective;
        }
      case AnimationStatus.reverse:
        switch (lastEffective) {
          case AnimationStatus.dismissed:
          case AnimationStatus.completed:
          case AnimationStatus.reverse:
            return current;
          case AnimationStatus.forward:
            return lastEffective;
        }
    }
  }

  void _updateAnimations() {
    switch (_effectiveAnimationStatus) {
      case AnimationStatus.dismissed:
      case AnimationStatus.forward:
        _forwardAnimation.parent = widget.animation;
        _reverseAnimation.parent = kAlwaysDismissedAnimation;
        break;
      case AnimationStatus.reverse:
      case AnimationStatus.completed:
        _forwardAnimation.parent = kAlwaysCompleteAnimation;
        _reverseAnimation.parent = ReverseAnimation(widget.animation);
        break;
    }
  }

  @override
  void dispose() {
    widget.animation.removeStatusListener(_animationListener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.forwardBuilder(
      context,
      _forwardAnimation,
      widget.reverseBuilder(
        context,
        _reverseAnimation,
        widget.child,
      ),
    );
  }
}