本文所采用Flutter 版本为 3.10.6, 也许后续版本官方会对手势做进一步丰富完善,以解决本文涉及到的解决方案。
本文涉及项目手势需求:双指向外或内触屏伸缩,对图片进行缩放;双指在屏幕上同向触屏移动,移动图片。
Flutter 使用中采用GestureDetector进行移动或是手势判定,该手势检测器内置了移动,缩放,长按等动作检测。如果应用在缩放和移动上是单手势,该控件能够满足应用需求。但是如果是个复合手势,如手势可能是缩放也可能是移动,然后就可能会有冲突。
本文就是基于实际项目中遇到的需要双指缩放和双指移动并存手势,在GestureDetector基础上构建一个双手指并存手势控件
这是制作好后最终效果图:
应用商店搜索七彩涂色体验
动作 | 回调 | 冲突动作 |
---|---|---|
垂直移动 | onVerticalDrag* | 水平移动 |
水平移动 | onVerticalDrag* | 垂直移动 |
移动 | onPan* | 缩放 |
缩放 | onScale* | 移动 |
如上表所示,缩放和移动动作是相互冲突。如果同时指定了缩放回调和移动回调,就会遇到如下图的异常。
组合 | 效果 | 结论 |
---|---|---|
只移动 | 单双指均可行 | 单指头拖拽图片和手势跟随很好,但是双指拖拽移动增量超过双指移动 |
缩放 + 移动 | 直接异常 | 查看源码,无法共存,抛出异常 |
缩放 + 水平移动或垂直移动 | 不冲突,可移动 | 缩放或移动内部自行断定,效果很好;但是只能水平移动或垂直移动 |
缩放 + 水平移动+垂直移动 | 直接异常 | 查看源码,水平移动和垂直移动无法共存,抛出异常 |
从上述测试表中可以看到要实现本文的双指缩放和移动共存,采用GestureDetector 不可实现。
缩放示意图
缩放开始时,触点的中心位置基本保持不变。中心点相对不变则,但是触点距离变大,则是缩放动作。
移动示意图
移动开始时,触点之间相对距离不变,但是与起始点有较大位移。中心点位移,触摸点相对位置不变则是移动。
手势流程
2. 与**GestureDetector** 结合本文中采用在GestureDetector 基础上实现双指缩放或移动,经过GestureDetector 源码查阅,决定在缩放回调基础上进行手指判断。因为缩放手势不仅对Scale 比例 进行了计算和回传,而且对触摸点位置也进行了回传。可以利用Scale 和缩放点判断,触摸点相对增加进行移动判断,减少工作量。
GestureDetector(
onScaleStart: (details) {
if (details.pointerCount >= 2) {
/// 清空手势判断类型参数
offset = Offset.zero;
scaleAccumulatedX = 0;
scaleAccumulatedY = 0;
twoTouchMode = TwoTuchMode.twoTouchNoneMode;
}
},
onScaleUpdate: (details) {
if (details.pointerCount >= 2) {
if (twoTouchMode == TwoTuchMode.twoTouchNoneMode) {
twoTouchMode = checkTwoTouchesMoveMode(details);
}
if (twoTouchMode == TwoTuchMode.twoTouchZoomMode) {
debugPrint("details.scale:${details.scale}");
scale = details.scale;
setState(() {});
} else if (twoTouchMode == TwoTuchMode.twoTouchMoveMode) {
offset += details.focalPointDelta;
setState(() {});
}
}
},
上述代码在接收scaleUpdate 时,如果还没有识别到手势,则先调用 checkTwoTouchesMoveMode 进行判断。已经识别到手势,就按已识别的手势进行缩放处理。
/// 判断手势类型是缩放还是移动
/// 缩放类型判断阀值
double constScaleThreshold = 0.1;
/// 移动类型判断阀值
double constPanningThreshold = 20;
/// 类型判断,移动累计值
double scaleAccumulatedX = 0;
double scaleAccumulatedY = 0;
TwoTuchMode checkTwoTouchesMoveMode(ScaleUpdateDetails touches) {
if ((1 - touches.scale).abs() >= constScaleThreshold) {
debugPrint("two touch scale mode:${(1 - touches.scale).abs()}");
return TwoTuchMode.twoTouchZoomMode;
}
scaleAccumulatedX += touches.focalPointDelta.dx;
scaleAccumulatedY += touches.focalPointDelta.dy;
if (scaleAccumulatedX.abs() >= constPanningThreshold ||
scaleAccumulatedY.abs() >= constPanningThreshold) {
debugPrint(
"two touch move mode dx:${scaleAccumulatedX.abs()} , dy:${scaleAccumulatedY.abs()}");
return TwoTuchMode.twoTouchMoveMode;
}
return TwoTuchMode.twoTouchNoneMode;
}
checkTwoTouchesMoveMode 进行缩放或是移动动作判断,缩放判断优先,如果超过阀值constScaleThreshold识别为缩放手势;横行或是纵向移动距离超过阀值 constPanningThreshold 识别为移动手势。
测试下来操作性还可以,代码在GestureDetector 基础上构建,也不复杂。
代码没有进行过封装,与使用该缩放功能的代码混合编码,如果要跨项目使用,不便于功能更迭。
本例代码在本文中全部打包提供。
环境:Flutter 3.10.6
示例第一个截图,可以在应用商店搜索七彩涂色体验,由Flutter 构建的涂色应用。
Flutter 双指缩放和移动手势检测系列之–2封装 敬请期待