效果图,为啥GIF不动呢不看功夫,先看看效果:
实现以上效果,主要是通过GestureDetector检测用户手势来处理的。通过Transform.translation调整X坐标进行处理,原理十分简单。
大致代码内容如下:
2023-06-05
增加滑动后自动关闭的动画效果
import 'package:flutter/material.dart';
class SlideMenuView extends StatefulWidget {
const SlideMenuView(
{super.key,
this.maxDragDistance = 80.0,
required this.child,
this.background,
required this.onDragStart});
final double maxDragDistance;
final Widget child;
final Widget? background;
final void Function() onDragStart;
@override
State<SlideMenuView> createState() => SlideMenuViewState();
}
class SlideMenuViewState extends State<SlideMenuView>
with TickerProviderStateMixin {
double _dragDistance = 0;
late AnimationController _animController;
Animation<double>? _animation;
@override
void initState() {
_animController = AnimationController(
duration: const Duration(milliseconds: 200), vsync: this)
..addListener(() {
if (_animation == null) return;
Future.microtask(
() => setState(() => _dragDistance = _animation!.value));
debugPrint("===> 动画运行中:${_animation!.value}");
});
super.initState();
}
void _updateDragDataAndUI(double moveDistance) {
debugPrint("===> 拖动进行事件");
_dragDistance += moveDistance;
if (_dragDistance < -widget.maxDragDistance) {
_dragDistance = -widget.maxDragDistance;
} else if (_dragDistance >= 0.0) {
_dragDistance = 0.0;
}
Future.microtask(() => setState(() {}));
}
void _dragCompleted() {
debugPrint("===> 拖动完成事件");
_animController.reset(); //动画重置
if (_dragDistance < -widget.maxDragDistance / 2.0) {
_animation = _animController
.drive(Tween(begin: _dragDistance, end: -widget.maxDragDistance));
} else if (_dragDistance > -widget.maxDragDistance / 2.0) {
_animation = _animController.drive(Tween(begin: _dragDistance, end: 0.0));
}
Future.microtask(_animController.forward);
}
void closeSlideMenu() => setState(() => _dragDistance = 0);
@override
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragDown: (_) => widget.onDragStart(),
onHorizontalDragStart: (_) => widget.onDragStart(),
onHorizontalDragUpdate: (details) =>
_updateDragDataAndUI(details.delta.dx),
onHorizontalDragCancel: _dragCompleted,
onHorizontalDragEnd: (_) => _dragCompleted(),
child: Stack(children: [
if (widget.background != null) widget.background!,
Transform.translate(
offset: Offset(_dragDistance, 0), child: widget.child)
]));
}
}
extension SlideMenuViewExtensions on Widget {
/// 应用侧滑菜单的效果
Widget applySlideMenuEffect(
{Key? key,
double maxSlideDistance = 80.0,
Widget? background,
required void Function() onDragStart}) {
return SlideMenuView(
key: key,
maxDragDistance: maxSlideDistance,
onDragStart: onDragStart,
background: background,
child: this);
}
}
2023-05-26
import 'package:flutter/material.dart';
class SlideMenuView extends StatefulWidget {
const SlideMenuView(
{super.key,
this.maxDragDistance = 80.0,
required this.child,
this.background,
required this.onDragStart});
final double maxDragDistance;
final Widget child;
final Widget? background;
final void Function() onDragStart;
@override
State<SlideMenuView> createState() => SlideMenuViewState();
}
class SlideMenuViewState extends State<SlideMenuView> {
double _dragDistance = 0;
void _updateDragDataAndUI(double moveDistance) {
_dragDistance += moveDistance;
if (_dragDistance <= -widget.maxDragDistance) {
_dragDistance = -widget.maxDragDistance;
} else if (_dragDistance >= 0.0) {
_dragDistance = 0.0;
}
Future.microtask(() => setState(() {}));
}
void _dragCompleted() {
if (_dragDistance < -widget.maxDragDistance / 2.0) {
_dragDistance = -widget.maxDragDistance;
} else if (_dragDistance > -widget.maxDragDistance / 2.0) {
_dragDistance = 0.0;
}
Future.microtask(() => setState(() {}));
}
void closeSlideMenu() => setState(() => _dragDistance = 0);
@override
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragDown: (_) => widget.onDragStart(),
onHorizontalDragStart: (_) => widget.onDragStart(),
onHorizontalDragUpdate: (details) =>
_updateDragDataAndUI(details.delta.dx),
onHorizontalDragCancel: _dragCompleted,
onHorizontalDragEnd: (_) => _dragCompleted(),
child: Stack(children: [
if (widget.background != null) widget.background!,
Transform.translate(
offset: Offset(_dragDistance, 0), child: widget.child)
]));
}
}
/// 提供扩展类,让Widget直接使用视图
extension SlideMenuViewExtensions on Widget {
Widget applySlideMenuEffect(
{Key? key,
double maxSlideDistance = 80.0,
Widget? background,
required void Function() onDragStart}) {
return SlideMenuView(
key: key,
maxDragDistance: maxSlideDistance,
onDragStart: onDragStart,
background: background,
child: this);
}
}
应用案例
Container(
height: 80,
width: double.infinity,
color: Colors.red,
child: Text(widget.title))
.applySlideMenuEffect(
onDragStart: () {},
background: Container(
width: double.infinity,
alignment: Alignment.centerRight,
child: const Text('删除')))
关闭菜单通过调用GloablKey来调用 closeSlideMenu方法。
*原创内容,转载和分享请注明作者或出处!
网友评论