import 'package:flutter/material.dart';
class DragDropExample extends StatefulWidget {
@override
_DragDropExampleState createState() => _DragDropExampleState();
}
class _DragDropExampleState extends State<DragDropExample> {
List<String> libraryCards = ["Card A", "Card B", "Card C"];
List<String> droppedCards = [];
int? dropIndex; // To track where the card will be dropped
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Drag and Drop Example')),
body: Row(
children: [
// Left Container: Library
Expanded(
flex: 1,
child: Container(
color: Colors.blue[50],
padding: const EdgeInsets.all(8),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: libraryCards.map((card) {
return Draggable<String>(
data: card,
feedback: Material(
child: Card(
color: Colors.blue[200],
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(card),
),
),
),
childWhenDragging: Opacity(
opacity: 0.5,
child: Card(
color: Colors.blue[100],
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(card),
),
),
),
child: Card(
color: Colors.blue[100],
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(card),
),
),
);
}).toList(),
),
),
),
// Right Container: Droppable List
Expanded(
flex: 2,
child: Container(
color: Colors.green[50],
padding: const EdgeInsets.all(8),
child: DragTarget<String>(
onAccept: (data) {
setState(() {
droppedCards.insert(dropIndex ?? droppedCards.length, data);
dropIndex = null; // Reset the drop index
});
},
onMove: (details) {
RenderBox renderBox = context.findRenderObject() as RenderBox;
double localDy = renderBox.globalToLocal(details.offset).dy;
double itemHeight = 70; // Approximate height of each card
dropIndex = (localDy / itemHeight).floor().clamp(0, droppedCards.length);
setState(() {});
},
onLeave: (_) => setState(() => dropIndex = null),
builder: (context, candidateData, rejectedData) {
return Column(
children: [
for (int i = 0; i <= droppedCards.length; i++) ...[
if (i == dropIndex)
const Divider(
color: Colors.green,
thickness: 3,
),
if (i < droppedCards.length)
Draggable<String>(
data: droppedCards[i],
feedback: Material(
child: Card(
color: Colors.green[200],
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(droppedCards[i]),
),
),
),
childWhenDragging: Opacity(
opacity: 0.5,
child: Card(
color: Colors.green[100],
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(droppedCards[i]),
),
),
),
onDragCompleted: () => droppedCards.removeAt(i),
child: Card(
color: Colors.green[100],
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(droppedCards[i]),
),
),
),
],
],
);
},
),
),
),
],
),
);
}
}
[![enter image description here][1]][1]
[1]: https://i.sstatic.net/pzOOz4Cf.png
[![enter image description here][1]][1]
----------
I believe the answer [solution from CopsOnRoad][2] is better and simple for someone who don't want to use a 3rd party library. **However, since there is no animation, I add the scale animation when the card is viewed (expand) and the previous card is swiped (shrink) using index**. So what happened is whenever the first time the page load, 1st and 2nd card won't have any animation, and when the card is swiped, only the previous and current card have the scale animation. So this is my implementation:
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = -1, previousIndex = 0;
double getAnimationValue(int currentIndex, int widgetIndex, int previousIndex,
{bool begin = true}) {
if (widgetIndex == currentIndex) {
return begin ? 0.9 : 1;
} else {
return begin ? 1 : 0.9;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 200, // card height
child: PageView.builder(
itemCount: 10,
controller: PageController(viewportFraction: 0.7),
onPageChanged: (int index) {
setState(() {
if (currentIndex != -1) {
previousIndex = currentIndex;
}
currentIndex = index;
});
},
itemBuilder: (_, widgetIndex) {
return (currentIndex != -1 &&
(previousIndex == widgetIndex ||
widgetIndex == currentIndex))
? TweenAnimationBuilder(
duration: const Duration(milliseconds: 400),
tween: Tween<double>(
begin: getAnimationValue(
currentIndex,
widgetIndex,
previousIndex,
),
end: getAnimationValue(
currentIndex,
widgetIndex,
previousIndex,
begin: false,
),
),
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Card(
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Card${widgetIndex + 1}",
style: const TextStyle(fontSize: 30),
),
Text(
"$widgetIndex >> Widget Index << $widgetIndex",
style: const TextStyle(fontSize: 22),
),
Text(
"$currentIndex >> Current Index << $currentIndex",
style: const TextStyle(fontSize: 22),
),
Text(
"$previousIndex >> Previous Index << $previousIndex",
style: const TextStyle(fontSize: 22),
),
],
),
),
);
},
)
: Transform.scale(
// this is used when you want to disable animation when initialized the page
scale:
(widgetIndex == 0 && currentIndex == -1) ? 1 : 0.9,
child: Card(
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Card${widgetIndex + 1}",
style: const TextStyle(fontSize: 30),
),
Text(
"$widgetIndex >> Widget Index << $widgetIndex",
style: const TextStyle(fontSize: 22),
),
Text(
"$currentIndex >> Init Index << $currentIndex",
style: const TextStyle(fontSize: 22),
),
Text(
"$previousIndex >> Previous Index << $previousIndex",
style: const TextStyle(fontSize: 22),
),
],
),
),
);
},
),
),
],
),
);
}
}
I used TweenAnimationBuilder for this animation and hardcoded the widget. You can use method for your widget or use package [flutter_animate][3] for easy animation whenever necessary.
[1]: https://i.sstatic.net/c8LiH.gif
[2]: https://stackoverflow.com/a/60456937/11451151
[3]: https://pub.dev/packages/flutter_animate