Flutter 动画:将一个小部件从一个 flex 布局移动到另一个 flex 布局

Flutter Animation: Move a widget from a flex layout to another flex layout

我想将小部件从弹性布局(在本例中为 Wrap)移动到另一个弹性布局(再次为 Wrap)。这有点像编程拖放。

我已经编写了一个有效的概念验证程序:

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

结果是这样的:a moving widget animation (gif)。

所以,我的问题是:是否有更好的方法在框架内或库中执行此操作?

我不确定你所说的“更好的方法”是什么意思,但是有一个名为 package(不是库)的 package 10=] 这与您在两个页面之间但在同一页面内使用 Hero 小部件获得的动画相同,也许您可​​以尝试一下,看看它是否是您想要的:

https://pub.dev/packages/local_hero

以防万一你不知道,https://pub.dev 是 flutter/dart 包正式发布的地方。