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]),
),
);
},
);
}
}
在这个示例中:
- 我们首先定义了一个包含20个字符串项的列表。
- 使用
ListView.builder
来构建列表项。 - 每个列表项都被包裹在
Swipeable
小部件中。 Swipeable
小部件的startAction
属性定义了左滑时的动作(例如删除项),而endAction
属性定义了右滑时的动作(例如标记为已读)。- 使用
setState
来更新列表项,当左滑删除项时,列表会重新构建以反映更改。
你可以根据需要自定义startAction
和endAction
中的逻辑,以及child
属性中的小部件,以满足你的应用需求。