karvulf / flutter-reorderable-grid-view

BSD 3-Clause "New" or "Revised" License
143 stars 20 forks source link

Gridview.builder #71

Closed NathanFlorins closed 1 year ago

NathanFlorins commented 1 year ago

Hello, is it possible to use a gridview.builder with this plugin? Here is an example of code that I would like to put in place I also use a pagination


return ReorderableBuilder(
      enableLongPress: true,
      enableDraggable: true,
      enableScrollingWhileDragging: false,
      scrollController: scrollController,
      onReorder: (List<OrderUpdateEntity> orderUpdateEntities) {
        for (final orderUpdateEntity in orderUpdateEntities) {
          final publication = controller.publicationList.removeAt(orderUpdateEntity.oldIndex);
          controller.publicationList.insert(orderUpdateEntity.newIndex, publication);
        }
      },
      builder: (children) {
        return Obx(() => controller.isLoading.value == false
            ? GridView.builder(
                key: GlobalKey(),
                shrinkWrap: true,
                scrollDirection: Axis.vertical,
                controller: scrollController,
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  mainAxisSpacing: 1,
                  crossAxisSpacing: 1,
                ),
                itemCount: controller.publicationList.length < profileController.totalPublications.value
                    ? controller.publicationList.length + 3
                    : controller.publicationList.length,
                itemBuilder: (BuildContext context, int index) {
                  if(index < controller.publicationList.length){
                    return InkWell(
                        key: Key(controller.publicationList.elementAt(index).publicationHasFiles?.first.fileUrl ?? ""),
                        onTap: () async {

                          await Get.to(PublicationShow(user, index));
                          controller.update();
                          //controller.refresh();
                        },
                        child: controller.publicationList.elementAt(index).publicationHasFiles?.first.type == "image"
                            ? Stack(
                          children: [
                            Shimmer.fromColors(
                              baseColor: Colors.grey.shade300,
                              highlightColor: Colors.grey.shade100,
                              child: Container(
                                color: LGREY,
                              ),
                            ),
                            CachedNetworkImage(
                              imageUrl: controller.publicationList.elementAt(index).publicationHasFiles?.first.fileUrl ?? "",
                              fit: BoxFit.cover,
                              height: double.infinity,
                              width: double.infinity,
                            ),
                          ],
                        )
                            : Container(
                          color: Colors.black,
                          child: Stack(
                            children: [
                              PublicationVideoPlayer(
                                videoPlayerController: VideoPlayerController.network(controller.publicationList.elementAt(index).publicationHasFiles?.first.fileUrl ?? ""),
                                autoPlay: false,
                                aspectRation: 1/1,
                                showControls: false,
                              ),
                              const Padding(
                                padding: EdgeInsets.all(4.0),
                                child: Icon(Icons.play_circle_outline, color: Colors.white, size: 15),
                              )
                            ],
                          ),
                        )
                    );
                  }
                  else {
                    if(controller.publicationList.length < profileController.totalPublications.value){
                      return Shimmer.fromColors(
                        baseColor: Colors.grey.shade300,
                        highlightColor: Colors.grey.shade100,
                        child: Container(
                          color: LGREY,
                        ),
                      );
                    }
                  }
                  return const SizedBox();
                },
              )
            : GridView.builder(
                key: GlobalKey(),
                shrinkWrap: true,
                scrollDirection: Axis.vertical,
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  mainAxisSpacing: 1,
                  crossAxisSpacing: 1,
                ),
                itemCount: 18,
                itemBuilder: (BuildContext context, int index) {
                  return Shimmer.fromColors(
                    baseColor: Colors.grey.shade300,
                    highlightColor: Colors.grey.shade100,
                    child: Container(
                      color: LGREY,
                    ),
                  );
                },
              )
        );
      },
      children: generatedChildren,
    );
karvulf commented 1 year ago

