【问题标题】:Flutter SlideTransition begin with Offset CENTER SCREENFlutter SlideTransition 从偏移中心屏幕开始
【发布时间】:2020-10-21 01:57:07
【问题描述】:

我正在尝试将图像从屏幕中心设置到新位置。我正在尝试计算运行时相对于小部件和屏幕尺寸的偏移量。下面的链接描述了如何在屏幕外开始偏移,但在我的情况下,我希望过渡从屏幕中心开始,而不管设备大小。

Flutter SlideTransition begin with Offset OFF SCREEN.

编辑:

[第一张图片是启动画面]imagelink

[第二个图像是我希望图像从中心移动到的下一个屏幕]imagelink2

我尝试过的代码。在这里,我试图将屏幕的高度减半并将其用作起始位置。但数学是错误的。

Future<void>.delayed(Duration(seconds: 0), () {
  final Size screenSize = MediaQuery.of(context).size;
  print('screen size: $screenSize');
  double offsetY = screenSize.height / 2 / 1000;
  print('offsetY: $offsetY');
  tween = Tween<Offset>(
    begin: Offset(0.0, offsetY),
    end: Offset(0.0, 0.0),
  );
  _offsetAnimation = tween.animate(
    CurvedAnimation(
      parent: _controller,
      curve: Curves.bounceIn,
    ),
  );
  this.setState(() {
    // Animate it.
    Timer(
      Duration(milliseconds: 3000),
      () => _controller.forward(),
    );
  });
});

