Created
September 9, 2021 21:21
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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