Hello @NathanFlorins It is possible to use GridView.builder. But I recommend to use version 5.0.0-dev.3, because at this version I support GridView.builder. In my example app, I added also a part for GridView.builder:

...

      case ReorderableType.gridViewBuilder:
        return ReorderableBuilder.builder(
          key: Key(_gridViewKey.toString()),
          onReorder: _handleReorder,
          lockedIndices: lockedIndices,
          onDragStarted: () {
            const snackBar = SnackBar(
              content: Text('Dragging has started!'),
            );
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          },
          scrollController: _scrollController,
          childBuilder: (itemBuilder) {
            return GridView.builder(
              key: _gridViewKey,
              controller: _scrollController,
              itemCount: children.length,
              itemBuilder: (context, index) {
                return itemBuilder(
                  _getChild(index: index),
                  index,
                );
              },
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 4,
                mainAxisSpacing: 4,
                crossAxisSpacing: 8,
              ),
            );
          },
        );
...

  void _handleReorder(ReorderedListFunction reorderedListFunction) {
    setState(() {
      children = reorderedListFunction(children) as List<int>;
    });
  }
NathanFlorins commented 1 year ago

Thank you for your answer, I will test this

karvulf commented 1 year ago

Did it work for you? @NathanFlorins

NathanFlorins commented 1 year ago

Hello, No I have a bit of trouble setting up, I use the getx system

karvulf commented 1 year ago

What I see could be a problem:

karvulf commented 1 year ago

Then I would close this issue until you find a bug or something problematic @NathanFlorins And if you need any help, feel free to write

NathanFlorins commented 1 year ago

Ok Thank you @karvulf

NathanFlorins commented 1 year ago

Hi @karvulf Sorry but i can't drag, Can you tell me what is wrong?


class PublicationList extends StatelessWidget {
  UserModel user;
  int totalPublications;

  PublicationList({
    Key? key,
    required this.user,
    required this.totalPublications,
  }) : super(key: key);

  ScrollController scrollController = ScrollController();

  late PublicationController publicationController;

  @override
  Widget build(BuildContext context) {
    publicationController = Get.find(tag: 'PublicationController#${user.id}');
    var reordableKey = GlobalKey();

    return ReorderableBuilder.builder(
      key: reordableKey,
      onReorder: (value) {},
      onDragStarted: () {
        const snackBar = SnackBar(
          content: Text('Dragging has started!'),
        );
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      },
      scrollController: scrollController,
      childBuilder: (itemBuilder) {
       return GridView.builder(
          shrinkWrap: true,
          scrollDirection: Axis.vertical,
          controller: scrollController,
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            mainAxisSpacing: 1,
            crossAxisSpacing: 1,
          ),
          itemCount: publicationController.publicationList.length < totalPublications
              ? publicationController.publicationList.length + 3
              : publicationController.publicationList.length,
          itemBuilder: (BuildContext context, int index) {
            if (index < publicationController.publicationList.length) {
              return InkWell(
                  key: Key(publicationController.publicationList
                      .elementAt(index)
                      .files
                      .first
                      .fileUrl),
                  onTap: () async {
                    await Get.to(PublicationShow(user, index, totalPublications));
                    publicationController.update();
                    //publicationController.refresh();
                  },
                  child: Builder(builder: (context) {
                    PublicationModel currentPublication =
                    publicationController.publicationList.elementAt(index);
                    PublicationMediaModel currentFile =
                        publicationController.publicationList.elementAt(index).files.first;
                    String cacheKey =
                        "publication_${currentPublication.id}_files_${currentFile.id}";

                    return currentFile.type == "image"
                        ? Stack(
                      children: [
                        Shimmer.fromColors(
                          baseColor: Colors.grey.shade300,
                          highlightColor: Colors.grey.shade100,
                          child: Container(
                            color: LGREY,
                          ),
                        ),
                        CachedNetworkImage(
                          imageUrl: currentFile.fileUrl,
                          fit: BoxFit.cover,
                          height: double.infinity,
                          width: double.infinity,
                          cacheKey: cacheKey,
                          fadeInDuration: Duration.zero,
                          fadeOutDuration: Duration.zero,
                        ),
                      ],
                    )
                        : Container(
                      color: Colors.black,
                      child: Stack(
                        children: [
                          // Pourquoi passer le contrôleur en paramètres si c'est pour le construire à ce moment ?????? ... A revois
                          PublicationVideoPlayer(
                            videoPlayerController:
                            VideoPlayerController.network(currentFile.fileUrl),
                            autoPlay: false,
                            aspectRation: 1 / 1,
                            showControls: false,
                          ),
                          const Padding(
                            padding: EdgeInsets.all(4.0),
                            child: Icon(Icons.play_circle_outline,
                                color: Colors.white, size: 15),
                          )
                        ],
                      ),
                    );
                  }));
            } else {
              if (publicationController.publicationList.length < totalPublications) {
                return Shimmer.fromColors(
                  baseColor: Colors.grey.shade300,
                  highlightColor: Colors.grey.shade100,
                  child: Container(
                    color: LGREY,
                  ),
                );
              }
            }
            return const SizedBox();
          },
        );
      },
    );
  }
}
karvulf commented 1 year ago

