Flutter滑动操作插件mm_swipeable的使用

Flutter滑动操作插件mm_swipeable的使用

截图

MmSwipeable 是一个 Flutter 包,它提供了一个小部件来启用左右方向的滑动手势。

特性

  • 允许用户向左或向右滑动子小部件。
  • 不会自动取消子小部件的滑动;相反,触发回调函数来处理滑动手势。
  • 可以自定义滑动手势确认条件。
  • 支持通过专用控制器程序化控制滑动手势。

使用方法

将你希望实现滑动手势的小部件包裹在 MmSwipeable 小部件中,并提供必要的回调函数来处理滑动手势:

final swipeableController = MmSwipeableController();

MmSwipeable(
  controller: swipeableController,
  swipeAnimationDuration: const Duration(milliseconds: 2000),
  resetAnimationDuration: const Duration(milliseconds: 1000),
  actionOffsetDuration: const Duration(milliseconds: 200),
  confirmSwipe: () {
    // 读取控制器中的当前角度和力值。
    final angle = swipeableController.value.angle.abs();
    final force = swipeableController.value.force.abs();
    // 检查滑动手势是否满足某些条件。
    if (angle <= 0.5 && force <= 0.5) {
      // 滑动手势不够强烈,忽略此动作。
      return null;
    } else {
      // 滑动手势满足条件,可以确认或取消。
      // 进一步检查或逻辑处理...
    }
  },
  onSwipedLeft: () {
    // 处理向左滑动的动作。
  },
  onSwipedRight: () {
    // 处理向右滑动的动作。
  },
  onSwipeLeftCancelled: () {
    // 处理向左滑动被取消。
  },
  onSwipeRightCancelled: () {
    // 处理向右滑动被取消。
  },
  child: Container(
    height: 600,
    color: Colors.blue,
  ),
)

你还可以使用 MmSwipeableController 来程序化地控制滑动手势小部件的行为:

// 程序化触发向右滑动手势
swipeController.swipeRight();

// 程序化触发向左滑动手势
swipeController.swipeLeft();

更多详情和示例,请参阅 示例目录

许可证

该项目采用 MIT 许可证 - 详情请参阅 许可证文件


示例代码

import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mm_swipeable/mm_swipeable.dart';

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

class Cat {
  final int id;
  final String name;
  final String about;
  final String imageUrl;

  const Cat({
    required this.id,
    required this.name,
    required this.about,
    required this.imageUrl,
  });

  factory Cat.random1() {
    return Cat(
      id: Random().nextInt(10000),
      name: 'Fluffy',
      about: 'Cute and adorable',
      imageUrl:
          'https://images.pexels.com/photos/2071882/pexels-photo-2071882.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
    );
  }

  factory Cat.random2() {
    return Cat(
      id: Random().nextInt(10000),
      name: 'Whiskers',
      about: 'Loves to play',
      imageUrl:
          'https://images.pexels.com/photos/208984/pexels-photo-208984.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
    );
  }

  factory Cat.random3() {
    return Cat(
      id: Random().nextInt(10000),
      name: 'Mittens',
      about: 'Very friendly',
      imageUrl:
          'https://images.pexels.com/photos/1521304/pexels-photo-1521304.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
    );
  }
}

class CatProvider extends ChangeNotifier {
  final Map<Cat, MmSwipeableController> catsAndControllers = {};

  void initCats() {
    for (var i = 0; i < 20; i++) {
      catsAndControllers[Cat.random1()] = MmSwipeableController();
      catsAndControllers[Cat.random2()] = MmSwipeableController();
      catsAndControllers[Cat.random3()] = MmSwipeableController();
    }
    notifyListeners();
  }

  void removeCat(Cat cat) {
    catsAndControllers.remove(cat);
    notifyListeners();
  }

  [@override](/user/override)
  void dispose() {
    for (final controller in catsAndControllers.values) {
      controller.dispose();
    }
    super.dispose();
  }
}

