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.