You declared your global key in build. You have to declare it at the top of your widget, where also scrollController is created. Also onReorder is not used, so after reordering your items, it is possible that the reorder will be reset, when the widget rebuilds because you didn't update the order of your items. Also your if-else statement in itemBuilder could be a problem if you return widgets with no keys.

If you have still issues, let me know it @NathanFlorins

NathanFlorins commented 1 year ago

I don't even have an animation to move an item, nothing happens, as if it was a simple gridview

NathanFlorins commented 1 year ago

class PublicationList extends StatelessWidget {
  UserModel user;
  int totalPublications;

  PublicationList({
    Key? key,
    required this.user,
    required this.totalPublications,
  }) : super(key: key);

  ScrollController scrollController = ScrollController();

  late PublicationController publicationController;
  var reordableKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    publicationController = Get.find(tag: 'PublicationController#${user.id}');

    return ReorderableBuilder.builder(
      key: reordableKey,
      enableDraggable: true,
      onReorder: (value) {},
      onDragStarted: () {
        const snackBar = SnackBar(
          content: Text('Dragging has started!'),
        );
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      },
      scrollController: scrollController,
      childBuilder: (itemBuilder) {
       return GridView.builder(
          shrinkWrap: true,
          scrollDirection: Axis.vertical,
          controller: scrollController,
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            mainAxisSpacing: 1,
            crossAxisSpacing: 1,
          ),
          itemCount: publicationController.publicationList.length < totalPublications
              ? publicationController.publicationList.length + 3
              : publicationController.publicationList.length,
          itemBuilder: (BuildContext context, int index) {
            if (index < publicationController.publicationList.length) {
              return InkWell(
                  key: Key(publicationController.publicationList
                      .elementAt(index)
                      .files
                      .first
                      .fileUrl),
                  onTap: () async {
                    await Get.to(PublicationShow(user, index, totalPublications));
                    publicationController.update();
                    //publicationController.refresh();
                  },
                  child: Builder(builder: (context) {
                    PublicationModel currentPublication =
                    publicationController.publicationList.elementAt(index);
                    PublicationMediaModel currentFile =
                        publicationController.publicationList.elementAt(index).files.first;
                    String cacheKey =
                        "publication_${currentPublication.id}_files_${currentFile.id}";

                    return currentFile.type == "image"
                        ? Stack(
                      children: [
                        Shimmer.fromColors(
                          baseColor: Colors.grey.shade300,
                          highlightColor: Colors.grey.shade100,
                          child: Container(
                            color: LGREY,
                          ),
                        ),
                        CachedNetworkImage(
                          imageUrl: currentFile.fileUrl,
                          fit: BoxFit.cover,
                          height: double.infinity,
                          width: double.infinity,
                          cacheKey: cacheKey,
                          fadeInDuration: Duration.zero,
                          fadeOutDuration: Duration.zero,
                        ),
                      ],
                    )
                        : Container(
                      color: Colors.black,
                      child: Stack(
                        children: [
                          // Pourquoi passer le contrôleur en paramètres si c'est pour le construire à ce moment ?????? ... A revois
                          PublicationVideoPlayer(
                            videoPlayerController:
                            VideoPlayerController.network(currentFile.fileUrl),
                            autoPlay: false,
                            aspectRation: 1 / 1,
                            showControls: false,
                          ),
                          const Padding(
                            padding: EdgeInsets.all(4.0),
                            child: Icon(Icons.play_circle_outline,
                                color: Colors.white, size: 15),
                          )
                        ],
                      ),
                    );
                  }));
            } else {
              if (publicationController.publicationList.length < totalPublications) {
                return Shimmer.fromColors(
                  key: Key(publicationController.publicationList
                    .elementAt(index)
                    .files
                    .first
                    .fileUrl),
                  baseColor: Colors.grey.shade300,
                  highlightColor: Colors.grey.shade100,
                  child: Container(
                    color: LGREY,
                  ),
                );
              }
            }
            return SizedBox(key: Key(publicationController.publicationList
                .elementAt(index)
                .files
                .first
                .fileUrl));
          },
        );
      },
    );
  }
}
karvulf commented 1 year ago

