From fda873bc26948fd0f90fb336b5149f1203cc0aca Mon Sep 17 00:00:00 2001 From: cp-sneha-s Date: Sun, 4 Aug 2024 11:08:26 +0530 Subject: [PATCH] Add equality operator to compare items --- example/lib/home_page.dart | 27 ++------- example/lib/model/user_model.dart | 6 ++ lib/src/animated_gridview.dart | 11 +++- lib/src/animated_listview.dart | 50 +++++++++------- lib/src/animated_reorderable_gridview.dart | 5 ++ lib/src/animated_reorderable_listview.dart | 67 +++++++++++----------- lib/src/builder/motion_list_base.dart | 14 ++++- lib/src/builder/motion_list_impl.dart | 48 +++++++++------- 8 files changed, 125 insertions(+), 103 deletions(-) create mode 100644 example/lib/model/user_model.dart diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart index 1f61390..bc51d1b 100644 --- a/example/lib/home_page.dart +++ b/example/lib/home_page.dart @@ -6,24 +6,7 @@ import 'package:example/utils/item_tile.dart'; import 'package:flutter/material.dart'; import 'package:animated_reorderable_list/animated_reorderable_list.dart'; -class User { - final String name; - final int index; - - User({required this.name, required this.index}); - - // To prevent unnecessary animations when updating items in a list, it's essential to correctly implement the == operator and hashCode for your list item class. - // This allows the list to recognize items with the same data as equal, even if they are different instances. - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is User && other.index == index; - } - - @override - int get hashCode => index.hashCode; -} +import 'model/user_model.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -43,10 +26,8 @@ class _HomePageState extends State { void insert() { addedNumber += 1; - setState(() { - list.insert( - list.length, User(name: "User $addedNumber", index: addedNumber)); + list.insert(2, User(name: "User $addedNumber", index: addedNumber)); }); } @@ -174,7 +155,7 @@ class _HomePageState extends State { scrollDirection: Axis.vertical, itemBuilder: (BuildContext context, int index) { return ItemCard( - key: ValueKey(list[index]), + key: ValueKey(list[index].index), index: list[index].index); }, sliverGridDelegate: @@ -223,7 +204,7 @@ class _HomePageState extends State { items: list, itemBuilder: (BuildContext context, int index) { return ItemTile( - key: Key(list[index].name), + key: ValueKey(list[index].index), index: list[index].index); }, enterTransition: animations, diff --git a/example/lib/model/user_model.dart b/example/lib/model/user_model.dart new file mode 100644 index 0000000..fd80d5c --- /dev/null +++ b/example/lib/model/user_model.dart @@ -0,0 +1,6 @@ +class User { + final String name; + final int index; + + User({required this.name, required this.index}); +} \ No newline at end of file diff --git a/lib/src/animated_gridview.dart b/lib/src/animated_gridview.dart index e729ced..39fda98 100644 --- a/lib/src/animated_gridview.dart +++ b/lib/src/animated_gridview.dart @@ -140,6 +140,9 @@ class AnimatedGridView extends StatelessWidget { /// transition for the widget that is built. final AnimatedWidgetBuilder? removeItemBuilder; + /// A function that compares two items to determine whether they are the same. + final bool Function(E a, E b)? isSameItem; + const AnimatedGridView( {Key? key, required this.items, @@ -161,7 +164,9 @@ class AnimatedGridView extends StatelessWidget { this.dragStartBehavior = DragStartBehavior.start, this.clipBehavior = Clip.hardEdge, this.insertItemBuilder, - this.removeItemBuilder}) + this.removeItemBuilder, + this.isSameItem + }) : super(key: key); @override @@ -190,7 +195,9 @@ class AnimatedGridView extends StatelessWidget { exitTransition: exitTransition, scrollDirection: scrollDirection, insertItemBuilder: insertItemBuilder, - removeItemBuilder: removeItemBuilder), + removeItemBuilder: removeItemBuilder, + isSameItem: isSameItem + ), ), ]); } diff --git a/lib/src/animated_listview.dart b/lib/src/animated_listview.dart index a5ea70e..79b4bc2 100644 --- a/lib/src/animated_listview.dart +++ b/lib/src/animated_listview.dart @@ -134,28 +134,33 @@ class AnimatedListView extends StatelessWidget { /// transition for the widget that is built. final AnimatedWidgetBuilder? removeItemBuilder; - const AnimatedListView( - {Key? key, - required this.items, - required this.itemBuilder, - this.enterTransition, - this.exitTransition, - this.insertDuration, - this.removeDuration, - this.scrollDirection = Axis.vertical, - this.padding, - this.reverse = false, - this.controller, - this.primary, - this.physics, - this.scrollBehavior, - this.restorationId, - this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, - this.dragStartBehavior = DragStartBehavior.start, - this.clipBehavior = Clip.hardEdge, - this.insertItemBuilder, - this.removeItemBuilder}) - : super(key: key); + + /// A function that compares two items to determine whether they are the same. + final bool Function(E a, E b)? isSameItem; + + const AnimatedListView({ + Key? key, + required this.items, + required this.itemBuilder, + this.enterTransition, + this.exitTransition, + this.insertDuration, + this.removeDuration, + this.scrollDirection = Axis.vertical, + this.padding, + this.reverse = false, + this.controller, + this.primary, + this.physics, + this.scrollBehavior, + this.restorationId, + this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + this.dragStartBehavior = DragStartBehavior.start, + this.clipBehavior = Clip.hardEdge, + this.insertItemBuilder, + this.removeItemBuilder, + this.isSameItem, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -183,6 +188,7 @@ class AnimatedListView extends StatelessWidget { scrollDirection: scrollDirection, insertItemBuilder: insertItemBuilder, removeItemBuilder: removeItemBuilder, + isSameItem: isSameItem, ), ), ]); diff --git a/lib/src/animated_reorderable_gridview.dart b/lib/src/animated_reorderable_gridview.dart index bedf31f..ad8d9ea 100644 --- a/lib/src/animated_reorderable_gridview.dart +++ b/lib/src/animated_reorderable_gridview.dart @@ -179,6 +179,9 @@ class AnimatedReorderableGridView extends StatelessWidget { /// Whether the items can be dragged by long pressing on them. final bool longPressDraggable; + /// A function that compares two items to determine whether they are the same. + final bool Function(E a, E b)? isSameItem; + const AnimatedReorderableGridView({ Key? key, required this.items, @@ -206,6 +209,7 @@ class AnimatedReorderableGridView extends StatelessWidget { this.longPressDraggable = true, this.insertItemBuilder, this.removeItemBuilder, + this.isSameItem }) : super(key: key); @override @@ -240,6 +244,7 @@ class AnimatedReorderableGridView extends StatelessWidget { insertItemBuilder: insertItemBuilder, removeItemBuilder: removeItemBuilder, longPressDraggable: longPressDraggable, + isSameItem: isSameItem, ), ), ]); diff --git a/lib/src/animated_reorderable_listview.dart b/lib/src/animated_reorderable_listview.dart index 2c63d0a..01bdf8c 100644 --- a/lib/src/animated_reorderable_listview.dart +++ b/lib/src/animated_reorderable_listview.dart @@ -27,7 +27,7 @@ import 'builder/motion_list_impl.dart'; /// callback serves as a "proxy" (a substitute) for the item in the list. The proxy is /// created with the original list item as its child. -class AnimatedReorderableListView extends StatefulWidget { +class AnimatedReorderableListView extends StatelessWidget { /// The current list of items that this[AnimatedReorderableListView] should represent. final List items; @@ -188,8 +188,12 @@ class AnimatedReorderableListView extends StatefulWidget { /// transition for the widget that is built. final AnimatedWidgetBuilder? removeItemBuilder; + /// Whether the items can be dragged by long pressing on them. final bool longPressDraggable; + /// A function that compares two items to determine whether they are the same. + final bool Function(E a, E b)? isSameItem; + const AnimatedReorderableListView({ Key? key, required this.items, @@ -217,47 +221,42 @@ class AnimatedReorderableListView extends StatefulWidget { this.insertItemBuilder, this.removeItemBuilder, this.longPressDraggable = true, + this.isSameItem, }) : super(key: key); - @override - State> createState() => - _AnimatedReorderableListViewState(); -} - -class _AnimatedReorderableListViewState - extends State> { @override Widget build(BuildContext context) { return CustomScrollView( - scrollDirection: widget.scrollDirection, - reverse: widget.reverse, - controller: widget.controller, - primary: widget.primary, - physics: widget.physics, - scrollBehavior: widget.scrollBehavior, - restorationId: widget.restorationId, - keyboardDismissBehavior: widget.keyboardDismissBehavior, - dragStartBehavior: widget.dragStartBehavior, - clipBehavior: widget.clipBehavior, + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + scrollBehavior: scrollBehavior, + restorationId: restorationId, + keyboardDismissBehavior: keyboardDismissBehavior, + dragStartBehavior: dragStartBehavior, + clipBehavior: clipBehavior, slivers: [ SliverPadding( - padding: widget.padding ?? EdgeInsets.zero, + padding: padding ?? EdgeInsets.zero, sliver: MotionListImpl( - items: widget.items, - itemBuilder: widget.itemBuilder, - enterTransition: widget.enterTransition, - exitTransition: widget.exitTransition, - insertDuration: widget.insertDuration, - removeDuration: widget.removeDuration, - onReorder: widget.onReorder, - onReorderStart: widget.onReorderStart, - onReorderEnd: widget.onReorderEnd, - proxyDecorator: widget.proxyDecorator, - buildDefaultDragHandles: widget.buildDefaultDragHandles, - scrollDirection: widget.scrollDirection, - insertItemBuilder: widget.insertItemBuilder, - removeItemBuilder: widget.removeItemBuilder, - longPressDraggable: widget.longPressDraggable, + items: items, + itemBuilder: itemBuilder, + enterTransition: enterTransition, + exitTransition: exitTransition, + insertDuration: insertDuration, + removeDuration: removeDuration, + onReorder: onReorder, + onReorderStart: onReorderStart, + onReorderEnd: onReorderEnd, + proxyDecorator: proxyDecorator, + buildDefaultDragHandles: buildDefaultDragHandles, + scrollDirection: scrollDirection, + insertItemBuilder: insertItemBuilder, + removeItemBuilder: removeItemBuilder, + longPressDraggable: longPressDraggable, + isSameItem: isSameItem, ), ), ]); diff --git a/lib/src/builder/motion_list_base.dart b/lib/src/builder/motion_list_base.dart index 245e02f..6953950 100644 --- a/lib/src/builder/motion_list_base.dart +++ b/lib/src/builder/motion_list_base.dart @@ -32,6 +32,7 @@ abstract class MotionListBase final AnimatedWidgetBuilder? removeItemBuilder; final bool? buildDefaultDragHandles; final bool? longPressDraggable; + final bool Function(E a, E b)? isSameItem; const MotionListBase( {Key? key, @@ -50,7 +51,8 @@ abstract class MotionListBase this.insertItemBuilder, this.removeItemBuilder, this.buildDefaultDragHandles, - this.longPressDraggable}) + this.longPressDraggable, + this.isSameItem}) : super(key: key); } @@ -129,6 +131,10 @@ abstract class MotionListBaseState< @protected bool get longPressDraggable => widget.longPressDraggable ?? false; + @nonVirtual + @protected + bool Function(E a, E b) get isSameItem => widget.isSameItem ?? (a, b) => a == b; + @override void initState() { super.initState(); @@ -192,12 +198,18 @@ abstract class MotionListBaseState< void calculateDiff(List oldList, List newList) { // Detect removed and updated items for (int i = oldList.length - 1; i >= 0; i--) { + if(isSameItem(oldList[i], newList[i])) { + continue; + } if (!newList.contains(oldList[i])) { listKey.currentState!.removeItem(i, removeItemDuration: removeDuration); } } // Detect added items for (int i = 0; i < newList.length; i++) { + if(isSameItem(oldList[i], newList[i])) { + continue; + } if (!oldList.contains(newList[i])) { listKey.currentState!.insertItem(i, insertDuration: insertDuration); } diff --git a/lib/src/builder/motion_list_impl.dart b/lib/src/builder/motion_list_impl.dart index af7b9ce..1927d64 100644 --- a/lib/src/builder/motion_list_impl.dart +++ b/lib/src/builder/motion_list_impl.dart @@ -5,24 +5,25 @@ import 'motion_animated_builder.dart'; import 'motion_list_base.dart'; class MotionListImpl extends MotionListBase { - const MotionListImpl( - {Key? key, - required List items, - required ItemBuilder itemBuilder, - List? enterTransition, - List? exitTransition, - Duration? insertDuration, - Duration? removeDuration, - ReorderCallback? onReorder, - void Function(int)? onReorderStart, - void Function(int)? onReorderEnd, - ReorderItemProxyDecorator? proxyDecorator, - required Axis scrollDirection, - AnimatedWidgetBuilder? insertItemBuilder, - AnimatedWidgetBuilder? removeItemBuilder, - bool? buildDefaultDragHandles, - bool? longPressDraggable}) - : super( + const MotionListImpl({ + Key? key, + required List items, + required ItemBuilder itemBuilder, + List? enterTransition, + List? exitTransition, + Duration? insertDuration, + Duration? removeDuration, + ReorderCallback? onReorder, + void Function(int)? onReorderStart, + void Function(int)? onReorderEnd, + ReorderItemProxyDecorator? proxyDecorator, + required Axis scrollDirection, + AnimatedWidgetBuilder? insertItemBuilder, + AnimatedWidgetBuilder? removeItemBuilder, + bool? buildDefaultDragHandles, + bool? longPressDraggable, + bool Function(E a, E b)? isSameItem, + }) : super( key: key, items: items, itemBuilder: itemBuilder, @@ -38,7 +39,8 @@ class MotionListImpl extends MotionListBase { insertItemBuilder: insertItemBuilder, removeItemBuilder: removeItemBuilder, buildDefaultDragHandles: buildDefaultDragHandles, - longPressDraggable: longPressDraggable); + longPressDraggable: longPressDraggable, + isSameItem: isSameItem); const MotionListImpl.grid( {Key? key, @@ -57,7 +59,9 @@ class MotionListImpl extends MotionListBase { AnimatedWidgetBuilder? insertItemBuilder, AnimatedWidgetBuilder? removeItemBuilder, bool? buildDefaultDragHandles, - bool? longPressDraggable}) + bool? longPressDraggable, + bool Function(E a, E b)? isSameItem, + }) : super( key: key, items: items, @@ -75,7 +79,9 @@ class MotionListImpl extends MotionListBase { insertItemBuilder: insertItemBuilder, removeItemBuilder: removeItemBuilder, buildDefaultDragHandles: buildDefaultDragHandles, - longPressDraggable: longPressDraggable); + longPressDraggable: longPressDraggable, + isSameItem: isSameItem + ); @override MotionListImplState createState() => MotionListImplState();