how to animate collapse elements in flutter

2020-02-04 06:08发布

问题:

How can i expand and collapse widget when user taps on different widget ( sibling or parent ) with animation ?

new Column(
    children: <Widget>[
        new header.IngridientHeader(
            new Icon(
                Icons.fiber_manual_record,
                color: AppColors.primaryColor
            ),
            'Voice Track 1'
        ),
        new Grid()
    ],
)

I want user to be able to tap on header.IngridientHeader and then Grid widget should toggle ( hide if visible and other way around )

edit:

im trying to do something that in bootstrap is called Collapse. getbootstrap.com/docs/4.0/components/collapse

edit 2: header.IngridientHeader should stay in place all the time Grid() is scrollable ( horizontal ) widget.

回答1:

If you want to collapse a widget to zero height or zero width that has a child that overflow when collapsed, I would recommend SizeTransition or ScaleTransition.

Here is an example of the ScaleTransition widget being used to collapse the container for the four black buttons and status text. My ExpandedSection widget is used with a column to get the following structure.

An example of a Widget that use animation with the SizeTransition widget:

class ExpandedSection extends StatefulWidget {

  final Widget child;
  final bool expand;
  ExpandedSection({this.expand = false, this.child});

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

class _ExpandedSectionState extends State<ExpandedSection> with SingleTickerProviderStateMixin {
  AnimationController expandController;
  Animation<double> animation; 

  @override
  void initState() {
    super.initState();
    prepareAnimations();
    _runExpandCheck();
  }

  ///Setting up the animation
  void prepareAnimations() {
    expandController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 500)
    );
    animation = CurvedAnimation(
      parent: expandController,
      curve: Curves.fastOutSlowIn,
    );
  }

  void _runExpandCheck() {
    if(widget.expand) {
      expandController.forward();
    }
    else {
      expandController.reverse();
    }
  }

  @override
  void didUpdateWidget(ExpandedSection oldWidget) {
    super.didUpdateWidget(oldWidget);
    _runExpandCheck();
  }

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

  @override
  Widget build(BuildContext context) {
    return SizeTransition(
      axisAlignment: 1.0,
      sizeFactor: animation,
      child: widget.child
    );
  }
}

AnimatedContainer also works but Flutter can complain about overflow if the child is not resizable to zero width or zero height.



回答2:

Alternatively you can just use an AnimatedContainer to mimic this behavior.

class AnimateContentExample extends StatefulWidget {
  @override
  _AnimateContentExampleState createState() => new _AnimateContentExampleState();
}

class _AnimateContentExampleState extends State<AnimateContentExample> {
  double _animatedHeight = 100.0;
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text("Animate Content"),),
      body: new Column(
        children: <Widget>[
          new Card(
            child: new Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                new GestureDetector(
                  onTap: ()=>setState((){
                    _animatedHeight!=0.0?_animatedHeight=0.0:_animatedHeight=100.0;}),
                  child:  new Container(
                  child: new Text("CLICK ME"),
                  color: Colors.blueAccent,
                  height: 25.0,
                    width: 100.0,
                ),),
                new AnimatedContainer(duration: const Duration(milliseconds: 120),
                  child: new Text("Toggle Me"),
                  height: _animatedHeight,
                  color: Colors.tealAccent,
                  width: 100.0,
                )
              ],
            ) ,
          )
        ],
      ),
    );
  }
}


回答3:

I think you are looking for ExpansionTile widget. This takes a title property which is equivalent to header and children property to which you can pass widgets to be shown or hidden on toggle. You can find an example of how to use it here.

Simple Example Usage:

new ExpansionTile(title: new Text("Numbers"),
      children: <Widget>[
        new Text("Number: 1"),
        new Text("Number: 2"),
        new Text("Number: 3"),
        new Text("Number: 4"),
        new Text("Number: 5")
      ],
),

Hope that helps!



回答4:

Output:


Code:

class _HomePageState extends State<HomePage> {
  Duration _duration = Duration(seconds: 1);
  int _flex1 = 1, _flex2 = 1, _flex3 = 1;

  @override
  Widget build(BuildContext context) {
    var data = MediaQuery.of(context);
    double height = data.size.height - data.padding.top - kToolbarHeight;
    var height1 = (_flex1 * height) / (_flex1 + _flex2 + _flex3);
    var height2 = (_flex2 * height) / (_flex1 + _flex2 + _flex3);
    var height3 = (_flex3 * height) / (_flex1 + _flex2 + _flex3);

    return Scaffold(
      appBar: AppBar(
        title: Text("Awesome Effect"),
      ),
      body: Column(
        children: <Widget>[
          AnimatedContainer(
            duration: _duration,
            color: Colors.blue,
            height: height1,
            alignment: Alignment.center,
            child: Text("Flex: ${_flex1}", style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold)),
          ),
          AnimatedContainer(
            duration: _duration,
            color: Colors.red,
            height: height2,
            alignment: Alignment.center,
            child: Text("Flex: ${_flex2}", style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold)),
          ),
          AnimatedContainer(
            duration: _duration,
            color: Colors.teal,
            height: height3,
            alignment: Alignment.center,
            child: Text("Flex: ${_flex3}", style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold)),
          ),
        ],
      ),
    );
  }
}