An Awesome UI Package incluing IOS Style Cell Swipe Action Effect In Flutter

Get started 

3.0.0 and later version is for flutter 3 

pub home page click here: pub

install:

flutter_swipe_action_cell: ^3.1.2

1.Preview: 

Simple deletePerform first action when full swipe
  
Delete with animationMore than one action
  
Effect like WeChat(confirm delete)Automatically adjust the button width
  
Effect like WeChat collection Page: Customize your button shape
With leading Action and trailing action
 
Edit mode
 

Full example: 

Preview (YouTobe video)

And you can find full example code in example page

Examples 

Example 1: Simple delete the item in ListView 

Tip: put the code in the itemBuilder of your ListView

 SwipeActionCell(
      key: ObjectKey(list[index]), /// this key is necessary
      trailingActions: <SwipeAction>[
        SwipeAction(
            title: "delete",
            onTap: (CompletionHandler handler) async {
              list.removeAt(index);
              setState(() {});
            },
            color: Colors.red),
      ],
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text("this is index of ${list[index]}",
            style: TextStyle(fontSize: 40)),
      ),
    );

Example 2: Perform first action when full swipe 

SwipeActionCell(
      /// this key is necessary
      key: ObjectKey(list[index]),
      trailingActions: <SwipeAction>[
        SwipeAction(
            /// this is the same as iOS native
            performsFirstActionWithFullSwipe: true,
            title: "delete",
            onTap: (CompletionHandler handler) async {
              list.removeAt(index);
              setState(() {});
            },
            color: Colors.red),
      ],
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text("this is index of ${list[index]}",
            style: TextStyle(fontSize: 40)),
      ),
    );

Example 3: Delete with animation 

SwipeActionCell(
     key: ObjectKey(list[index]),
     trailingActions: <SwipeAction>[
       SwipeAction(
           title: "delete",
           onTap: (CompletionHandler handler) async {
             
             /// await handler(true) : will delete this row
             /// And after delete animation,setState will called to 
             /// sync your data source with your UI

             await handler(true);
             list.removeAt(index);
             setState(() {});
           },
           color: Colors.red),
     ],
     child: Padding(
       padding: const EdgeInsets.all(8.0),
       child: Text("this is index of ${list[index]}",
           style: TextStyle(fontSize: 40)),
     ),
   );

Example 4: More than one action: 

SwipeActionCell(
     key: ObjectKey(list[index]),
     trailingActions: <SwipeAction>[
       SwipeAction(
           title: "delete",
           onTap: (CompletionHandler handler) async {
             await handler(true);
             list.removeAt(index);
             setState(() {});
           },
           color: Colors.red),

       SwipeAction(
           widthSpace: 120,
           title: "popAlert",
           onTap: (CompletionHandler handler) async {
             /// false means that you just do nothing,it will close
             /// action buttons by default
             handler(false);
             showCupertinoDialog(
                 context: context,
                 builder: (c) {
                   return CupertinoAlertDialog(
                     title: Text('ok'),
                     actions: <Widget>[
                       CupertinoDialogAction(
                         child: Text('confirm'),
                         isDestructiveAction: true,
                         onPressed: () {
                           Navigator.pop(context);
                         },
                       ),
                     ],
                   );
                 });
           },
           color: Colors.orange),
     ],
     child: Padding(
       padding: const EdgeInsets.all(8.0),
       child: Text(
           "this is index of ${list[index]}",
           style: TextStyle(fontSize: 40)),
     ),
   );

Example 5:Delete like WeChat message page(need to confirm it: 

return SwipeActionCell(
      key: ValueKey(list[index]),
      trailingActions: <SwipeAction>[
        SwipeAction(
          ///
          /// This attr should be passed to first action
          ///
          nestedAction: SwipeNestedAction(title: "确认删除"),
          title: "删除",
          onTap: (CompletionHandler handler) async {
            await handler(true);
            list.removeAt(index);
            setState(() {});
          },
          color: Colors.red,
        ),
        SwipeAction(
            title: "置顶",
            onTap: (CompletionHandler handler) async {
              /// false means that you just do nothing,it will close
              /// action buttons by default
              handler(false);
            },
            color: Colors.grey),
      ],
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text("this is index of ${list[index]}",
            style: TextStyle(fontSize: 40)),
      ),
    );

Example 6:Edit mode(just like iOS native effect) 

/// To controller edit mode
SwipeActionEditController controller;

/// 在initState
@override
  void initState() {
    super.initState();
    controller = SwipeActionController();
  }