final catProvider = CatProvider()..initCats();

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'MmSwipeable Demo',
      home: HomePage(),
    );
  }
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        centerTitle: true,
        title: Image.asset(
          'assets/makromusic_logo_with_text.png',
          height: 30,
        ),
      ),
      body: ListenableBuilder(
        listenable: catProvider,
        builder: (context, _) {
          final catsAndControllers = catProvider.catsAndControllers;
          if (catsAndControllers.isEmpty) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
          final length = catsAndControllers.length;
          final bottom = catsAndControllers.entries.elementAt(length - 2);
          final top = catsAndControllers.entries.elementAt(length - 1);

          return Container(
            margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
            child: Column(
              children: [
                Expanded(
                  child: Stack(
                    children: [
                      CatCard(
                        key: ValueKey(bottom.key.id),
                        controller: bottom.value,
                        cat: bottom.key,
                      ),
                      CatCard(
                        key: ValueKey(top.key.id),
                        controller: top.value,
                        cat: top.key,
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 20),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: [
                    Expanded(
                      child: GestureDetector(
                        onTap: () {
                          catsAndControllers.values.last.swipeLeft();
                        },
                        child: Container(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 16,
                            vertical: 8,
                          ),
                          decoration: const BoxDecoration(
                            borderRadius: BorderRadius.only(
                              topLeft: Radius.circular(24),
                              bottomLeft: Radius.circular(24),
                              topRight: Radius.circular(6),
                              bottomRight: Radius.circular(6),
                            ),
                            color: Color(0xFF0F141E),
                          ),
                          child: const Text(
                            'Nope',
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              fontSize: 24,
                              fontWeight: FontWeight.bold,
                              color: Colors.white,
                            ),
                          ),
                        ),
                      ),
                    ),
                    const SizedBox(width: 12),
                    Expanded(
                      child: GestureDetector(
                        onTap: () {
                          catsAndControllers.values.last.swipeRight();
                        },
                        child: Container(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 16,
                            vertical: 8,
                          ),
                          decoration: const BoxDecoration(
                            borderRadius: BorderRadius.only(
                              topLeft: Radius.circular(6),
                              bottomLeft: Radius.circular(6),
                              topRight: Radius.circular(24),
                              bottomRight: Radius.circular(24),
                            ),
                            color: Color(0xFF42C0C6),
                          ),
                          child: const Text(
                            'Like',
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              fontSize: 24,
                              fontWeight: FontWeight.bold,
                              color: Colors.white,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ],
                )
              ],
            ),
          );
        },
      ),
    );
  }
}

class CatCard extends StatefulWidget {
  final MmSwipeableController controller;
  final Cat cat;

  const CatCard({
    super.key,
    required this.controller,
    required this.cat,
  });

  [@override](/user/override)
  State<CatCard> createState() => _CatCardState();
}

class _CatCardState extends State<CatCard> {
  double leftTextOpacity = 0;
  double rightTextOpacity = 0;

  [@override](/user/override)
  void initState() {
    widget.controller.addListener(updateOpacity);
    super.initState();
  }

  [@override](/user/override)
  void dispose() {
    widget.controller.removeListener(updateOpacity);
    super.dispose();
  }

  void updateOpacity() {
    final angle = widget.controller.value.angle;
    setState(() {
      leftTextOpacity = clampDouble(-angle, 0, 1);
      rightTextOpacity = clampDouble(angle, 0, 1);
    });
  }

