简体   繁体   中英

Flutter AnimatedSize works in one direction only

I have got following code from https://medium.com/flutter-community/flutter-working-with-animatedsize-35253ff8f16a

it is using AnimatedSize, but animation works only while container is expanding and not while is it shirking. is this the default behavior? I want to animated while both expanding and shriking.

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  double _height = 80.0;
  double _width = 80.0;
  var _color = Colors.blue;
  bool _resized = false;
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            AnimatedSize(
              curve: Curves.easeIn,
              vsync: this,
              duration: new Duration(seconds: 1),
              child: new GestureDetector(
                onTap: () {
                  setState(() {
                    if (_resized) {
                      _resized = false;
                      _color = Colors.blue;
                      _height = 80.0;
                      _width = 80.0;
                    } else {
                      _resized = true;
                      _color = Colors.blue;
                      _height = 320.0;
                      _width = 320.0;
                    }
                  });
                },
                child: new Container(
                  width: _width,
                  height: _height,
                  color: _color,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

在此处输入图像描述

AnimatedContainer isn't really an option when using text, lists or other variably sized widgets because it doesn't allow a width or height of null. Also it won't let you specify an alignment.

Instead I fixed this using a ClipRect with an animation controller. In the animation below you can see the word 'laboris' on the left of the center line in the closing bounce. This shows the text is getting centered during the closing of the container, just keep your eyes on 'laboris'.

动画示例

I created an AnimatedClipRect widget class that you can easily implement yourself. You can specify alignment, curves, durations and if you want horizontal and/or vertical animation. As it is now it assumes you just want to fully close or open it:

class AnimatedClipRect extends StatefulWidget {
  @override
  _AnimatedClipRectState createState() => _AnimatedClipRectState();

  final Widget child;
  final bool open;
  final bool horizontalAnimation;
  final bool verticalAnimation;
  final Alignment alignment;
  final Duration duration;
  final Duration? reverseDuration;
  final Curve curve;
  final Curve? reverseCurve;

  ///The behavior of the controller when [AccessibilityFeatures.disableAnimations] is true.
  final AnimationBehavior animationBehavior;

  const AnimatedClipRect({
    Key? key,
    required this.child,
    required this.open,
    this.horizontalAnimation = true,
    this.verticalAnimation = true,
    this.alignment = Alignment.center,
    this.duration = const Duration(milliseconds: 500),
    this.reverseDuration,
    this.curve = Curves.linear,
    this.reverseCurve,
    this.animationBehavior = AnimationBehavior.normal,
  }) : super(key: key);
}

class _AnimatedClipRectState extends State<AnimatedClipRect> with TickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation _animation;

  @override
  void dispose() {
     _animationController.dispose();
     super.dispose();
  }

  @override
  void initState() {
    _animationController = AnimationController(
        duration: widget.duration,
        reverseDuration: widget.reverseDuration ?? widget.duration,
        vsync: this,
        value: widget.open ? 1.0 : 0.0,
        animationBehavior: widget.animationBehavior);
    _animation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
      parent: _animationController,
      curve: widget.curve,
      reverseCurve: widget.reverseCurve ?? widget.curve,
    ));
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    widget.open ? _animationController.forward() : _animationController.reverse();

    return ClipRect(
      child: AnimatedBuilder(
        animation: _animationController,
        builder: (_, child) {
          return Align(
            alignment: widget.alignment,
            heightFactor: widget.verticalAnimation ? _animation.value : 1.0,
            widthFactor: widget.horizontalAnimation ? _animation.value : 1.0,
            child: child,
          );
        },
        child: widget.child,
      ),
    );
  }
}

Basic usage example, put whatever you want to animate in its child:

// declare bool _open somewhere

Column(
  children: <Widget>[
    AnimatedClipRect(
      open: _open,
      horizontalAnimation: false,
      verticalAnimation: true,
      alignment: Alignment.center,
      duration: const Duration(milliseconds: 1000),
      curve: Curves.bounceOut,
      reverseCurve: Curves.bounceIn,
      child: Container(
        color: Colors.lightGreenAccent,
        padding: const EdgeInsets.all(8),
        child: const Text(
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'),
      ),
    ),
    ElevatedButton(
        child: const Text("open/close"),
        onPressed: () {
          setState(() => _open ^= true);
        }),
  ],
)

As you can see all you have to do is change _open and do setState((){}) to trigger the animation.

AnimatedContainer is what you are looking for no need for AnimatedSize .

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  double _height = 80.0;
  double _width = 80.0;
  var _color = Colors.blue;
  bool _resized = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            GestureDetector(
              onTap: () {
                setState(() {
                  if (_resized) {
                    _resized = false;
                    _color = Colors.blue;
                    _height = 80.0;
                    _width = 80.0;
                  } else {
                    _resized = true;
                    _color = Colors.blue;
                    _height = 320.0;
                    _width = 320.0;
                  }
                });
              },
              child: AnimatedContainer(
                duration: Duration(seconds: 1),
                width: _width,
                height: _height,
                color: _color,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Or if you need AnimatedSize you can add a new container with the color as the AnimatedSize parent.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  double _height = 320.0;
  double _width = 320.0;
  var _color = Colors.blue;
  bool _resized = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onTap: () {
            setState(() {
              if (_resized) {
                _resized = false;
                _color = Colors.blue;
                _height = 80.0;
                _width = 80.0;
              } else {
                _resized = true;
                _color = Colors.blue;
                _height = 320.0;
                _width = 320.0;
              }
            });
          },
          child: Container(
            color: _color,
            child: AnimatedSize(
              curve: Curves.easeIn,
              vsync: this,
              duration: Duration(seconds: 1),
              child: Container(
                width: _width,
                height: _height,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

在此处输入图像描述

Widget SizeTransition worked for me. It works when the child height or width is null too.

Here is my sample code:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget>
    with TickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    duration: const Duration(seconds: 1),
    vsync: this,
  );
  late final Animation<double> _animation = CurvedAnimation(
    parent: _controller,
    curve: Curves.fastOutSlowIn,
  );

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          SizeTransition(
            sizeFactor: _animation,
            axis: Axis.vertical,
            axisAlignment: 1,
            child: Container(
              color: Colors.amberAccent,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: const [Text("Hello"), Text("World")],
              ),
            ),
          ),
          TextButton(
            child: const Text('toggle'),
            onPressed: () => _controller.isDismissed
                ? _controller.forward()
                : _controller.reverse(),
          ),
        ],
      ),
    );
  }
}

Animated Size was giving me the same problem as for you, but then I looked into AnimatedCrossFade's source code, and it used Animated Size and it animated size both while shrinking and expanding. Use LayoutBuilder as child of AnimatedSize and put the actual child inside LayoutBuilder. I'm not sure why it works, but that was how it was for AnimatedCrossFade.

I have noticed that AnimatedSize fails in "one direction" when transitioning to a layout that contains RichText . I was able to work around this simply by wrapping the text in a sized box with a fixed height (providing a height for the layout). If you are experiencing AnimatedSize seemingly not working in random situations try adding fixed size constraints to your layout and see if it helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM