【问题标题】:Slide child widget in Row layout container from its current position从当前位置滑动行布局容器中的子小部件
【发布时间】:2019-08-17 21:49:05
【问题描述】:

我有一个包含 x 个按钮小部件的行。因此,当用户单击其中一个按钮时,我需要淡出其他按钮并将单击的按钮滑动到行布局的起始位置(0,0)。我设法让淡出动画工作。

final _opacityTween = Tween<double>(begin: 1, end: 0);
_opacityAnimation = _opacityTween.animate(CurvedAnimation(
        parent: _controller,
        curve: new Interval(0.00, 0.50, curve: Curves.linear)
        // curve: Curves.ease
        ));

// And using the opacityAnimation value in Opacity widget.

return Opacity(
        opacity: _opacityAnimation.value,
        child: child,
      );

但是我没有成功使用动画类和小部件将子小部件滑动到行的开始位置。基本上我不确定什么样的类可以用来实现这样的动画。我可以使用 Tween 并将结束位置设置为 (0, 0) 并使用 Translation 来实现它,但是我不确定在初始化动画时如何设置动画的开始位置,因为孩子的位置未知.

【问题讨论】:

    标签: flutter flutter-animation


    【解决方案1】:

    试试这个解决方案。我觉得这几乎是你所需要的。您只需添加 SizeTransition 小部件。 询问您是否有任何问题。

    class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
    
        int _pressedButton;
    
        Animation<double> _animation;
        AnimationController _controller;
    
        @override
        void initState() {
            super.initState();
            _controller = AnimationController(
                duration: const Duration(milliseconds: 500),
                vsync: this,
            );
            _animation = Tween<double>(begin: 1.0, end: 0.0)
                .animate(_controller)
                ..addListener(() {
                    setState(() { });
                });
        }
    
        @override
        Widget build(BuildContext context) {
            return Scaffold(
                appBar: AppBar(
                    title: Text("Test"),
                ),
                body: Column(
                    children: <Widget>[
                        Row(
                            children: <Widget>[
                                _animatedButton(1),
                                _animatedButton(2),
                                _animatedButton(3),
                                _animatedButton(4)
                            ]
                        ),
                        Text(_pressedButton != null ? "$_pressedButton button pressed" : "No button pressed"),
                        RaisedButton(
                            child: Text("Reset"),
                            onPressed: () {
                                _controller.reset();
                            }
                        )
                    ]
                )
            );
        }
    
        Widget _animatedButton(int button) {
            if (button != _pressedButton) {
                return SizeTransition(
                    sizeFactor: _animation,
                    axis: Axis.horizontal,
                    child: Opacity(
                        opacity: _animation.value,
                        child: _button(button)
                    )
                );
            } else {
                return _button(button);
            }
        }
    
        Widget _button(int button) {
            return RaisedButton(
                onPressed: () => onButtonClick(button),
                child: Text("Button $button")
            );
        }
    
        void onButtonClick(int button) {
            setState(() {
                _pressedButton = button;
            });
            _controller.forward();
        }
    
    }
    

    更新 1

    检查另一种方法来做你需要的。下面的代码中几乎没有不同的东西。

    1. 将 SingleTickerProviderStateMixin 更改为 TickerProviderStateMixin。
    2. 使用两个动画控制器和动画(用于淡出未选中的按钮和向左滑动选中的按钮)。
    3. 按顺序运行动画(请参阅动画补间监听器)。
    4. 添加 bool _buttonSelected 以跟踪动画的完成情况。
    5. 使用 _buttonSelected 构建正确的小部件(小部件 _buttonsWidget())
    class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
      int _pressedButton = -1;
      bool _buttonSelected = false;
    
      Animation<double> _animationFadeOut;
      AnimationController _controllerFadeOut;
      Animation<double> _animationSlideLeft;
      AnimationController _controllerSlideLeft;
    
      @override
      void initState() {
        super.initState();
        _initFadeOutAnimation();
        _initSlideLeftAnimation();
      }
    
      void _initFadeOutAnimation() {
        _controllerFadeOut = AnimationController(
          duration: const Duration(milliseconds: 500),
          vsync: this,
          );
        _animationFadeOut = Tween<double>(begin: 1.0, end: 0.0)
            .animate(_controllerFadeOut)
          ..addListener(() {
            setState(() {
              if (_controllerFadeOut.isCompleted && !_controllerSlideLeft.isAnimating) {
                _controllerSlideLeft.forward();
              }
            });
          });
      }
    
      void _initSlideLeftAnimation() {
        _controllerSlideLeft = AnimationController(
          duration: const Duration(milliseconds: 500),
          vsync: this,
          );
        _animationSlideLeft = Tween<double>(begin: 1.0, end: 0.0)
            .animate(_controllerSlideLeft)
          ..addListener(() {
            setState(() {
              if (_controllerSlideLeft.isCompleted) {
                _buttonSelected = true;
              }
            });
          });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text("Test"),
              ),
            body: Column(
                children: <Widget>[
                  _buttonsWidget(),
                  Text(_pressedButton != null ? "$_pressedButton button pressed" : "No button pressed"),
                  RaisedButton(
                      child: Text("Reset"),
                      onPressed: () {
                        _controllerFadeOut.reset();
                        _controllerSlideLeft.reset();
                        _pressedButton = -1;
                        _buttonSelected = false;
                      }
                      )
                ]
                )
            );
      }
    
      Widget _buttonsWidget() {
        if (_buttonSelected) {
          return Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[_buttonWidget(_pressedButton)]
              );
        } else {
          return Row(
              children: <Widget>[
                _animatedButtonWidget(1),
                _animatedButtonWidget(2),
                _animatedButtonWidget(3),
                _animatedButtonWidget(4)
              ]
              );
        }
      }
    
      Widget _animatedButtonWidget(int button) {
        if (button == _pressedButton) {
          return _buttonWidget(button);
        } else {
          return SizeTransition(
              sizeFactor: _animationSlideLeft,
              axis: Axis.horizontal,
              child: Opacity(
                  opacity: _animationFadeOut.value,
                  child: _buttonWidget(button)
                  )
              );
        }
      }
    
      Widget _buttonWidget(int button) {
        return RaisedButton(
            onPressed: () => _onButtonClick(button),
            child: Text("Button $button")
            );
      }
    
      void _onButtonClick(int button) {
        setState(() {
          _pressedButton = button;
        });
        _controllerFadeOut.forward();
      }
    }
    
    

    【讨论】:

    • 嗨@Eldar。 SizeTransition 只应该改变小部件的大小。我的意图是在隐藏其他按钮后将跨越行的子小部件移动到 行的最开始。我设法隐藏了其他按钮,但不知道如何滑动按钮。我可以使用 Tween 并将结束位置设置为 (0, 0) 并使用 Translation 来实现它,但是我不确定在初始化动画时如何设置动画的开始位置,因为它可能是动态的.
    • 抱歉耽搁了。我根据您的评论更新了我的答案。 SizeTransition 用于通过按比例缩小其他按钮来使选定按钮向左侧过渡的效果。然后你只构建你选择的那个按钮。
    • 完美。谢谢@Eldar。对实现此类功能的正确方法有什么建议吗?就使用 Row 以外的正确布局而言。无论如何,再次感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-19
    • 2019-03-25
    • 1970-01-01
    相关资源
    最近更新 更多