gq3
gq3

Reputation: 859

Animate position of a widget in Flutter

I would like to animate the position of an icon widget on the screen. The problem is that I don't know the end position of widget until the whole screen has finished building.

This sketch illustrates what I want to achieve: enter image description here
The icon should start out from the centre of the screen and end up in middle of the space available above the text field and button. The text field and the button themselves are centred in the screen.

How can I achieve this?

Upvotes: 3

Views: 9274

Answers (2)

William Terrill
William Terrill

Reputation: 3744

I've updated the code since I didn't fully understand your question before.

Here's the updated code on dartpad.dev so you can see how it looks and works: https://dartpad.dev/74043837eaa459089fae03449fdc0778

And here's the code:

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

Differences and explanation:

In this version, I made it as responsive as possible, and was able to use a global key in order to select the red box and get it's position and size. At the end of render time, it invokes _afterLayout() that updates the information for the animation, so that it starts on the red box, and finishes halfway up. (using the data from the size and position of the red box.)

This was new to me, so I relied on this tutorial here: https://medium.com/@diegoveloper/flutter-widget-size-and-position-b0a9ffed9407 in order to get a feel for how to approach getting size and position of a widget as well as how to manage running the code at the end of render.

Upvotes: 3

gq3
gq3

Reputation: 859

This is can be achieved using a Hero animation. If I wrap and tag the icon widgets with a Hero widgets in both screens, the icon will automatically be animated when the screen is transitioned using a navigator.

Upvotes: 1

Related Questions