/// To get the selected rows index
List<int> selectedIndexes = controller.getSelectedIndexes();


/// open cell
controller.openCellAt(index: 2, trailing: true, animated: true);

/// close cell
controller.closeAllOpenCell();

/// toggleEditingMode
controller.toggleEditingMode()

/// startEditMode
controller.startEditingMode()

/// stopEditMode
controller.stopEditingMode()

/// select cell
controller.selectCellAt(indexPaths:[1,2,3])

controller.deselectCellAt(indexPaths:[1,2,3])

/// pass your data length to selectedAll
controller.selectAll(30
)

/// deselect all cell
controller deselectAll()

ListView.builder(
        itemBuilder: (c, index) {
          return _item(index);
        },
        itemCount: list.length,
      );


 Widget _item(int index) {
     return SwipeActionCell(
       /// controller
       controller: controller,
       /// index is required if you want to enter edit mode
       index: index,
       key: ValueKey(list[index]),
       trailingActions: [
         SwipeAction(
             /// this is the same as iOS native
             performsFirstActionWithFullSwipe: true,
             onTap: (handler) async {
               await handler(true);
               list.removeAt(index);
               setState(() {});
             },
             title: "delete"),
       ],
       child: Padding(
         padding: const EdgeInsets.all(15.0),
         child: Text("This is index of ${list[index]}",
             style: TextStyle(fontSize: 35)),
       ),
     );
   }

Example 7:customize shape 


Widget _item(int index) {
    return SwipeActionCell(
      key: ValueKey(list[index]),
      trailingActions: [
        SwipeAction(
            nestedAction: SwipeNestedAction(
              /// customize your nested action content
              content: Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(30),
                  color: Colors.red,
                ),
                width: 130,
                height: 60,
                child: OverflowBox(
                  maxWidth: double.infinity,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(
                        Icons.delete,
                        color: Colors.white,
                      ),
                      Text('确认删除',
                          style: TextStyle(color: Colors.white, fontSize: 20)),
                    ],
                  ),
                ),
              ),
            ),

            /// you should set the default  bg color to transparent
            color: Colors.transparent,

            /// set content instead of title of icon
            content: _getIconButton(Colors.red, Icons.delete),
            onTap: (handler) async {
              list.removeAt(index);
              setState(() {});
            }),
        SwipeAction(
            content: _getIconButton(Colors.grey, Icons.vertical_align_top),
            color: Colors.transparent,
            onTap: (handler) {}),
      ],
      child: Padding(
        padding: const EdgeInsets.all(15.0),
        child: Text(
            "This is index of ${list[index]},Awesome Swipe Action Cell!! I like it very much!",
            style: TextStyle(fontSize: 25)),
      ),
    );
  }

  Widget _getIconButton(color, icon) {
    return Container(
      width: 50,
      height: 50,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(25),

        /// set you real bg color in your content
        color: color,
      ),
      child: Icon(
        icon,
        color: Colors.white,
      ),
    );
  }

Example 8:Close opening cell when navigator change its routes. 

  • Add a SwipeActionNavigatorObserver in MaterialApp's navigatorObservers
return MaterialApp(
  navigatorObservers: [SwipeActionNavigatorObserver()],
  ....
);

About CompletionHandler in onTap function of SwipeAction 

it means how you want control this cell after you tap it. If you don't want any animation, just don't call it and update your data and UI with setState()

If you want some animation:

handler(true) : Means this row will be deleted(You should call setState after it)

await handler(true) : Means that you will await the animation to complete(you should call setState after it so that you will get an animation)

handler(false) : means it will not delete this row.By default, it just close this cell's action buttons.

await handler(false) : means it will wait the close animation to complete.

About all parameter: 

I wrote them in my code with dart doc comments. You can read them in source code.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_swipe_action_cell

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_swipe_action_cell: ^3.1.2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_swipe_action_cell/flutter_swipe_action_cell.dart';

example/lib/main.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_swipe_action_cell/flutter_swipe_action_cell.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      /// Add this SwipeActionNavigatorObserver to close opening cell when navigator changes its routes
      /// 添加这个可以在路由切换的时候统一关闭打开的cell,全局有效
      navigatorObservers: [SwipeActionNavigatorObserver()],
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const HomePage(),
    );
  }
}

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

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: CupertinoButton.filled(
            child: const Text('Enter new page'),
            onPressed: () {
              Navigator.push(context,
                  CupertinoPageRoute(builder: (c) => const SwipeActionPage()));
            }),
      ),
    );
  }
}

