如何在颤动中为折叠元素设置动画

Posted

技术标签:

【中文标题】如何在颤动中为折叠元素设置动画【英文标题】:how to animate collapse elements in flutter 【发布时间】:2018-08-08 08:23:17 【问题描述】:

当用户点击带有动画的不同小部件(兄弟或父)时,我如何展开和折叠小部件?

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

我希望用户能够点击 header.IngridientHeader 然后 Grid 小部件应该切换(如果可见则隐藏或其他方式)

编辑:

我正在尝试做一些在引导程序中称为 Collapse 的事情。 getbootstrap.com/docs/4.0/components/collapse

编辑 2: header.IngridientHeader 应始终保持原位 Grid() 是可滚动的(水平)小部件。

【问题讨论】:

不清楚。你能提供一个工作代码并解释到底需要什么吗? @Darky 我正在尝试做一些在引导程序中称为 Collapse 的事情。 getbootstrap.com/docs/4.0/components/collapse 【参考方案1】:

如果您想将一个小部件折叠到零高度或零宽度,并且折叠时有一个子项溢出,我建议您使用SizeTransition 或ScaleTransition。

这是一个 ScaleTransition 小部件的示例,该小部件用于折叠四个黑色按钮和状态文本的容器。我的 ExpandedSection 小部件与列一起使用以获得以下结构。

通过 SizeTransition 小部件使用动画的小部件示例:

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 也可以,但如果子级不能调整到零宽度或零高度,Flutter 会抱怨溢出。

【讨论】:

您好,请您提供您用于创建动画的代码。在我的情况下,大小转换正在工作,但是当大小为 0 时它会占用空间。 我使用上面创建的小部件制作了一个类似于 gif 动画的working app。很遗憾,我无法分享上述应用程序的具体代码,因为我不拥有该项目。 非常感谢。对于要在开始时扩展的小部件,我必须在 initState() 中使用 forwardreverse 逻辑以及 didWidgetUpdate() 您不需要addListener()setState,因为SizeTransition 会自动执行此操作。 我的项目需要这个,所以修改了这段代码来制作一个 ExpandableSection 小部件。您可以自定义动画持续时间和曲线。警告:它需要使用 flutter_hooks。 gist.github.com/venkatd/b820b3b34b09239d85bf51ccb7f2278a【参考方案2】:

或者,您也可以使用 AnimatedContainer 来模仿这种行为。

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,
                )
              ],
            ) ,
          )
        ],
      ),
    );
  

【讨论】:

你在这里使用 StatefulWidget 有什么原因吗? 是的,setState 不会工作,除非它是 StatefulWidget AnimatedContainer 不适用于儿童身高不固定的情况。另一方面,SizeTransition 在这种情况下就像一个魅力。 这种方法的问题是你必须为关闭(0)和打开提供一个高度。它不会像普通容器那样适应内容。 Flutter 2.* 版本:gist.github.com/AdamJonsson/…【参考方案3】:

我认为您正在寻找ExpansionTile 小部件。这需要一个title 属性,它相当于标题和children 属性,您可以将小部件传递给它以在切换时显示或隐藏。 你可以找到一个如何使用它的例子here。

简单示例用法:

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")
      ],
),

希望有帮助!

【讨论】:

如果不是因为我的 header.IngridientHeader (代码中的 new Text("Numbers") )必须保持原位并且 Grid (代码中的子项)是可滚动的小部件,那么您的答案将是完美的.很抱歉没有早点指出这一点 我认为这也是如此。只需为孩子添加一个可滚动的孩子。那是你想要的吗。或者你能否指出一些关于你想要达到的目标的视觉参考。【参考方案4】:

输出:


代码:

class FooPageState extends State<SoPage> 
  static const _duration = Duration(seconds: 1);
  int _flex1 = 1, _flex2 = 2, _flex3 = 1;

  @override
  Widget build(BuildContext context) 
    final total = _flex1 + _flex2 + _flex3;
    final height = MediaQuery.of(context).size.height;
    final height1 = (height * _flex1) / total;
    final height2 = (height * _flex2) / total;
    final height3 = (height * _flex3) / total;

    return Scaffold(
      body: Column(
        children: [
          AnimatedContainer(
            height: height1,
            duration: _duration,
            color: Colors.red,
          ),
          AnimatedContainer(
            height: height2,
            duration: _duration,
            color: Colors.green,
          ),
          AnimatedContainer(
            height: height3,
            duration: _duration,
            color: Colors.blue,
          ),
        ],
      ),
    );
  

【讨论】:

【参考方案5】:

感谢@Adam Jonsson,他的回答解决了我的问题。这是关于如何使用ExpandedSection的演示,希望对您有所帮助。

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);
  

  
class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Demo'),
        ),
        body: Home(),
      ),
    );
  


class Home extends StatefulWidget 
  @override
  _HomeState createState() => _HomeState();


class _HomeState extends State<Home> 
  bool _expand = false;

  @override
  Widget build(BuildContext context) 
    return Column(
      children: [
        Header(
          onTap: () 
            setState(() 
              _expand = !_expand;
            );
          ,
        ),
        ExpandedSection(child: Content(), expand: _expand,)
      ],
    );
  


class Header extends StatelessWidget 
  final VoidCallback onTap;

  Header(@required this.onTap);

  @override
  Widget build(BuildContext context) 
    return GestureDetector(
      onTap: onTap,
      child: Container(
        color: Colors.cyan,
        height: 100,
        width: double.infinity,
        child: Center(
          child: Text(
            'Header -- Tap me to expand!',
            style: TextStyle(color: Colors.white, fontSize: 20),
          ),
        ),
      ),
    );
  


class Content extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Container(
      color: Colors.lightGreen,
      height: 400,
    );
  

【讨论】:

以上是关于如何在颤动中为折叠元素设置动画的主要内容,如果未能解决你的问题,请参考以下文章

如何在 iPhone 中为图层设置动画?

如何在颤动中为整个应用程序设置文本颜色主题

如何在 Angular 中为 *ngFor 设置动画?

如何在 Angular 中为 *ngFor 设置动画?

如何在颤动的屏幕开始处添加动画显示

如何在颤动中为标签栏下划线添加渐变?