animated_size.dart 4.58 KB
Newer Older
1 2 3 4 5
// Copyright 2016 The Chromium 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 'package:flutter/animation.dart';
6
import 'package:flutter/scheduler.dart';
7 8 9 10 11 12 13
import 'package:meta/meta.dart';

import 'box.dart';
import 'object.dart';
import 'shifted_box.dart';

/// A render object that animates its size to its child's size over a given
14 15 16 17 18
/// [duration] and with a given [curve]. If the child's size itself animates
/// (i.e. if it changes size two frames in a row, as opposed to abruptly
/// changing size in one frame then remaining that size in subsequent frames),
/// this render object sizes itself to fit the child instead of animating
/// itself.
19
///
20 21
/// When the child overflows the current animated size of this render object, it
/// is clipped.
22 23
class RenderAnimatedSize extends RenderAligningShiftedBox {
  /// Creates a render object that animates its size to match its child.
24 25 26
  /// The [duration] and [curve] arguments define the animation.
  ///
  /// The [alignment] argument is used to align the child when the parent is not
27 28
  /// (yet) the same size as the child.
  ///
29 30 31 32 33 34 35
  /// The [duration] is required.
  ///
  /// The [vsync] should specify a [TickerProvider] for the animation
  /// controller.
  ///
  /// The arguments [duration], [curve], [alignment], and [vsync] must
  /// not be null.
36
  RenderAnimatedSize({
37 38
    @required TickerProvider vsync,
    @required Duration duration,
39 40
    Curve curve: Curves.linear,
    FractionalOffset alignment: FractionalOffset.center,
41 42 43
    RenderBox child,
  }) : _vsync = vsync, super(child: child, alignment: alignment) {
    assert(vsync != null);
44 45 46
    assert(duration != null);
    assert(curve != null);
    _controller = new AnimationController(
47 48
      vsync: vsync,
      duration: duration,
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
    )..addListener(() {
      if (_controller.value != _lastValue)
        markNeedsLayout();
    });
    _animation = new CurvedAnimation(
      parent: _controller,
      curve: curve
    );
  }

  AnimationController _controller;
  CurvedAnimation _animation;
  SizeTween _sizeTween = new SizeTween();
  bool _didChangeTargetSizeLastFrame = false;
  bool _hasVisualOverflow;
  double _lastValue;

  /// The duration of the animation.
  Duration get duration => _controller.duration;
  set duration(Duration value) {
    assert(value != null);
    if (value == _controller.duration)
      return;
    _controller.duration = value;
  }

  /// The curve of the animation.
  Curve get curve => _animation.curve;
  set curve(Curve value) {
    assert(value != null);
    if (value == _animation.curve)
      return;
    _animation.curve = value;
  }

84 85 86 87 88 89 90 91 92 93 94
  /// The [TickerProvider] for the [AnimationController] that runs the animation.
  TickerProvider get vsync => _vsync;
  TickerProvider _vsync;
  set vsync(TickerProvider value) {
    assert(value != null);
    if (value == _vsync)
      return;
    _vsync = value;
    _controller.resync(vsync);
  }

95 96 97 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
  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    if (_animatedSize != _sizeTween.end && !_controller.isAnimating)
      _controller.forward();
  }

  @override
  void detach() {
    _controller.stop();
    super.detach();
  }

  Size get _animatedSize {
    return _sizeTween.evaluate(_animation);
  }

  @override
  void performLayout() {
    _lastValue = _controller.value;
    _hasVisualOverflow = false;

    if (child == null) {
      size = _sizeTween.begin = _sizeTween.end = constraints.smallest;
      return;
    }

    child.layout(constraints, parentUsesSize: true);
    if (_sizeTween.end != child.size) {
      _sizeTween.begin = _animatedSize ?? child.size;
      _sizeTween.end = child.size;

      if (_didChangeTargetSizeLastFrame) {
        size = child.size;
        _controller.stop();
      } else {
131
        // Don't register first change as a last-frame change.
132 133 134 135 136 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
        if (_sizeTween.end != _sizeTween.begin)
          _didChangeTargetSizeLastFrame = true;

        _lastValue = 0.0;
        _controller.forward(from: 0.0);

        size = constraints.constrain(_animatedSize);
      }
    } else {
      _didChangeTargetSizeLastFrame = false;

      size = constraints.constrain(_animatedSize);
    }

    alignChild();

    if (size.width < _sizeTween.end.width ||
        size.height < _sizeTween.end.height)
      _hasVisualOverflow = true;
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null && _hasVisualOverflow) {
      final Rect rect = Point.origin & size;
      context.pushClipRect(needsCompositing, offset, rect, super.paint);
    } else {
      super.paint(context, offset);
    }
  }
}