did you also long pressed the item? the default behavior is to long press the item before the drag started @NathanFlorins

karvulf commented 1 year ago

Oh wait there is another strange thing. In my example, I have the following

        return ReorderableBuilder.builder(
          key: Key(_gridViewKey.toString()),
          onReorder: _handleReorder,
          lockedIndices: lockedIndices,
          onDragStarted: () {
            const snackBar = SnackBar(
              content: Text('Dragging has started!'),
            );
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          },
          scrollController: _scrollController,
          childBuilder: (itemBuilder) {
            return GridView.builder(
              key: _gridViewKey,
              controller: _scrollController,
              itemCount: children.length,
              itemBuilder: (context, index) {
                return itemBuilder(
                  _getChild(index: index),
                  index,
                );
              },
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 4,
                mainAxisSpacing: 4,
                crossAxisSpacing: 8,
              ),
            );
          },
        );

I always called itemBuilder where I add the child and current index. This is also missing in your code @NathanFlorins

NathanFlorins commented 1 year ago

Ok that's it thanks a lot ! @karvulf

karvulf commented 1 year ago

Glad to hear! @NathanFlorins

NathanFlorins commented 1 year ago

Sorry but I still need a little help from you I can't use OrderUpdateEntity in ReorderableBuilder.builder ?

image

karvulf commented 1 year ago

That part changed since the update to 5.0.0. Now you get a function that does the reorder process for you, so you just add your list to the function and it returns the list reordered back.

In your case, that would mean, you just do the following:

...
onReorder: (ReorderedListFunction reorderedListFunction) {
      final updatedChildren = reorderedListFunction(your_children) as List<Your_Type>;
     // do update your children in your controller
},
...

Possible you need a setState if your controller does not notify the the widget to rebuilt. @NathanFlorins

NathanFlorins commented 1 year ago

@karvulf thanks

NathanFlorins commented 1 year ago

@karvulf In order not to send the whole list back to the backend to update it, I would like to send only the new index of the item I moved, is it possible to have the new index/rank of an item with this new function?

karvulf commented 1 year ago

It would be possible but this solution has also a meaning because you can lock items that should not move. The function orders these items in a correct way that ensures it is ordered correctly. That is the reason why I added the function. I don't think it works well with your solution updating the list in the backend with the old and new index. Is it possible that you just send a list of the ids of your items? Then you wouldn't send so much data. @NathanFlorins

