【问题标题】:Flutter SlideTransition begin with Offset OFF SCREENFlutter SlideTransition 从 Offset OFF SCREEN 开始
【发布时间】:2019-06-25 00:35:08
【问题描述】:

我的应用是一个简单的 Column,其中包含 3 个 Text 小部件,包含在 SlideTransitions 中。我的目标是让应用程序在屏幕上什么都没有加载,然后将这些 Text 小部件从底部(屏幕外)向上移动到屏幕(位于中间)。

return Column(
   children: <Widget>[
     SlideTransition(
         position:_curve1,
         child: Text('Hello 1')
     ),
     SlideTransition(
         position:_curve1,
         child: Text('Hello 2')
     ),
     SlideTransition(
         position:_curve1,
         child: Text('Hello 3')
     ),
   ]
);

问题是在定义动画时,你必须指定它们的初始偏移量。

@override
void initState(){
    ...
    Offset beginningOffset = Offset(0.0,20.0);
    _curve1 = Tween<Offset>(begin: beginningOffset, end: Offset.zero).animate(
    CurvedAnimation(
        parent: _animationController,
        curve: Curves.easeInOut)));
    ...
}  

在这里您可以看到 beginOffset 被指定为 Offset(0.0, 20.0) ,它确实成功地将小部件定位在屏幕之外。但是如果我突然在平板电脑上运行它会发生什么?由于 Offset 被定义为小部件的高度单位,那么这显然不是将小部件定位到屏幕外的好方法。

由于动画是在“initState()”中定义的……我看不出有什么办法可以使用 MediaQuery.of(context)……因为我们没有上下文。

在原生 iOS 中,这很简单。在“viewDidLoad”中,您将从 self.view 中获取框架。您将明确地将每个文本字段定位在框架的边缘。然后,您将为约束更改设置动画,将它们定位在您想要的位置。为什么这在 Flutter 中这么难?

我觉得特别奇怪的是,我能找到的任何示例都完全没有涵盖这种确切的场景(在屏幕外开始动画)。例如:

https://github.com/flutter/website/blob/master/examples/_animation/basic_staggered_animation/main.dart

似乎几乎所有类型的动画都在这里展示,除了加载时的显式屏幕外动画......也许我只是错过了一些东西。

编辑:ThePeanut 已经解决了!检查他的“第二次更新”。

