在移动应用中,手势非常的重要,在Flutter中文网手势篇我们可以学到一些基本的知识,本篇文章带着大家写两个简单的手势的示例。
-
PanView
- 跟着手指移动的部件 (Transform.translate) -
DownRotationView
- 手指旋转锚点在底部的部件 (Transform.rotate)
1. 跟着手指移动的部件
我们在开发的过程中基本上是需要从Widget
层监听手势,所以直接使用GestureDetector
来搞定这个功能,先看一下GestureDetector
的大致源码:
GestureDetector({
...
this.onTapDown,
this.onTapUp,
this.onTap,
this.onTapCancel,
this.onSecondaryTapDown,
this.onSecondaryTapUp,
this.onSecondaryTapCancel,
this.onDoubleTap,
this.onLongPress,
this.onLongPressStart,
this.onLongPressMoveUpdate,
this.onLongPressUp,
this.onLongPressEnd,
this.onVerticalDragDown,
this.onVerticalDragStart,
this.onVerticalDragUpdate,
this.onVerticalDragEnd,
this.onVerticalDragCancel,
this.onHorizontalDragDown,
this.onHorizontalDragStart,
this.onHorizontalDragUpdate,
this.onHorizontalDragEnd,
this.onHorizontalDragCancel,
...
this.onPanDown,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
...
})
可以看到在GestureDetector
中系统提供了非常丰富的手势事件,比如onPanStart
(开始拖动),onPanUpdate
(拖动中),onPanEnd
(拖动结束),onDoubleTap
(双击),这些事件足以让我们完成一个View跟着手指来移动的功能,直接上源码:
class BoxPanView extends StatefulWidget {
@override
_BoxPanViewState createState() => _BoxPanViewState();
}
class _BoxPanViewState extends State<BoxPanView> with TickerProviderStateMixin {
double _offsetX = 0.0;
double _tmpX = 0.0;
double _offsetY = 0.0;
double _tmpY = 0.0;
Offset _tmpOffset = Offset.zero;
AnimationController _animationController;
Animation<double> _values;
@override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_values = Tween(begin: 1.0, end: 0.0).animate(_animationController);
_animationController.addListener(() {
setState(() {
_offsetX = _tmpX * _values.value;
_offsetY = _tmpY * _values.value;
});
});
}
@override
void dispose() {
_animationController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onDoubleTap: () {
if (!_animationController.isAnimating) {
_tmpX = _offsetX;
_tmpY = _offsetY;
_animationController.reset();
_animationController.forward();
}
},
onPanStart: (details) {
if (!_animationController.isAnimating) {
_tmpOffset = details.globalPosition;
_tmpX = _offsetX;
_tmpY = _offsetY;
}
},
onPanUpdate: (details) {
if (!_animationController.isAnimating) {
setState(() {
_offsetX = _tmpX + (details.globalPosition.dx - _tmpOffset.dx);
_offsetY = _tmpY + (details.globalPosition.dy - _tmpOffset.dy);
});
}
},
child: Container(
color: Colors.white,
child: Center(
child: Transform.translate(
offset: Offset(_offsetX, _offsetY),
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
),
),
),
);
}
}
在这个示例中,我们实现了Widget跟随手指移动和双击恢复初始位置的功能,看下运行效果,还是非常不错的:
2. 手指旋转锚点在底部的部件
这个手势是在后面的项目开发中需要使用到的一个手势,所以正好借着现在在学习Flutter中的手势,就尝试着开发一下:
class _BoxDownRotationViewState extends State<BoxDownRotationView> with TickerProviderStateMixin{
double _offsetX = 0.0;
double _tmpX = 0.0;
double _tmpY = 0.0;
Offset _tmpOffset = Offset.zero;
AnimationController _animationController;
Animation<double> _values;
@override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_values = Tween(begin: 1.0, end: 0.0).animate(_animationController);
_animationController.addListener(() {
setState(() {
_offsetX = _tmpX * _values.value;
});
});
}
@override
void dispose() {
_animationController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onDoubleTap: () {
if (!_animationController.isAnimating) {
_tmpX = _offsetX;
_animationController.reset();
_animationController.forward();
}
},
onHorizontalDragStart: (details) {
if (!_animationController.isAnimating) {
_tmpOffset = details.globalPosition;
_tmpX = _offsetX;
_tmpY=_tmpOffset.dy;
debugPrint('offsetY:$_tmpY');
}
},
onHorizontalDragUpdate: (details) {
if (!_animationController.isAnimating) {
setState(() {
_offsetX = _tmpX + (details.globalPosition.dx - _tmpOffset.dx);
debugPrint('offsetX:$_offsetX');
});
}
},
onHorizontalDragEnd: (details) {
if (!_animationController.isAnimating) {
_tmpX = _offsetX;
_animationController.reset();
_animationController.forward();
}
},
child: Container(
color: Colors.white,
child: Center(
child: Transform.rotate(
angle: _offsetX>0 ? acos((560.0-_tmpY)/sqrt((560.0-_tmpY)*(560.0-_tmpY)+_offsetX*_offsetX)) : -acos((560.0-_tmpY)/sqrt((560.0-_tmpY)*(560.0-_tmpY)+_offsetX*_offsetX)),
origin: Offset(0, 560),
child: Container(
width: 300,
height: 560,
decoration: BoxDecoration(
color: Colors.green,
border: Border.all(color: Colors.green, width: 1),
borderRadius: BorderRadius.all(//圆角
Radius.circular(20.0)
),
),
),
),
),
),
),
);
}
}
看下运行效果:
代码写的比较粗糙,只是简单地使用到了
GestureDetector
,也就是说在Transform.rotate
中锚点origin
和弧度angle
是非常重要的两个值,需要计算一个余弦值,来设置当前需要旋转的Widget的弧度,默认旋转中心点在部件的中心,我们需要将旋转的锚点origin
设置到部件的正下方某个点,这样手势操作出来效果会更好一点,看起来就像要旋转到屏幕外边了,代码中在松手后又回到了初始的位置,用了一个Animation
来实现的。文中所有的代码都可以在Github:BoxJ/Flutter-daydayup中下载,本篇代码的文件夹是
boxdemo_007
,欢迎一起交流!