Home > Software design >  Flutter Animation: Move a widget from a flex layout to another flex layout
Flutter Animation: Move a widget from a flex layout to another flex layout

Time:05-20

I want to move a widget from a flex layout (in this case a Wrap) to another flex layout (a Wrap again). It's a bit like programmatically dragging and droping.

I have already programmed a working proof of concept:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

// To get the position of a widget
extension GlobalKeyExtension on GlobalKey {
  Rect? get globalPaintBounds {
    final renderObject = currentContext?.findRenderObject();
    final matrix = renderObject?.getTransformTo(null);

    if (matrix != null && renderObject?.paintBounds != null) {
      final rect = MatrixUtils.transformRect(matrix, renderObject!.paintBounds);
      return rect;
    } else {
      return null;
    }
  }
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: MyHomePage());
  }
}

// A simple square
class Square extends StatelessWidget {
  final Color color;

  const Square({this.color = Colors.blue, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: color,
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final parentKey = GlobalKey();
  final sourceKey = GlobalKey();
  final targetKey = GlobalKey();
  Rect? sourceRect;
  Rect? targetRect;
  bool moved = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Move test')),
      body: Stack(
        key: parentKey,
        children: [
          Padding(
            padding: const EdgeInsets.all(32),
            child: Column(
              children: [
                // The top Wrap is the target of the movement
                Wrap(
                  spacing: 16,
                  runSpacing: 16,
                  children: [
                    const Square(),
                    const Square(),
                    // Display the target
                    if (moved)
                      const Square(color: Colors.yellow)
                    else if (sourceRect != null && targetRect != null)
                      const Opacity(opacity: 0, child: Square())
                    else
                      Square(key: targetKey),
                    const Square(),
                    const Square(),
                  ],
                ),
                const Spacer(),
                Wrap(
                  spacing: 16,
                  runSpacing: 16,
                  children: [
                    const Square(color: Colors.red),
                    const Square(color: Colors.red),
                    // Display the source
                    if (sourceRect == null && targetRect == null && !moved)
                      InkWell(
                        onTap: () {
                          print('Tapped');
                          setState(() {
                            final parentRect = parentKey.globalPaintBounds;
                            if (parentRect != null) {
                              sourceRect = sourceKey.globalPaintBounds;
                              sourceRect = sourceRect?.translate(
                                -parentRect.left,
                                -parentRect.top,
                              );
                              targetRect = targetKey.globalPaintBounds;
                              targetRect = targetRect?.translate(
                                -parentRect.left,
                                -parentRect.top,
                              );
                            }
                          });
                          WidgetsBinding.instance
                              .addPostFrameCallback((timeStamp) {
                            setState(() {
                              sourceRect = targetRect;
                              print('START animation!');
                            });
                          });
                        },
                        child: Square(key: sourceKey, color: Colors.red),
                      ),
                    const Square(color: Colors.red),
                    const Square(color: Colors.red),
                  ],
                ),
              ],
            ),
          ),
          // This part is within Stack and moves the square
          if (sourceRect != null && targetRect != null)
            AnimatedPositioned(
              left: sourceRect!.left,
              top: sourceRect!.top,
              duration: const Duration(seconds: 1),
              onEnd: () {
                print('STOP animation!');
                setState(() {
                  sourceRect = null;
                  targetRect = null;
                  moved = true;
                });
              },
              child: const Square(color: Colors.yellow),
            ),
        ],
      ),
    );
  }
}

And the result is this: a moving widget animation (gif).

So, my question is this: Is there a better way to do this within the framework or with a library?

CodePudding user response:

I'm not sure what you meant by "better way" but there is a package (not library) called local_hero which goes for the same animation you would get with a Hero widget between two pages but inside the same one, maybe you could try it out to see if it is what you wanted:

https://pub.dev/packages/local_hero

Just in case you didn't know, https://pub.dev is where flutter/dart packages are officially posted.

  • Related