在Flutter中,如何实现小部件位置的动画效果?

3
我想在屏幕上动画显示图标控件的位置。问题是,直到整个屏幕构建完毕后,我才知道小部件的最终位置。
这张草图说明了我的想法: enter image description here
图标应该从屏幕中心开始,并最终位于文本字段和按钮上方可用空间的中央。文本字段和按钮本身位于屏幕中心。
如何实现这一点?
2个回答

3

由于我之前没有完全理解你的问题,所以我已经更新了代码。

在dartpad.dev上,这是更新后的代码,你可以看到它的外观和运作方式: https://dartpad.dev/74043837eaa459089fae03449fdc0778

这是代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        body: Center(
          child: LoginWidget(),
        ),
      ),
    );
  }
}

class LoginWidget extends StatefulWidget {
  const LoginWidget({
    Key key,
  }) : super(key: key);

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

class _LoginWidgetState extends State<LoginWidget> {
  @override
  initState() {
    initialTimer();
    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    super.initState();
  }

  GlobalKey _keyRed = GlobalKey();

  var gotPosition = false;
  var redPositionX;
  var redPositionY;
  var redWidth;
  var redHeight;

  _afterLayout(_) {
    _getPositions();
    _getSizes();
    setState(() {
      gotPosition = true;
    });
  }

  _getSizes() {
    final RenderBox renderBoxRed = _keyRed.currentContext.findRenderObject();
    final sizeRed = renderBoxRed.size;
    print("SIZE of Red: $sizeRed");
    redWidth = sizeRed.width;
    redHeight = sizeRed.height;
  }

  _getPositions() {
    print("beer");
    final RenderBox renderBoxRed = _keyRed.currentContext.findRenderObject();
    print(renderBoxRed);
    final positionRed = renderBoxRed.localToGlobal(Offset.zero);
    print("POSITION of Red: $positionRed ");
    redPositionY = positionRed.dy;
    redPositionX = positionRed.dx;
  }

  var startAnimation = false;
  initialTimer() async {
    await new Future.delayed(const Duration(milliseconds: 500));
    setState(() {
      startAnimation = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    var screenWidth = MediaQuery.of(context).size.width;
    var screenHeight = MediaQuery.of(context).size.height;
    print('width/height $screenWidth / $screenHeight');

    return Container(
      color: Colors.blue,
      child: Center(
        child: Stack(
          children: [
            Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Container(
                  margin: EdgeInsets.symmetric(horizontal: screenWidth * 0.3),
                  key: _keyRed,
                  color: Colors.red,
                  width: screenWidth * 0.4,
                  height: screenHeight * 0.2,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      Container(
                        color: Colors.yellow,
                        width: screenWidth * 0.3,
                        height: screenHeight * 0.05,
                      ),
                      Container(
                        color: Colors.yellow,
                        width: screenWidth * 0.3,
                        height: screenHeight * 0.05,
                      )
                    ],
                  ),
                ),
                // ),
              ],
            ),
            AnimatedPositioned(
              duration: Duration(seconds: 1),
              top: startAnimation
                  ? ((redPositionY / 2) - (redHeight / 2))
                  : redPositionY,
              curve: Curves.easeInCubic,
              left: 0.3 * screenWidth,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    color: Colors.green,
                    width: screenWidth * 0.4,
                    height: screenHeight * 0.2,
                  ),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}

区别和解释:
在这个版本中,我尽可能地使其具有响应性,并能够使用全局键来选择红色框并获取其位置和大小。在渲染结束时,它调用_afterLayout()更新动画的信息,以便从红色框开始,并完成到一半。(使用红色框的大小和位置数据。)
这对我来说是新的,所以我依靠这里的教程:https://medium.com/@diegoveloper/flutter-widget-size-and-position-b0a9ffed9407来了解如何获取小部件的大小和位置,以及如何管理在渲染结束时运行代码的方法。

我知道如何在已知小部件起始和结束位置的情况下对其进行动画处理。问题是如何对一个由布局确定其结束位置的小部件进行动画处理。例如,在这种情况下,图标是 Expanded 小部件的子级,而该小部件又是 Column 小部件的子级。 - gq3
你有任何例子代码吗?我想这是由响应式需求驱动的,对吗?你尝试使用Mediaquery.of(context).height调用的百分比作为初始和最终位置(以及对象的高度)了吗? - William Terrill
现在我更好地理解了你的问题,正在尝试更新我的代码。我特别关注了这个链接:https://medium.com/@diegoveloper/flutter-widget-size-and-position-b0a9ffed9407 其中定义了一个全局键来获取小部件在布局完成后的位置。然后,(我希望)可以在动画中使用此位置。 - William Terrill
最好的方法似乎是使用Hero动画。我可以在完全不同的布局中标记两个小部件,并在从第一个屏幕过渡到第二个屏幕时自动执行动画。 - gq3
我已经使用直接控制完成了它。我即将更新我的答案。我认为英雄应该也能工作...只是你对动画的控制不会那么多。(但是,如果它能工作,那就好了!) - William Terrill
显示剩余3条评论

1
这可以通过使用 Hero 动画来实现。如果我在两个屏幕中将图标小部件包装并标记为 Hero 小部件,则在使用导航器进行屏幕转换时,图标将自动进行动画处理。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接