class Model {
  String id = UniqueKey().toString();
  int index = 0;

  @override
  String toString() {
    return index.toString();
  }
}

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

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

class _SwipeActionPageState extends State<SwipeActionPage> {
  List<Model> list = List.generate(30, (index) {
    return Model()..index = index;
  });

  late SwipeActionController controller;

  @override
  void initState() {
    super.initState();
    controller = SwipeActionController(selectedIndexPathsChangeCallback:
        (changedIndexPaths, selected, currentCount) {
      print(
          'cell at ${changedIndexPaths.toString()} is/are ${selected ? 'selected' : 'unselected'} ,current selected count is $currentCount');

      /// I just call setState() to update simply in this example.
      /// But the whole page will be rebuilt.
      /// So when you are developing,you'd better update a little piece
      /// of UI sub tree for best performance....

      setState(() {});
    });
  }

  Widget bottomBar() {
    return Container(
      color: Colors.grey[200],
      padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          children: [
            Expanded(
              child: CupertinoButton.filled(
                  padding: const EdgeInsets.only(),
                  child: const Text('open cell at 2'),
                  onPressed: () {
                    controller.openCellAt(
                        index: 2, trailing: true, animated: true);
                  }),
            ),
            const SizedBox(
              width: 10,
            ),
            Expanded(
              child: CupertinoButton.filled(
                  padding: const EdgeInsets.only(),
                  child: const Text('switch edit mode'),
                  onPressed: () {
                    controller.toggleEditingMode();
                  }),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: bottomBar(),
      appBar: CupertinoNavigationBar(
        middle: CupertinoButton.filled(
            padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
            minSize: 0,
            child: const Text('deselect all', style: TextStyle(fontSize: 22)),
            onPressed: () {
              controller.deselectAll();
            }),
        leading: CupertinoButton.filled(
            padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10),
            minSize: 0,
            child: Text(
                'delete cells (${controller.getSelectedIndexPaths().length})',
                style: const TextStyle(color: Colors.white)),
            onPressed: () {
              /// 获取选取的索引集合
              List<int> selectedIndexes = controller.getSelectedIndexPaths();

              List<String> idList = [];
              for (var element in selectedIndexes) {
                idList.add(list[element].id);
              }

              /// 遍历id集合,并且在原来的list中删除这些id所对应的数据
              for (var itemId in idList) {
                list.removeWhere((element) {
                  return element.id == itemId;
                });
              }

              /// 更新内部数据,这句话一定要写哦
              controller.deleteCellAt(indexPaths: selectedIndexes);
              setState(() {});
            }),
        trailing: CupertinoButton.filled(
            minSize: 0,
            padding: const EdgeInsets.all(10),
            child: const Text('select all'),
            onPressed: () {
              controller.selectAll(dataLength: list.length);
            }),
      ),
      body: ListView.builder(
        physics: const BouncingScrollPhysics(),
        itemCount: list.length,
        itemBuilder: (context, index) {
          return _item(context, index);
        },
      ),
    );
  }

  Widget _item(BuildContext ctx, int index) {
    return SwipeActionCell(
      controller: controller,
      index: index,

      // Required!
      key: ValueKey(list[index]),

      // Animation default value below
      // deleteAnimationDuration: 400,
      selectedForegroundColor: Colors.black.withAlpha(30),
      trailingActions: [
        SwipeAction(
            title: "delete",
            performsFirstActionWithFullSwipe: true,
            nestedAction: SwipeNestedAction(title: "confirm"),
            onTap: (handler) async {
              await handler(true);

              list.removeAt(index);
              setState(() {});
            }),
        SwipeAction(title: "action2", color: Colors.grey, onTap: (handler) {}),
      ],
      leadingActions: [
        SwipeAction(
            title: "delete",
            onTap: (handler) async {
              await handler(true);
              list.removeAt(index);
              setState(() {});
            }),
        SwipeAction(
            title: "action3", color: Colors.orange, onTap: (handler) {}),
      ],
      child: GestureDetector(
        onTap: () {
          Navigator.push(
              context, CupertinoPageRoute(builder: (ctx) => const HomePage()));
        },
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Text("This is index of ${list[index]}",
              style: const TextStyle(fontSize: 30)),
        ),
      ),
    );
  }
}

Download details:

Author: luckysmg

Source: https://github.com/luckysmg/flutter_swipe_action_cell

#flutter #android #ios 

An Awesome UI Package incluing IOS Style Cell Swipe Action Effect In Flutter
1.05 GEEK