Skip to content

Instantly share code, notes, and snippets.

@rydmike
Created September 9, 2021 21:21
Show Gist options
  • Save rydmike/ceb9b022da43734bf618ac98cd088853 to your computer and use it in GitHub Desktop.
Save rydmike/ceb9b022da43734bf618ac98cd088853 to your computer and use it in GitHub Desktop.
Flutter Drag and Tap Re-order list, with "fake" up down animation effect... just an experiment
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData.from(colorScheme: const ColorScheme.light()),
darkTheme: ThemeData.from(colorScheme: const ColorScheme.dark()),
themeMode: ThemeMode.system,
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const int _msAnimTime = 250;
final List<Item> _items =
List<Item>.generate(10, (int index) => Item(id: index, hide: false));
@override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
return Scaffold(
appBar: AppBar(title: const Text("Tap Reorder List")),
body: ReorderableListView(
buildDefaultDragHandles: false,
children: <Widget>[
for (int index = 0; index < _items.length; index++)
AnimatedHide(
key: Key('${_items[index].id}'),
duration: const Duration(milliseconds: _msAnimTime),
hide: _items[index].hide,
child: Container(
color: _items[index].id.isOdd ? oddItemColor : evenItemColor,
child: Row(
children: <Widget>[
Container(
width: 64,
height: 64,
padding: const EdgeInsets.all(8),
child: ReorderableDragStartListener(
index: index,
child: Card(
color: colorScheme.primary,
elevation: 2,
child: const Icon(
Icons.pan_tool_outlined,
color: Colors.white,
),
),
),
),
// Move up item one step.
Container(
width: 64,
height: 64,
padding: const EdgeInsets.all(8),
child: GestureDetector(
onTap: () {
if (index > 0) {
setState(() {
_items[index].hide = true;
});
Future.delayed(
const Duration(milliseconds: _msAnimTime),
() {
setState(() {
final Item item = _items.removeAt(index);
_items.insert(index - 1, item);
_items[index - 1].hide = false;
});
});
}
},
child: Card(
color: colorScheme.secondary,
elevation: 2,
child: index > 0
? const Icon(Icons.arrow_upward)
: null,
),
),
),
// Move down item one step.
Container(
width: 64,
height: 64,
padding: const EdgeInsets.all(8),
child: GestureDetector(
onTap: () {
if (index < _items.length - 1) {
setState(() {
_items[index].hide = true;
});
Future.delayed(
const Duration(milliseconds: _msAnimTime),
() {
setState(() {
final Item item = _items.removeAt(index);
_items.insert(index + 1, item);
_items[index + 1].hide = false;
});
});
}
},
child: Card(
color: colorScheme.secondaryVariant,
elevation: 2,
child: index < _items.length - 1
? const Icon(Icons.arrow_downward)
: null,
),
),
),
Text('Item ${_items[index].id}'),
],
),
),
),
],
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final Item item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
});
},
));
}
}
class Item {
Item({required this.id, required this.hide});
final int id;
bool hide;
}
/// This widget is good for using a boolean condition to show/hide the [child]
/// widget. It is a simple convenience wrapper for AnimatedSwitcher
/// where the Widget that is Switched to is an invisible SizedBox.shrink()
/// effectively removing the child by animation into a zero sized widget
/// instead.
class AnimatedHide extends StatelessWidget {
const AnimatedHide({
final Key? key,
required this.hide,
this.duration = const Duration(milliseconds: 500),
required this.child,
}) : super(key: key);
/// Set hide to true to remove the child with size transition.
final bool hide;
/// The duration of the hide animation.
final Duration duration;
/// The widget to be conditionally hidden, when hide is true.
final Widget child;
@override
Widget build(final BuildContext context) {
return AnimatedSwitcher(
duration: duration,
transitionBuilder:
(final Widget child, final Animation<double> animation) {
return SizeTransition(
sizeFactor: animation,
child: child,
);
},
child: hide ? const SizedBox.shrink() : child,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment