文章目录
前言
在上一篇《双指缩放和双指移动共存手势系列–1方案》中通过直接在GestureDetector 中加入手势判断代码来实现是双指缩放或是双指触屏移动控件。上一篇也提到个问题,就是该代码与功能代码高度耦合,不利于是多项目之间共享。
本篇讲把该部分代码进行封装,作为一个 package 独立存在。
一、封装效果
如上图所示,左侧是封装后的控件,右侧是GestureDetector 控件。左侧控件在使用上保持了 GestureDetector 的一致性。新控件GestureDetectorTwoFingersScaleMov多了一个controller, 该controller 主要是用于存储手势区分数据。
二、GestureDetector 深入了解
1.GestureDetector对象结构

2.手势回调

3. Gesture Detector 代码片段
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context);
...
if (onPanDown != null ||
onPanStart != null ||
onPanUpdate != null ||
onPanEnd != null ||
onPanCancel != null) {
gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => PanGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
(PanGestureRecognizer instance) {
instance
..onDown = onPanDown
..onStart = onPanStart
..onUpdate = onPanUpdate
..onEnd = onPanEnd
..onCancel = onPanCancel
..dragStartBehavior = dragStartBehavior
..gestureSettings = gestureSettings
..supportedDevices = supportedDevices;
},
);
}
if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
() => ScaleGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
(ScaleGestureRecognizer instance) {
instance
..onStart = onScaleStart
..onUpdate = onScaleUpdate
..onEnd = onScaleEnd
..dragStartBehavior = dragStartBehavior
..gestureSettings = gestureSettings
..trackpadScrollCausesScale = trackpadScrollCausesScale
..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor
..supportedDevices = supportedDevices;
},
);
}
...
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
三、封装
目标:封装目标是将双指缩放和双指移动共存手势作为一个新的手势控件,该控件为一个独立的插件,并且通过单独的插件工程进行维护。
1. 处理流程
分析GestureDetector 源码后,决定自行构建一个GestureDetector, 将 ScaleGestureRecognizer 回调设定内部回调(其他类型的GestureRecognizer 直接回调),先判断Gesture 类型,再行回调上层控件注册的回调函数。流程图如下:

2. 代码结构

对象 | 说明 |
---|---|
UserView | 上层使用手势控件 |
GestureDetectorTwoFingersScaleMov | 封装的支持双指缩放和双指移动共存手势,继承至 GestureDetector。没有完全重新写一个Detector 是因为GestureDetector 完成了资源管理,释放等相关操作,减少封装工作量。 |
TwoFingerScaleMoveController | 用于判断是缩放还是移动的数据存储控制器,该对象需要在上层代码中声明,并传给新的GestureDetector。因为GestureDetectorTwoFingersScaleMov 支持双指缩放移动以外的其他Gesture 上层控件可能会因为手势或是其他场景需要刷新页面,造成Detector重构。判定缩放的数据是一个累计量,不能因为Detector 重构而丢失数据。首次封装测试中,该controller 就是置于内部,从而出现缩放或是移动过程中,不断闪烁,就是应为其被不断重构。 |
TwoFingerScaleMoveGesture | 双指缩放和双指移动共存手势判定。ScaleGestureRecognizer 的回调,将会调用到该对象进行手势判定,再由该判定Gesture 调用上层注册的双指缩放或是双指移动回调 |
3. 回调变更
接口 | GestureDetector | GestureDetectorTwoFingersScaleMov | 说明 |
---|---|---|---|
缩放开始 | onScaleStart | onScaleMovStart | |
缩放中 | onScaleUpdate | onScaleMovUpdate | onScaleMovUpdate 回调时会返回缩放或是移动 Gesture类型,就算没有识别到具体类型,也会回调,只是Gesture 类型为Unknown |
缩放结束 | onScaleEnd | onScaleMovEnd |
四、使用
调用代码
Widget createWidget() {
return GestureDetectorTwoFingersScaleMov(
controller: twoFingerScaleMoveController,
// onScaleMoveStart
onScaleMovStart: (details) {
initScale = scale;
},
// scale or move update
onScaleMovUpdate: (details) {
// 0 unrecorgnize mode,1 zoom update, 2 moving update.
if (details.type == TwoTuchMode.twoTouchZoomMode) {
// once recorgnized as zoom or mov gesture this value is same until onScaleMovEnd
scale = initScale * details.scale;
debugPrint("details.scale:${details.scale}");
setState(() {});
} else if (details.type == TwoTuchMode.twoTouchMoveMode) {
offset += details.focalPointDelta;
setState(() {});
}
},
// scale or move end
onScaleMovEnd: (details) {
debugPrint("onScaleMovEnd");
},
child: SizedBox.fromSize(
size: MediaQuery.of(context).size,
child: Stack(children: [
const Text(
'Two fingers zoom or move',
),
Positioned(
top: offset.dy,
left: offset.dx,
child: Transform.scale(
key: _nodeKey,
scale: scale,
child: Image.asset("images/flutter.png"))),
])));
}
上述代码中 onScaleMovUpdate 回调 details.type 会指定是未知,或是缩放或是移动。一旦认定为缩放或是移动,直到触控结束。
注意 在触控结束手指离开屏幕时,因为有先后顺序,所以会出现一个手指触控信息。新控件此时将details.type 设置回 unknown, 避免双指触控中途变成单指引起数据混乱。
效果

劣势
不同版本Flutter 中因为Gesture 基类有差异,封装也要进行适当更新,需要针对不同版本进行适配。总体看,利大于弊。
本文源码
系列
上一篇《双指缩放和双指移动共存手势系列–1方案》
下一篇 《双指缩放和双指移动共存手势系列–3发布》发布到pub.dev 稍后奉上