A Flutter GridView whose items the user can interactively reorder by dragging

Overview

Pub Version Codecov GitHub branch checks state

A GridView whose items the user can interactively reorder by dragging.

Compared to the given ReorderableListView, it is possible to reorder different sizes of widgets with or without animation.

Also you can lock specific items that should not change their position.

An animated image of the iOS ReordableGridView UI An animated image of the iOS ReordableGridView UI

Features

Use this package in your Flutter App to:

  • Enable a reordering logic with different widgets
  • Simplified widget
  • Works with all kind of widgets that are rendered inside
  • Animated when reordering items
  • Locking all items you don't want to move

Getting started

Simply add ReordableGridView to your preferred Widget and specify a list of children.

Usage

import 'package:flutter_reorderable_grid_view/reorderable_grid_view.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: ReorderableGridView(
            children: List.generate(
              20,
                  (index) => Container(
                color: Colors.blue,
                height: 100,
                width: 100,
                child: Text(
                  'test $index',
                  style: const TextStyle(
                    fontSize: 20,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Additional information

ReordableGridView

Parameter Description Default Value
children Displays all given children that are build inside a Wrap. -
spacing Spacing in vertical direction between children. 8
runSpacing Spacing in horizontal direction between children. 8
enableAnimation Enables the animation when changing the positions of childrens after drag and drop. true
enableLongPress Decides if the user needs a long press to move the item around. true
longPressDelay Specify the delay to move an item when enabling long press. 500 ms
lockedChildren Define all children that can't be moved while dragging. You need to add the index of this child in a list. []
onUpdate After dragging an item to a new position, this function is called.
The function contains a list of all items in the same order they were added. The number in the list tells where the item is currently positioned.
-

Future

If you have feature requests or found some problems, feel free and open your issues in the GitHub project.

Thank you for using this package.

Comments
  • Saving the items order

    Saving the items order

    Hi Karvulf,

    Can you please add the feature on how to save the order of times, so for the next time the user gets the list to have the order he saved? Or can you guide me how to achieve this, please?

    Thanks in advance!

    help wanted 
    opened by RoyalCoder88 13
  • _ReorderableBuilderState._handleDragEnd thows null check error

    _ReorderableBuilderState._handleDragEnd thows null check error

    Hello, thank you for your great package.

    We've got this exception thrown at the production app.

    flutter_reorderable_grid_view: ^3.1.2
    

    Sentry stacktrace:

    _CastError: Null check operator used on a null value
      split_config.arm64_v8a.apk0x727de7b968 _ReorderableBuilderState._handleDragEnd (reorderable_builder.dart:318)
      split_config.arm64_v8a.apk0x727de7b558 _ReorderableBuilderState._handleDragEnd (reorderable_builder.dart:315)
      split_config.arm64_v8a.apk0x727de7ca24 _ReorderableScrollingListenerState.build.<T> (reorderable_scrolling_listener.dart:82)
      split_config.arm64_v8a.apk0x727dc6e674 RenderPointerListener.handleEvent (proxy_box.dart:2916)
      split_config.arm64_v8a.apk0x727e15f3a0 GestureBinding.dispatchEvent (binding.dart:425)
      split_config.arm64_v8a.apk0x727db0df7c RendererBinding.dispatchEvent (binding.dart:329)
      split_config.arm64_v8a.apk0x727db0dec0 GestureBinding._handlePointerEventImmediately (binding.dart:380)
      split_config.arm64_v8a.apk0x727db0dac4 GestureBinding.handlePointerEvent (binding.dart:344)
      split_config.arm64_v8a.apk0x727dbd560c GestureBinding._handlePointerDataPacket (binding.dart:285)
      split_config.arm64_v8a.apk0x727dbd5510 GestureBinding._handlePointerDataPacket (binding.dart:280)
      split_config.arm64_v8a.apk0x727e1143a0 _rootRunUnary (zone.dart:1442)
      split_config.arm64_v8a.apk0x727da35d4c _rootRunUnary (zone.dart:1432)
      split_config.arm64_v8a.apk0x727e00f5f8 _CustomZone.runUnary (zone.dart:1335)
      split_config.arm64_v8a.apk0x727e23269c _CustomZone.runUnaryGuarded (zone.dart:1244)
      split_config.arm64_v8a.apk0x727da46af0 _invoke1 (hooks.dart:170)
      split_config.arm64_v8a.apk0x727da469f0 PlatformDispatcher._dispatchPointerDataPacket (platform_dispatcher.dart:331)
      split_config.arm64_v8a.apk0x727da46968 _dispatchPointerDataPacket (hooks.dart:94)
    
    bug 
    opened by meomap 9
  • Cannot change crossAxisCount dynamically

    Cannot change crossAxisCount dynamically

    I'm trying to build a reorderable grid view where the user can select the crossAxisCount value themselves. This causes a few problems. If I change crossAxisCount from 2 to 3, the item being dragged is is still of larger size from the previous value. The other items also don't move as you would expect.

    Is this a known issue? I can probably get a minimal working example, but it should be trivial to reproduce.

    bug Release 4.0.0 
    opened by Erfa 8
  • Reorder not possible when there is an animation

    Reorder not possible when there is an animation

    Issue

    When adding an animation I can no longer re-order the grid.

    Possible cause

    I found that it might be the setState((){}) in the animation listener. If you remove the _expandAnimation.value in the Transform.scale and set the scale to 1, I can re-order due to the animation not being built.

    Code
    import 'package:flutter/material.dart';
    import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
    
    class Grids extends StatefulWidget {
      const Grids({Key key}) : super(key: key);
    
      @override
      State<Grids> createState() => _GridsState();
    }
    
    class _GridsState extends State<Grids> with SingleTickerProviderStateMixin {
      List<Test> editable_urls = [
        Test('https://picsum.photos/id/0/5616/3744', 1),
        Test('https://picsum.photos/id/1/5616/3744', 2),
        Test('https://picsum.photos/id/10/2500/1667', 3),
      ];
      AnimationController _controller;
      Animation<double> _expandAnimation;
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(
          duration: const Duration(milliseconds: 200),
          vsync: this,
        );
    
        _expandAnimation = Tween<double>(begin: 0, end: 1.0).animate(_controller);
        _expandAnimation.addListener(() {
          setState(() {
            print('setstate');
          });
        });
      }
    
      List<Widget> media() {
        List<Widget> generatedChildren = List.generate(
          editable_urls.length,
          (index) {
            final devicesize = MediaQuery.of(context).size;
            double _width = devicesize.width;
            double _height = devicesize.height;
    
            return Stack(
              key: Key(editable_urls[index].url), //
              children: [
                InkWell(
                  onTap: () {
                    print('ola');
                  },
                  child: AspectRatio(
                    aspectRatio: 1,
                    child: Container(
                      child: Image.network(
                        editable_urls[index].url,
                        fit: BoxFit.cover,
                      ),
                    ),
                  ),
                ),
              ],
            );
          },
        );
        return generatedChildren;
      }
    
      @override
      Widget build(BuildContext context) {
        final devicesize = MediaQuery.of(context).size;
        double _width = devicesize.width;
        double _height = devicesize.height;
        return SafeArea(
          child: Scaffold(
            appBar: AppBar(
              title: const Text('ola'),
            ),
            floatingActionButton:
                // body:
                Container(
              height: _height,
              width: _width,
              child: SizedBox.expand(
                child: Stack(
                  alignment: Alignment.bottomRight,
                  clipBehavior: Clip.none,
                  children: [
                    InkWell(
                      highlightColor: Colors.transparent,
                      splashColor: Colors.transparent,
                      onTap: () {
                        if (_controller.isCompleted) {
                          _controller.reverse();
                        } else {
                          _controller.forward();
                        }
                      },
                      child: Container(
                        decoration: BoxDecoration(
                          shape: BoxShape.circle,
                          gradient: LinearGradient(
                              begin: Alignment.centerLeft,
                              end: Alignment.centerRight,
                              colors: _controller.isCompleted
                                  ? [Colors.red, Colors.red]
                                  : [Color(0xff283e87), Color(0xff743351)]),
                        ),
                        height: _height * .065,
                        width: _height * .065,
                        child: _controller.isCompleted
                            ? Icon(
                                Icons.close,
                                color: Colors.white,
                              )
                            : Icon(
                                Icons.perm_media_outlined,
                                color: Colors.white,
                              ),
                      ),
                    ),
                    Positioned(
                      right: _width * .01,
                      bottom: _height * .08,
                      child: Transform.scale(
                        scale: _expandAnimation.value,
                        child: Container(
                          padding: EdgeInsets.all(_height * .007),
                          clipBehavior: Clip.hardEdge,
                          height: _height * .25,
                          width: _width * .9,       
                          decoration: BoxDecoration(
                            color: Colors.black.withOpacity(0.7),
                            borderRadius: BorderRadius.circular(_height * .03),
                          ),
                          child: ReorderableBuilder(
                            children: media(),
                            onReorder: (List orderUpdateEntities) {
                              for (final orderUpdateEntity in orderUpdateEntities) {
                                print(orderUpdateEntity);
                                final pos = editable_urls
                                    .removeAt(orderUpdateEntity.oldIndex);
                                editable_urls.insert(
                                    orderUpdateEntity.newIndex, pos);
                              }
                            },
                            lockedIndices: [editable_urls.length],
                            longPressDelay: Duration(milliseconds: 200),
                            builder: (children, scrollController) {
                              return GridView(
                                controller: scrollController,
                                children: children,
                                gridDelegate:
                                    const SliverGridDelegateWithFixedCrossAxisCount(
                                  crossAxisCount: 4,
                                  mainAxisSpacing: 3,
                                  crossAxisSpacing: 3,
                                ),
                              );
                            },
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class Test {
      String url;
      int rand;
      Test(this.url, this.rand);
    }
    

    I do apologize that the code is not null-safe

    Any assistance will greatly be appreciated ๐Ÿ˜Ž

    help wanted question 
    opened by cavedweller 6
  • Add support for ReorderableGridView.builder()

    Add support for ReorderableGridView.builder()

    When dealing with a large list, it can get pretty slow while scrolling.

    I understand the implementation is different and quite challenging, but something to consider support for better performance.

    enhancement 
    opened by renannery 6
  • Making boxShadow customisable

    Making boxShadow customisable

    Hi - great package you've made! Would greatly appreciate the ability to customise the boxShadow. I have circular items with a square shadow. Wanting to change the color is also another use-case.

    enhancement 
    opened by WD-J 4
  • RefreshIndicator problems with ReorderableGridView

    RefreshIndicator problems with ReorderableGridView

    In my app, I use an Refreshindicator to reload data and it worked fine with the normal GridView.

    I use the ReorderableWrap when the display is too small for > 1 columns. There are no problems with the RefreshIndicator.

    Do you have an idea / fix for this?

    Flutter tree: RefreshIndicator -- Container -- ValueListenableBuilder -- ReorderableGridView

    Thank you very much!

    bug 
    opened by timonaldenhoff 4
  • Feature/children size change with animation v2

    Feature/children size change with animation v2

    In this branch, I added an animation for the following things:

    • adding an item is shown with an animated opacity
    • removing an item has multiple animations
      • one animation for the removed item with an animated opacity
      • animations on all items that change theirs position because of the removed item
    opened by karvulf 4
  • Support reordering with animation for programatic changes to the data source

    Support reordering with animation for programatic changes to the data source

    In my app, I don't need the user to reorder the contents but they are done when some filters are enabled.

    Use case:

    • Grids show cars.
    • When user presses red color, I want to show only the red colored cars
    • And I want this filtering to happen in an animated way.
    • So there might be several cars that are removed from different places of the cars array.
    • When the filter is removed, all cars should be added in place with ordering animation.

    Can I use this package for this need?

    This implicitly_animated_reorderable_list package does it using a diff algorithm

    enhancement 
    opened by aytunch 4
  • Draggable working on web but not on android

    Draggable working on web but not on android

    Hello, i'm using ReorderableBuilder with GridView.builder and i'm able to drag on drop in web, but i'm unable to on Android.

    Has anyone else had this problem and have you been able to fix?

        final generatedChildren = List.generate(
          gridItems.length,
          (index) {
            Target item = gridItems[resolvedIndex];
            return Container(
              key: item.key,
              decoration: BoxDecoration(
              ),
              child: DistanceHistoryWidget(
                key: item.key,
                distanceNumber: item.targetDistance,
                elevation: item.elevation,
                distance: "${item.targetDistance.toString()} m",
                textColor: item.targetDistanceTextColor,
                backgroundColor: item.targetDistanceBackgroundColor,
              ),
            );
          },
        );
    
        var _gridViewKey = GlobalKey();
        var _scrollController = ScrollController();
        final lockedIndices = <int>[];
    
    ...
           Container(
                    height: scrollContainerHeight,
                    padding: EdgeInsets.fromLTRB(
                        0, labelBottomPadding, 0, keyboardTopPadding),
                    child: ReorderableBuilder(
                        children: generatedChildren,
                        enableDraggable: true,
                        lockedIndices: lockedIndices,
                        onReorder: onReorder,
                        enableScrollingWhileDragging: true,
                        scrollController: _scrollController,
                        enableLongPress: false,
                        dragChildBoxDecoration: BoxDecoration(),
                        onDragEnd: () => {log('drag ended')},
                        onDragStarted: () => {log('drag started')},
                        builder: (children) {
                          return GridView.builder(
                            key: _gridViewKey,
                            gridDelegate:
                                const SliverGridDelegateWithFixedCrossAxisCount(
                                    crossAxisCount: 2,
                                    mainAxisExtent: 69,
                                    mainAxisSpacing: 10,
                                    crossAxisSpacing: 10),
                            reverse: true,
                            itemCount: resolvedGridCount,
                            itemBuilder: (BuildContext context, int index) {
                              return children[index];
                            },
                          );
                        }),
                  ),
    question 
    opened by KtodaZ 3
  • _ReorderableBuilderState._checkForCollisions throws null cast error

    _ReorderableBuilderState._checkForCollisions throws null cast error

    Hello, we noticed this exception thrown up at a rate of 1/1000 users . Probably an edge case and not a fatal one.

    flutter_reorderable_grid_view: ^3.1.1
    

    Sentry stacktrace:

    _CastError: Null check operator used on a null value
      split_config.arm64_v8a.apk0x74955bda74 _ReorderableBuilderState._checkForCollisions (reorderable_builder.dart:483)
      split_config.arm64_v8a.apk0x74955bd814 _ReorderableBuilderState._checkForCollisions (reorderable_builder.dart:482)
      split_config.arm64_v8a.apk0x74955be630 _ReorderableScrollingListenerState._handleDragUpdate (reorderable_scrolling_listener.dart:118)
      split_config.arm64_v8a.apk0x74955be4ec _ReorderableScrollingListenerState.build.<T> (reorderable_scrolling_listener.dart:77)
      split_config.arm64_v8a.apk0x7495490d6c RenderPointerListener.handleEvent (proxy_box.dart:2914)
      split_config.arm64_v8a.apk0x74958db90c GestureBinding.dispatchEvent (binding.dart:425)
      split_config.arm64_v8a.apk0x74952b36d8 RendererBinding.dispatchEvent (binding.dart:329)
      split_config.arm64_v8a.apk0x74952b361c GestureBinding._handlePointerEventImmediately (binding.dart:380)
      split_config.arm64_v8a.apk0x74952b3220 GestureBinding.handlePointerEvent (binding.dart:344)
      split_config.arm64_v8a.apk0x7495353640 GestureBinding._handlePointerDataPacket (binding.dart:285)
      split_config.arm64_v8a.apk0x7495353544 GestureBinding._handlePointerDataPacket (binding.dart:280)
      split_config.arm64_v8a.apk0x749588ef9c _rootRunUnary (zone.dart:1442)
      split_config.arm64_v8a.apk0x74951c2388 _rootRunUnary (zone.dart:1432)
      split_config.arm64_v8a.apk0x7495792d98 _CustomZone.runUnary (zone.dart:1335)
      split_config.arm64_v8a.apk0x74959b95a4 _CustomZone.runUnaryGuarded (zone.dart:1244)
      split_config.arm64_v8a.apk0x74951e7ae4 _invoke1 (hooks.dart:170)
      split_config.arm64_v8a.apk0x74951fd7b0 PlatformDispatcher._dispatchPointerDataPacket (platform_dispatcher.dart:331)
      split_config.arm64_v8a.apk0x74951fd728 _dispatchPointerDataPacket (hooks.dart:94)
    
    bug 
    opened by meomap 3
  • Bug: Drag and scroll manually leads to incorrect behavior

    Bug: Drag and scroll manually leads to incorrect behavior

    How to reproduce this issue:

    • build GridView with mulitple children, so that you can scroll
    • drag one child at the bottom
    • then scroll manually (not with the dragged child) to the top
    • then the children are not changing their positions even when the dragged child is above them
    bug 
    opened by andreBalh 0
  • Bug: Changing `crossAxisCount` while dragging item leads to weird behavior

    Bug: Changing `crossAxisCount` while dragging item leads to weird behavior

    This bug came from #54.

    The following bug happens:

    • adding GridView with crossAxisCount = 2
    • start dragging item
    • while dragging changing crossAxisCount to 3
    • the dragged item still looks like the old one when crossAxisCount was 2

    This bug should be fixed for the version 4.0.0. It is also important to check, if there are more bugs when changing this value while dragging or animating an item.

    bug Release 4.0.0 
    opened by karvulf 0
  • [Feature] Supporting Drag Target to Merge

    [Feature] Supporting Drag Target to Merge

    Use case: Drag one item to another so that I can merge the one to another object when I drop. (It's like in Android/iOS home screen where you can drag apps to folders or drag into another that it creates a folder) Basically supporting both drag and drop, and when dropped into another item (before reorder) user can have ability to merge the items.

    Simple action: https://i.stack.imgur.com/bIS09.gif

    So user can either re-order, or merges the items together before the next index moves.

    enhancement Release 4.0.0 
    opened by objectorientedperson 6
  • Feature: Render children only in `itemBuilder` of GridView.builder

    Feature: Render children only in `itemBuilder` of GridView.builder

    even use ReorderableType reorderableType = ReorderableType.gridViewBuilder; so it means the library is not really using the viewport of rendering UI as GridView.Builder do

    please if you can solve this issue will be game-changing for Animated GridView.

    enhancement Release 4.0.0 
    opened by AmineLAHRIM 15
  • Feature: Add support for `Wrap`

    Feature: Add support for `Wrap`

    Currently, only all types of GridView are supported for animation and drag and drop. It would be nice to have also a support for Wrap because of similar behaviour.

    enhancement Release 4.0.0 
    opened by karvulf 15
Owner
null
A custom dropdown button lets the user select from a number of items

CircularDropDownMenu Description A custom dropdown button lets the user select from a number of items. The button shows the currently selected item as

Surya Dev Singh 2 Dec 5, 2020
This package supports drag & drop widgets inside the GridView.builder for multiplatform

This package supports drag & drop widgets inside the GridView.builder for multiplatform. It provides all the properties which are available in Gridview. builder and easy to implement with the few lines of code.

MindInventory 68 Dec 29, 2022
A multi select form field using alert dialog to select multiple items with checkboxes and showing as chips.

A multi select form field using alert dialog to select multiple items with checkboxes and showing as chips.

Carlos Eugenio Torres 73 Sep 7, 2022
A Flutter package which provides helper widgets for selecting single or multiple account/user from the given list.

account_selector A Flutter package which provides helper widgets for selecting single or multiple account/user from a list Supported Dart Versions Dar

Harpreet Singh 49 Oct 7, 2021
A widget that allow user resize the widget with drag

Flutter-Resizable-Widget A widget that allow user resize the widget with drag Note: this widget uses Getx Example bandicam.2021-11-11.12-34-41-056.mp4

MohammadAminZamani.afshar 22 Dec 13, 2022
Widget to let the user search through a keyword string typed on a customizable keyboard in a single or multiple choices list presented as a dropdown in a dialog box or a menu.

searchable_dropdown Widget to let the user search through a keyword string typed on a customizable keyboard in a single or multiple choices list prese

Bobby Stenly Irawan 108 Sep 11, 2022
Flutter Triple Status Button can use toggle button but in three statuses.

triple_status_button Triple Status Button. Flutter Triple Status Button can use toggle button but in three statuses. Property Description height heigh

MahdiBagjani 2 Nov 13, 2021
This repo is for anything that can be reusable in flutter like custom widgets ๐ŸŸฅ, animations ๐ŸŒŸand more

Flutter Shortcuts This repo is for anything that can be reusable in flutter like custom widgets ?? , animations ?? and more. How to Use Just get the f

Abdelrahman Mostafa Elmarakby 91 Dec 3, 2022
Flutter package: Define a theme (colors, text styles etc.) as static const values which can be changed dynamically.

Flutter package: Define a theme (colors, text styles etc.) as static const values which can be changed dynamically. Also comes with useful extensions to create text styles by composition.

Marcelo Glasberg 21 Jan 2, 2023
A widget lib that the widget in this lib can react to flutter ScrollController's offset

Language: English | ไธญๆ–‡็ฎ€ไฝ“ linked_scroll_widgets A lib full of widgets that can react to the scrollController's offset change,to custom your UI effect.

WenJingRui 8 Oct 16, 2022
Flutter Color Picker Wheel - an easy to use widget which can be heavily customized

Flutter Color Picker Wheel Flutter Color Picker Wheel is an easy to use widget which can be heavily customized. You can use the WheelColorPicker direc

Kexin Lu 35 Oct 4, 2022
Flutter ScrollView Observer - a library of widget that can be used to listen for child widgets those are being displayed in the scroll view

Flutter ScrollView Observer - a library of widget that can be used to listen for child widgets those are being displayed in the scroll view

ๆž—ๆดต้”‹ 67 Jan 6, 2023
Widget, that can make any static located widget hidable

Installing See the official installing guidline from hidable/install Usage & Overview To start using Hidable widget, we have to create a ScrollControl

Anon 18 Dec 16, 2022
A sliding up panel widget which can be used to show or hide content, beautiful and simple.

flutter_sliding_up_panel A sliding up panel widget which can be used to show or hide content, beautiful and simple. demo Getting Started dependencies:

null 25 Dec 12, 2022
PlutoGrid is a dataGrid that can be controlled by the keyboard on desktop and web. Of course, it works well on Android and IOS.

PlutoGrid is a dataGrid that can be controlled by the keyboard on desktop and web.

Manki Kim 453 Jan 4, 2023
Custom bottom sheet widget, that can resize by drag and then scroll.

Bottom Sheet This package is part of the SurfGear toolkit made by Surf. About Custom bottom sheet widget that can be resized in response to drag gestu

Surf 92 Dec 16, 2022
Global loading widget, which can be used through simple configuration.

load Global loading widget, which can be used through simple configuration. Pure flutter library, not use native code. It is similar to OKToast in use

Caijinglong 35 Nov 4, 2022
This app is a good example of how adding animations can liven up a digital clock.

Animal Clock This app is a good example of how adding animations can liven up a digital clock. The lion's head bobbles along without any care in the w

sei 7 Jun 29, 2022
An advanced switch widget, that can be fully customized with size, text, color, radius of corners.

flutter_advanced_switch An advanced switch widget, that can be fully customized with size, text, color, radius of corners. Switch Light Switch Dark Ge

Alex Melnyk 13 Dec 15, 2022