最近项目中有个需求,widget要能够移动、旋转、点击,代码实现如下。
class GestureDetectorDemo extends StatefulWidget {
@override
_GestureDetectorDemoState createState() => _GestureDetectorDemoState();
}
class _GestureDetectorDemoState extends State<GestureDetectorDemo> {
bool _show = true;
@override
Widget build(BuildContext context) {
// final control = TransformationController();
// control.
return _buildPanel();
}
//静止状态下的offset
Offset _idleOffset = Offset(0, 0);
//本次移动的offset
Offset _moveOffset = Offset(0, 0);
//最后一次down事件的offset
Offset _lastStartOffset = Offset(0, 0);
double _previousScale = 1;
double _scale = 1;
double _itemWidth = 150;
double _itemHeight = 150;
Widget _buildPanel() {
return Transform.scale(
scale: _scale,
alignment: Alignment(0, 0),
child: Transform.translate(
offset: _moveOffset,
child: GestureDetector(
onScaleStart: _onScaleStart,
onScaleUpdate: _onScaleUpdate,
onScaleEnd: _onScaleEnd,
onTap: _onTap,
child: Container(
color: Colors.red,
width: _itemWidth,
height: _itemHeight,
)),
),
);
}
void _onTap(){
print('_onTap');
}
void _onScaleStart(ScaleStartDetails details) {
setState(() {
_lastStartOffset = details.focalPoint;
print('onScaleStart $_lastStartOffset');
Offset local = details.localFocalPoint;
});
}
void _onScaleUpdate(ScaleUpdateDetails details) {
setState(() {
// 缩放
_scale = _previousScale * details.scale;
// 移动
_moveOffset =
(details.focalPoint - _lastStartOffset + _idleOffset) / _scale;
// 控制移动边界
double left =
(_itemWidth * _scale - _itemWidth) / 2.0 / _scale - _itemWidth / 2;
double top = ((_itemHeight * _scale - _itemHeight) / 2.0) / _scale -
_itemHeight / 2;
_moveOffset = Offset(max(left, _moveOffset.dx), max(top, _moveOffset.dy));
double right = (SizeFit.screenWidth -
_itemWidth -
(_itemWidth * _scale - _itemWidth) / 2.0) /
_scale +
_itemWidth / 2;
double bottom = (SizeFit.screenHeight -
_itemHeight -
(_itemHeight * _scale - _itemHeight) / 2.0 -
LayoutUtil.getAppBarHeight(context)) /
_scale +
_itemHeight / 4;
_moveOffset =
Offset(min(right, _moveOffset.dx), min(bottom, _moveOffset.dy));
});
}
void _onScaleEnd(ScaleEndDetails details) {
print('onScaleEnd');
setState(() {
_idleOffset = _moveOffset * _scale;
_previousScale = _scale;
});
}
}
基本功能都使用没什么问题,但是测试中发现在进行缩放的时候总是延迟,手势执行一定时间后widget才开始缩放。
造成这种情况的原因应该是手势冲突,解决的思路是用Listener处理tap事件,GestureDetector只处理缩放和移动事件。代码如下
class GestureDetectorDemo extends StatefulWidget {
@override
_GestureDetectorDemoState createState() => _GestureDetectorDemoState();
}
class _GestureDetectorDemoState extends State<GestureDetectorDemo> {
bool _show = true;
@override
Widget build(BuildContext context) {
// final control = TransformationController();
// control.
return _buildPanel();
}
//静止状态下的offset
Offset _idleOffset = Offset(0, 0);
//本次移动的offset
Offset _moveOffset = Offset(0, 0);
//最后一次down事件的offset
Offset _lastStartOffset = Offset(0, 0);
double _previousScale = 1;
double _scale = 1;
double _itemWidth = 150;
double _itemHeight = 150;
Duration _tapDownTimeStamp;
Widget _buildPanel() {
return Transform.scale(
scale: _scale,
alignment: Alignment(0, 0),
child: Transform.translate(
offset: _moveOffset,
child: Listener(
onPointerDown: (PointerDownEvent event) {
// 记录点击时间
_tapDownTimeStamp = event.timeStamp;
},
onPointerUp: (PointerUpEvent event) {
// 手指抬起时,计算时间差,100ms以内算点击事件
int interval = event.timeStamp.inMilliseconds -
_tapDownTimeStamp.inMilliseconds;
if (interval <= 100) {
// 处理tap事件
_onTap();
}
},
child: GestureDetector(
onScaleStart: _onScaleStart,
onScaleUpdate: _onScaleUpdate,
onScaleEnd: _onScaleEnd,
child: Container(
color: Colors.red,
width: _itemWidth,
height: _itemHeight,
)),
),
),
);
}
void _onTap() {
print('_onTap');
}
void _onScaleStart(ScaleStartDetails details) {
setState(() {
_lastStartOffset = details.focalPoint;
print('onScaleStart $_lastStartOffset');
Offset local = details.localFocalPoint;
});
}
void _onScaleUpdate(ScaleUpdateDetails details) {
setState(() {
// 缩放
_scale = _previousScale * details.scale;
// 移动
_moveOffset =
(details.focalPoint - _lastStartOffset + _idleOffset) / _scale;
// 控制移动边界
double left =
(_itemWidth * _scale - _itemWidth) / 2.0 / _scale - _itemWidth / 2;
double top = ((_itemHeight * _scale - _itemHeight) / 2.0) / _scale -
_itemHeight / 2;
_moveOffset = Offset(max(left, _moveOffset.dx), max(top, _moveOffset.dy));
double right = (SizeFit.screenWidth -
_itemWidth -
(_itemWidth * _scale - _itemWidth) / 2.0) /
_scale +
_itemWidth / 2;
double bottom = (SizeFit.screenHeight -
_itemHeight -
(_itemHeight * _scale - _itemHeight) / 2.0 -
LayoutUtil.getAppBarHeight(context)) /
_scale +
_itemHeight / 4;
_moveOffset =
Offset(min(right, _moveOffset.dx), min(bottom, _moveOffset.dy));
});
}
void _onScaleEnd(ScaleEndDetails details) {
print('onScaleEnd');
setState(() {
_idleOffset = _moveOffset * _scale;
_previousScale = _scale;
});
}
}