  void remove(Cat matchData) {
    catProvider.removeCat(matchData);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: catProvider,
      builder: (context, _) {
        return MmSwipeable(
          controller: widget.controller,
          actionOffsetDuration: const Duration(milliseconds: 250),
          swipeAnimationDuration: const Duration(milliseconds: 2000),
          resetAnimationDuration: const Duration(milliseconds: 1200),
          confirmSwipe: () {
            final value = widget.controller.value;
            final angle = value.angle.abs();
            final force = value.force.abs();
            if (angle <= 0.7 && force <= 0.3) {
              return null;
            }
            return true;
          },
          onSwipedRight: () {
            remove(widget.cat);
            // 执行任何你想在这里做的事情
          },
          onSwipedLeft: () {
            remove(widget.cat);
            // 执行任何你想在这里做的事情
          },
          onSwipeLeftCancelled: () {
            // 执行任何你想在这里做的事情
          },
          onSwipeRightCancelled: () {
            // 执行任何你想在这里做的事情
          },
          child: Stack(
            children: [
              ClipRRect(
                borderRadius: BorderRadius.circular(16),
                child: SizedBox(
                  height: MediaQuery.of(context).size.height * .8,
                  child: Image.network(
                    widget.cat.imageUrl,
                    fit: BoxFit.cover,
                  ),
                ),
              ),
              Positioned.fill(
                child: Container(
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(16),
                    gradient: LinearGradient(
                      begin: Alignment.bottomCenter,
                      end: Alignment.topCenter,
                      colors: [
                        Colors.black.withOpacity(0.8),
                        Colors.transparent,
                      ],
                    ),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 16.0,
                      vertical: 24,
                    ),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: [
                        Row(
                          children: [
                            ClipRRect(
                              borderRadius: BorderRadius.circular(100),
                              child: SizedBox(
                                height: 50,
                                width: 50,
                                child: Image.network(
                                  widget.cat.imageUrl,
                                  fit: BoxFit.cover,
                                ),
                              ),
                            ),
                            const SizedBox(width: 12),
                            Text(
                              widget.cat.name,
                              style: const TextStyle(
                                color: Colors.white,
                                fontSize: 24,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 8),
                        Text(
                          widget.cat.about,
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 16,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                  vertical: 12.0,
                  horizontal: 24,
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    AnimatedOpacity(
                      opacity: rightTextOpacity,
                      duration: Duration.zero,
                      child: Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 16,
                          vertical: 8,
                        ),
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(8),
                          color: const Color(0xFF42C0C6),
                        ),
                        child: const Text(
                          'Like',
                          style: TextStyle(
                            fontSize: 24,
                            fontWeight: FontWeight.bold,
                            color: Colors.white,
                          ),
                        ),
                      ),
                    ),
                    AnimatedOpacity(
                      opacity: leftTextOpacity,
                      duration: Duration.zero,
                      child: Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 16,
                          vertical: 8,
                        ),
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(8),
                          color: const Color(0xFF0F141E),
                        ),
                        child: const Text(
                          'Nope',
                          style: TextStyle(
                            fontSize: 24,
                            fontWeight: FontWeight.bold,
                            color: Colors.white,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

更多关于Flutter滑动操作插件mm_swipeable的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter滑动操作插件mm_swipeable的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用mm_swipeable插件来实现滑动操作的代码示例。mm_swipeable是一个流行的Flutter插件,它允许你在列表项或其他小部件上实现左右滑动操作,比如删除或标记为已读。

首先,你需要在你的pubspec.yaml文件中添加mm_swipeable依赖:

dependencies:
  flutter:
    sdk: flutter
  mm_swipeable: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来获取依赖。

接下来是一个简单的示例,展示如何使用mm_swipeable来创建一个可以左右滑动的列表项:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Swipeable List Example'),
        ),
        body: SwipeableListExample(),
      ),
    );
  }
}

class SwipeableListExample extends StatefulWidget {
  @override
  _SwipeableListExampleState createState() => _SwipeableListExampleState();
}

class _SwipeableListExampleState extends State<SwipeableListExample> {
  final List<String> items = List<String>.generate(20, (i) => "Item $i");

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        return Swipeable(
          key: ValueKey('item_${items[index]}'),
          startAction: () {
            // 左滑动作
            print("Left swipe action on ${items[index]}");
            // 例如:删除项
            setState(() {
              items.removeAt(index);
            });
          },
          endAction: () {
            // 右滑动作
            print("Right swipe action on ${items[index]}");
            // 例如:标记为已读
            // 这里只是打印信息,你可以添加实际逻辑
          },
          child: ListTile(
            title: Text(items[index]),
          ),
        );
      },
    );
  }
}

在这个示例中:

  1. 我们首先定义了一个包含20个字符串项的列表。
  2. 使用ListView.builder来构建列表项。
  3. 每个列表项都被包裹在Swipeable小部件中。
  4. Swipeable小部件的startAction属性定义了左滑时的动作(例如删除项),而endAction属性定义了右滑时的动作(例如标记为已读)。
  5. 使用setState来更新列表项,当左滑删除项时,列表会重新构建以反映更改。

你可以根据需要自定义startActionendAction中的逻辑,以及child属性中的小部件,以满足你的应用需求。

回到顶部