【问题讨论】:

    标签: flutter dart


    【解决方案1】:

    您可能想尝试在 initState 中使用 addPostFrameCallback 方法。

        @override
        void initState() {
          super.initState();
          WidgetsBinding.instance.addPostFrameCallback((_){
             // Schedule code execution once after the frame has rendered
             print(MediaQuery.of(context).size.toString());
          });
        }
    

    Flutter Api Docs Link

    您也可以为此使用 Future

        @override
        void initState() {
          super.initState();
          new Future.delayed(Duration.zero, () {
              // Schedule a zero-delay future to be executed
              print(MediaQuery.of(context).size.toString());
          });
        }
    

    希望这会有所帮助。

    更新

    有点不寻常的方法,但它确实可以满足您的需求。

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
      Animation<Offset> animation;
      AnimationController animationController;
    
      @override
      void initState() {
        super.initState();
    
        animationController = AnimationController(
          vsync: this,
          duration: Duration(seconds: 2),
        );
        animation = Tween<Offset>(
          begin: Offset(0.0, 1.0),
          end: Offset(0.0, 0.0),
        ).animate(CurvedAnimation(
          parent: animationController,
          curve: Curves.fastLinearToSlowEaseIn,
        ));
    
        Future<void>.delayed(Duration(seconds: 1), () {
          animationController.forward();
        });
      }
    
      @override
      void dispose() {
        // Don't forget to dispose the animation controller on class destruction
        animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Stack(
            alignment: Alignment.center,
            fit: StackFit.expand,
            children: <Widget>[
              SlideTransition(
                position: animation,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    CircleAvatar(
                      backgroundImage: NetworkImage(
                        'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
                      ),
                      radius: 50.0,
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      }
    }
    

    它是这样工作的:

    1.我们创建一个Stack项目,其中包含扩展其中任何子项的选项。

    2. 将您要显示的每张幻灯片 包裹到一个Column 中,子元素居中对齐。列将占用堆栈大小的 100%。

    3.将动画的开始Offset设置为Offset(0.0, 1.0)

    请记住,Offset 中的 dxdy 不是像素或类似的东西,而是 Widget 的 宽度或高度的比率。例如:如果您的小部件的宽度为 100.0,并且您将 0.25 作为 dx - 它会导致您的孩子向右移动 25.0 个点 .

    因此,将偏移设置为 (0.0, 1.0) 会将 Column 移到屏幕底部 100% 的高度(这是 Flutter 中的页面转换次数)。

    4。延迟 1 秒后将 Column 动画化回原来的位置。

    第二次更新

    此代码根据屏幕大小和小部件大小计算偏移量。 PS。我不知道可能有更好的方法。

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Page(),
        );
      }
    }
    
    class Page extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _PageState();
    }
    
    class _PageState extends State<Page> with SingleTickerProviderStateMixin {
      // Set the initial position to something that will be offscreen for sure
      Tween<Offset> tween = Tween<Offset>(
        begin: Offset(0.0, 10000.0),
        end: Offset(0.0, 0.0),
      );
      Animation<Offset> animation;
      AnimationController animationController;
    
      GlobalKey _widgetKey = GlobalKey();
    
      @override
      void initState() {
        super.initState();
    
        // initialize animation controller and the animation itself
        animationController = AnimationController(
          vsync: this,
          duration: Duration(seconds: 2),
        );
        animation = tween.animate(animationController);
    
        Future<void>.delayed(Duration(seconds: 1), () {
          // Get the screen size
          final Size screenSize = MediaQuery.of(context).size;
          // Get render box of the widget
          final RenderBox widgetRenderBox =
              _widgetKey.currentContext.findRenderObject();
          // Get widget's size
          final Size widgetSize = widgetRenderBox.size;
    
          // Calculate the dy offset.
          // We divide the screen height by 2 because the initial position of the widget is centered.
          // Ceil the value, so we get a position that is a bit lower the bottom edge of the screen.
          final double offset = (screenSize.height / 2 / widgetSize.height).ceilToDouble();
    
          // Re-set the tween and animation
          tween = Tween<Offset>(
            begin: Offset(0.0, offset),
            end: Offset(0.0, 0.0),
          );
          animation = tween.animate(animationController);
    
          // Call set state to re-render the widget with the new position.
          this.setState((){
            // Animate it.
            animationController.forward();
          });
        });
      }
    
      @override
      void dispose() {
        // Don't forget to dispose the animation controller on class destruction
        animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          alignment: Alignment.center,
          fit: StackFit.loose,
          children: <Widget>[
            SlideTransition(
              position: animation,
              child: CircleAvatar(
                key: _widgetKey,
                backgroundImage: NetworkImage(
                  'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
                ),
                radius: 50.0,
              ),
            ),
          ],
        );
      }
    }
    

    【讨论】:

    • 这有助于在运行时找到应用程序/设备视图的大小,但事实证明这只是问题的 1/2。由于“偏移”类使用您应用偏移量的小部件的高度定义偏移量,因此我还需要知道我试图隐藏在屏幕外的任何小部件的高度。必须有更好的方法来将小部件从屏幕外动画化到屏幕上......
    • 我可能只是提供一个完整的例子。您希望所有 3 个文本同时滑到页面中心吗?
    • 其实我希望它们是交错的动画。但为简单起见,不需要惊人的...我正在努力解决的主要问题是让它们从屏幕上开始(不是基于小部件高度的一些偏移,这使得很难/不可能知道如果你在屏幕外就够了)。具体的例子真的很有帮助
    • 更新了我的答案。
    • 首先感谢您提供的详尽示例,非常感谢。我仍然不知道的一件事是您将如何为 CircleAvatar 设置动画。假设您确实希望使用交错动画将每个 CircleAvatar 动画化到页面中(每个之间有轻微延迟)。您不能像这里一样仅偏移他们的容器……您实际上需要偏移 CircleAvatar。但是除非您静态定义它们的高度(然后将其与 MediaQuery.of(context).size.height 一起使用以找到所需的偏移量)...那么您将如何做:(
    【解决方案2】:

    我找到了这个解决方案,以获得屏幕外的确切值。

    屏幕大小 / 2 / 小部件大小 + 1 偏移量的一半

    没有半偏移,小部件只有一半在屏幕外。

    final widgetHeight = 190;
    final offsetScreen = MediaQueryData.fromWindow(WidgetsBinding.instance!.window).size.height / 2 / widgetHeight + 0.5;
    

    【讨论】:

      猜你喜欢
      • 2020-10-21
      • 2020-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-28
      • 2018-04-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多