NathanFlorins commented 1 year ago

I plan to use this solution, but I would just like to have the index of the item I move, and the index of the place where I move the item @karvulf

karvulf commented 1 year ago

Maybe that would a possible way: If you don't use lockedIndices, so this list is empty, then the normal reorder process would be possible with the old and new index just by inserting it. I could add another function that would return also return a result in that case. But if you have a lot of items and tries to reorder them, then I would recommend to use my function otherwise you could reorder the items by yourself. I will add that to the next update of version 5.0.0-dev.4. @NathanFlorins

NathanFlorins commented 1 year ago

Ok thanks so it is not possible to have the index of the item I modify ?

NathanFlorins commented 1 year ago

Maybe with the onDragStarted method? and onDragEnd ?

karvulf commented 1 year ago

Thats also possible. Then I will do the following thing:

I will try to implement that this evening. @NathanFlorins

NathanFlorins commented 1 year ago

Keep me posted it would help me a lot if you implement this tonight because I need to finish what I am doing for tomorrow night! Thanks in advance!

karvulf commented 1 year ago

Ah well in that case, I will implement this right now @NathanFlorins

karvulf commented 1 year ago

I published 5.0.0-dev.4. You can try to built your solution with that update. I changed the following:

@NathanFlorins

NathanFlorins commented 1 year ago

Oh already, great it works! Thank you so much you are great !!! @karvulf

karvulf commented 1 year ago

No problem 👍 @NathanFlorins

AClon314 commented 11 months ago

@karvulf Hi, is there a way to return SliverGrid.builder? I've tried a few methods like warp in SliverToBoxAdapter()->error: unbounded height. Currently this is runnable:

NestedScrollView(
  headerSliverBuilder: (context, innerBoxIsScrolled) {
    return <Widget>[MyWidget1()];
  },
  body: ReorderableBuilder.builder(childBuilder: (itemBuilder) {
    return GridView.builder(
      itemBuilder: (context, index) {
        return itemBuilder(myWidget(), index);
      },
      gridDelegate:
          SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
    );
  }),
);

but If I write like this then the Error happens which I hardly to solve it. I don't know how to wrap ReorderableBuilder.builder,

// in StatelessWidget
@override
Widget build(BuildContext context) {
  return RefreshIndicator(
    onRefresh: () => Future.sync(
      () => myGetxController.reset(),
    ),
    child: CustomScrollView(
      scrollDirection: Axis.vertical,
      // controller: myGetxController.scrollController,
      slivers: [
        SliverAppBar(
          title: MyWidget1(),
          floating: true,
          flexibleSpace: Placeholder(),
          expandedHeight: 200,
        ),
        ReorderableBuilder.builder(
          enableDraggable: false,
          scrollController: myGetxController.scrollController,
          childBuilder: (itemBuilder) {
            return SliverGrid.builder(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount:
                    (MediaQuery.of(context).size.width / 400).round(),
                mainAxisSpacing: 5,
                crossAxisSpacing: 5,
                childAspectRatio: 3,
              ),
              itemCount: myGetxController.length,
              itemBuilder: (context, index) {
                return itemBuilder(
                  PostWidget(post: myGetxController.datas[index], index: index),
                  index);
              },
            );
          },
        )
      ],
    ),
  );
}

Related Problem: https://stackoverflow.com/questions/71233618/a-renderviewport-expected-a-child-of-type-rendersliver-but-received-a-child-of-t

I've search for questions like "builder in customscrollview" and asked for help from chatGPT, but still can't solve it.

karvulf commented 11 months ago

Hi @AClon314 Currently this is not supported. The reason is that I am using some widgets around the GridView that are not supported by slivers. I will open a new issue to support also Slivers, but it could take some time before this works

karvulf commented 11 months ago

Opened Issue #99 @AClon314