【问题讨论】:

    标签: flutter animation dart slide


    【解决方案1】:
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: Page()
          ),
        );
      }
    }
    
    class Page extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _PageState();
    }
    
    class _PageState extends State<Page> with SingleTickerProviderStateMixin {
      AnimationController animationController;
    
      @override
      void initState() {
        super.initState();
        animationController = AnimationController(
          vsync: this,
          duration: Duration(seconds: 2),
        );
        animationController.repeat(); //just to show it can be animated
      }
    
      @override
      void dispose() {
        // Don't forget to dispose the animation controller on class destruction
        animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return SlideTransition(
          position: Tween<Offset>(begin: Offset.zero,
                                  end: Offset(1,0)).animate(animationController),
          child: Container(
              height: double.infinity,
              child: Center(
                child: CircleAvatar(
                  backgroundImage: NetworkImage(
                    'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
                  ),
                  radius: 50.0,
                ),
              )),
        );
      }
    }
    

    这里的诀窍是为整个容器(屏幕大小)设置动画,但在另一个大小的中心有一个孩子。在 Tween 中,您使用 Offset.zero (在中心的位置)定义开始,在您希望它移动的位置结束时定义 Offset。如果你想要更多的控制,你也可以使用带有堆栈的 PositionTransition,但为此我需要查看你的一些代码或一些关于你的目标的图片

    更新

    好吧,你可以做些什么来实现该动画是使用一个动画控制器并有 2 个补间(一个用于对齐图像,一个用于按钮的不透明度)

    class Page2 extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _PageState2();
    }
    
    class _PageState2 extends State<Page2>  with SingleTickerProviderStateMixin{
      AnimationController animationController;
      Animation<AlignmentGeometry> _align;
      Animation<double> _opacity;
    
      @override
      void initState() {
        super.initState();
        animationController = AnimationController(
          vsync: this,
          duration: Duration(seconds: 1),
        );
        _align = AlignmentGeometryTween(begin: Alignment.bottomCenter, end: Alignment.center).animate(animationController);
        _opacity = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(parent: animationController, curve: Interval(0.5, 1),    reverseCurve: Interval(0.5, 1.0)));
    
        //The next piece of code is just for test
        animationController.forward();
        animationController.addStatusListener((status) {
          if(status == AnimationStatus.completed)
            Future.delayed(const Duration(milliseconds: 800), () => animationController.reverse());
          else if(status == AnimationStatus.dismissed) 
            Future.delayed(const Duration(milliseconds: 800), () => animationController.forward());
        });
      }
      
      @override
      void dispose() {
        animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
            decoration: BoxDecoration(
                gradient: LinearGradient(
                    colors: [Colors.blue, Colors.teal[200]],
                    begin: Alignment.bottomLeft,
                    end: Alignment.topRight,
                    stops: [0.5, 0.85])
            ),
            child: Column(
              children: [
                Expanded(
                  flex: 1,
                  child: AnimatedBuilder( //create an AnimatedBuilder and provide the Animation of the align
                    animation: _align,
                      child: CircleAvatar(
                        backgroundImage: NetworkImage(
                            'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
                        ),
                        radius: 50.0,
                      ),
                      builder: (context, child){
                        return Align(
                          alignment: _align.value, //use the align value
                          child: child
                        );
                      },
                   )
                ),
                Expanded(
                  child: FadeTransition( //simply use this widget that receives an Animation<double> to animate opacity from 0 to 1
                     opacity: _opacity,
                      child: Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: [
                        Container(
                          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0),
                          width: double.infinity,
                          child: RaisedButton(
                            child: Text('Log in', style: TextStyle(color: Colors.teal[300], fontSize: 12)),
                            color: Colors.white,
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(16.0),
                            ),
                            onPressed: () => print('Log in'),
                         ),
                       ),
                        Container(
                         padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0),
                         width: double.infinity,
                         child: RaisedButton(
                         child: Text('Sign up', style: TextStyle(color: Colors.white70, fontSize: 12)),
                           color: Colors.teal[300],
                           shape: RoundedRectangleBorder(
                             borderRadius: BorderRadius.circular(16.0),
                           ),
                           onPressed: () => print('Sign up'),
                        ),
                       )
                      ]
                    ),
                  )
                ),
              ]
            )
        );
      }
    }
    

    如果您不关心animationController而只想制作一次动画(可能是在第一次显示页面时),您可以使用一些扩展ImplicitAnimations的小部件,这个类会为您完成所有动画,而无需您创建控制器,只需通过 setState 并更改它们的值

    class Page1 extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _PageState1();
    }
    
    class _PageState1 extends State<Page1>{
      double opacity = 0;
      double padding = 0;
      bool align = false;
    
      @override
      void initState() {
        super.initState();
        //setState after 300ms just to see the changes
        Future.delayed(const Duration(milliseconds: 300), () => _setAnimation());
      }
      
      _setAnimation() => setState((){
          opacity = opacity == 0 ? 1 : 0; //when you setState to different values it will animate to those values
          align = !align;
        });
    
      @override
      void dispose() {
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
            decoration: BoxDecoration(
                gradient: LinearGradient(
                    colors: [Colors.blue, Colors.teal[200]],
                    begin: Alignment.bottomLeft,
                    end: Alignment.topRight,
                    stops: [0.5, 0.85])
            ),
            child: Column(
              children: [
                Expanded(
                  flex: 1,
                  child: AnimatedAlign(
                    duration: const Duration(milliseconds: 300),
                    alignment: align ? Alignment.center : Alignment.bottomCenter, //pass an Align based on the bool value, or if you want to directly pass an Alignment
                      child: CircleAvatar(
                        backgroundImage: NetworkImage(
                          'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
                        ),
                        radius: 50.0,
                      ),
                  )
                ),
                Expanded(
                  child: AnimatedOpacity(
                    opacity: opacity, //give the opacity value
                    duration: const Duration(milliseconds: 500),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: [
                        Container(
                          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0),
                          width: double.infinity,
                          child: RaisedButton(
                            child: Text('Log in', style: TextStyle(color: Colors.teal[300], fontSize: 12)),
                            color: Colors.white,
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(16.0),
                            ),
                            onPressed: () => print('Log in'),
                         ),
                       ),
                        Container(
                         padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0),
                         width: double.infinity,
                         child: RaisedButton(
                         child: Text('Sign up', style: TextStyle(color: Colors.white70, fontSize: 12)),
                           color: Colors.teal[300],
                           shape: RoundedRectangleBorder(
                             borderRadius: BorderRadius.circular(16.0),
                           ),
                           onPressed: () => _setAnimation(),
                        ),
                       )
                      ]
                    )
                  ),
                ),
              ]
            )
        );
      }
    }
    

    这种小部件也有一个名为 onEnd 的 VoidCallback,所以如果你想制作一些动画,比如说移动图像,直到完成更改不透明度,只需像这样更改它

    _setAnimation() => setState((){
         //don't change opacity here 
         align = !align;
     });
    

    然后在 AnimatedAlign 的 onEnd 中

    AnimatedAlign(
      duration: const Duration(milliseconds: 300),
      alignment: align ? Alignment.center : Alignment.bottomCenter,
      child: CircleAvatar(
        backgroundImage: NetworkImage(
          'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
        ),
      radius: 50.0,
      onEnd: () => setState(() => opacity = opacity == 0 ? 1 : 0) //when the align animation ends, it will change the opacity
    ),
    

    【讨论】:

    • 我无法为整个容器设置动画,因为容器包含其他小部件。我附上了我想要实现的图像。谢谢
    • 我给你一些例子来说明如何实现这个动画,你也可以使用 Stack 和一些 Tween 来实现它,但这取决于你和你的页面还有什么其他东西跨度>
    猜你喜欢
    • 1970-01-01
    • 2022-12-31
    • 1970-01-01
    • 2018-01-01
    • 2020-08-26
    • 2022-12-14
    • 2020-10-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多