Explicitly reordering items of reorderableListView

602 Views Asked by At

I want to achieve the functionality along with animation of reorderableListView Widget when it is dragged and dropped but instead of dragging how could it be done explicitly by a button click?

Expected Output : -

This is what I have done , remove the desired item and added a new item on that index.

Code : -

import 'package:flutter/material.dart';

void main() => runApp(const ReorderableApp());

class ReorderableApp extends StatelessWidget {
  const ReorderableApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('ReorderableListView Sample')),
        body: const ReorderableExample(),
      ),
    );
  }
}

class ReorderableExample extends StatefulWidget {
  const ReorderableExample({super.key});

  @override
  State<ReorderableExample> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<ReorderableExample> {
  final List<int> _items = List<int>.generate(4, (int index) => index);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Container(
            height: 300,
            width: 300,
            color: Colors.blue[100],
            padding: const EdgeInsets.all(10),
            child: Center(
              child: ReorderableListView(
                children: <Widget>[
                  for (int index = 0; index < _items.length; index += 1)
                    Container(
                      key: Key('$index'),
                      height: 60,
                      margin: const EdgeInsets.all(5),
                      color: Colors.blue[400],
                      alignment: Alignment.center,
                      child: Text('Item ${_items[index]}'),
                    ),
                ],
                onReorder: (int oldIndex, int newIndex) {},
              ),
            ),
          ),
          const SizedBox(
            height: 20,
          ),
          FloatingActionButton(
            onPressed: () {
              setState(() {
                final int item = _items.removeAt(0);
                _items.insert(1, item);
              });
            },
            child: const Icon(Icons.arrow_downward_sharp),
          ),
        ],
      ),
    );
  }
}

Actual Output : -

3

There are 3 best solutions below

6
On

You can use the great_list_view package.

An example of using this package:

import 'package:flutter/material.dart';
import 'package:great_list_view/great_list_view.dart';

void main() {
  Executor().warmUp();
  runApp(App());
}

class App extends StatefulWidget {
  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Test App',
        home: SafeArea(
            child: Scaffold(
          body: Body(key: gkey),
        )));
  }
}

class Body extends StatefulWidget {
  Body({Key? key}) : super(key: key);

  @override
  _BodyState createState() => _BodyState();
}

class _BodyState extends State<Body> {
  late List<ItemData> currentList;

  @override
  void initState() {
    super.initState();
    currentList = listA;
  }

  void swapList() {
    setState(() {
      if (currentList == listA) {
        currentList = listB;
      } else {
        currentList = listA;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scrollbar(
      child: AutomaticAnimatedListView<ItemData>(
        list: currentList,
        comparator: AnimatedListDiffListComparator<ItemData>(
            sameItem: (a, b) => a.id == b.id,
            sameContent: (a, b) =>
                a.color == b.color && a.fixedHeight == b.fixedHeight),
        itemBuilder: (context, item, data) => data.measuring
            ? Container(
                margin: EdgeInsets.all(5), height: item.fixedHeight ?? 60)
            : Item(data: item),
        listController: controller,
        addLongPressReorderable: true,
        reorderModel: AutomaticAnimatedListReorderModel(currentList),
        detectMoves: true,
      ),
    );
  }
}

class Item extends StatelessWidget {
  final ItemData data;

  const Item({Key? key, required this.data}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onTap: () => gkey.currentState?.swapList(),
        child: AnimatedContainer(
            height: data.fixedHeight ?? 60,
            duration: const Duration(milliseconds: 500),
            margin: EdgeInsets.all(5),
            padding: EdgeInsets.all(15),
            decoration: BoxDecoration(
                color: data.color,
                border: Border.all(color: Colors.black12, width: 0)),
            child: Center(
                child: Text(
              'Item ${data.id}',
              style: TextStyle(fontSize: 16),
            ))));
  }
}

class ItemData {
  final int id;
  final Color color;
  final double? fixedHeight;
  const ItemData(this.id, [this.color = Colors.blue, this.fixedHeight]);
}

List<ItemData> listA = [
  ItemData(1, Colors.orange),
  ItemData(2),
  ItemData(3),
  ItemData(4, Colors.cyan),
  ItemData(5),
  ItemData(8, Colors.green)
];
List<ItemData> listB = [
  ItemData(4, Colors.cyan),
  ItemData(2),
  ItemData(6),
  ItemData(5, Colors.pink, 100),
  ItemData(7),
  ItemData(8, Colors.yellowAccent),
];

final controller = AnimatedListController();
final gkey = GlobalKey<_BodyState>();
1
On

If you don't mind not having the animation, I would use a normal ListView:

ListView(
      children: [
        for(var i = 0; i < _items.length; i++) ItemWidget(_items[I])
       ],
     )

Note though that this won't be an optimal solution for many children.

0
On

You should create reorder method like this:

 void reorderData(int oldindex, int newindex){
  setState(() {
    if(newindex>oldindex){
     newindex-=1;
    }
    final items =widget.item.removeAt(oldindex);
    widget.item.insert(newindex, items);
  });
 } 

Then assign it to onReorder parameter in your ReorderListView like that

ReorderableListView(
    children: <Widget>[
      for (int index = 0; index < _items.length; index += 1)
         Container(
             key: Key('$index'),
             height: 60,
             margin: const EdgeInsets.all(5),
             color: Colors.blue[400],
             alignment: Alignment.center,
             child: Text('Item ${_items[index]}'),
          ),
   ],
   onReorder: reorderData,
 )  

This is an article about ReorderableListView