我写这个系列的目的,可能更倾向于自我总结,而不是教Flutter入门,所以可能有些东西是在我本人的理解下的一些用法与理解,不一定准确与适合所有开发者,如果有其他更好的想法,可以与我讨论,联系方式见关于
本项目基于macOS桌面程序,源码地址:https://github.com/NeverOvO/learningfoundation
-NeverOuO
GestureDetecotr属性
GestureDetector({
Key? key,
this.child,
this.onTapDown,
this.onTapUp,
this.onTap,
this.onTapCancel,
this.onSecondaryTap,
this.onSecondaryTapDown,
this.onSecondaryTapUp,
this.onSecondaryTapCancel,
this.onTertiaryTapDown,
this.onTertiaryTapUp,
this.onTertiaryTapCancel,
this.onDoubleTapDown,
this.onDoubleTap,
this.onDoubleTapCancel,
this.onLongPressDown,
this.onLongPressCancel,
this.onLongPress,
this.onLongPressStart,
this.onLongPressMoveUpdate,
this.onLongPressUp,
this.onLongPressEnd,
this.onSecondaryLongPressDown,
this.onSecondaryLongPressCancel,
this.onSecondaryLongPress,
this.onSecondaryLongPressStart,
this.onSecondaryLongPressMoveUpdate,
this.onSecondaryLongPressUp,
this.onSecondaryLongPressEnd,
this.onTertiaryLongPressDown,
this.onTertiaryLongPressCancel,
this.onTertiaryLongPress,
this.onTertiaryLongPressStart,
this.onTertiaryLongPressMoveUpdate,
this.onTertiaryLongPressUp,
this.onTertiaryLongPressEnd,
this.onVerticalDragDown,
this.onVerticalDragStart,
this.onVerticalDragUpdate,
this.onVerticalDragEnd,
this.onVerticalDragCancel,
this.onHorizontalDragDown,
this.onHorizontalDragStart,
this.onHorizontalDragUpdate,
this.onHorizontalDragEnd,
this.onHorizontalDragCancel,
this.onForcePressStart,
this.onForcePressPeak,
this.onForcePressUpdate,
this.onForcePressEnd,
this.onPanDown,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
this.behavior,
this.excludeFromSemantics = false,
this.dragStartBehavior = DragStartBehavior.start,
}) : assert(excludeFromSemantics != null),
assert(dragStartBehavior != null),
assert(() {
final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null;
final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null;
final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
if (havePan || haveScale) {
if (havePan && haveScale) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Incorrect GestureDetector arguments.'),
ErrorDescription(
'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.',
),
ErrorHint('Just use the scale gesture recognizer.'),
]);
}
final String recognizer = havePan ? 'pan' : 'scale';
if (haveVerticalDrag && haveHorizontalDrag) {
throw FlutterError(
'Incorrect GestureDetector arguments.\n'
'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.',
);
}
}
return true;
}()),
super(key: key);
个人常用用法:隐藏键盘
包裹在存在TextEditingController页面的最外层,这样点击空白区域就能把键盘缩小回去:
behavior: HitTestBehavior.opaque,
onTap: (){
FocusScope.of(context).requestFocus(FocusNode());
},
this.child
可以是任何Widget,此处不做展开。
this.behavior
/// How to behave during hit tests.
enum HitTestBehavior {
/// Targets that defer to their children receive events within their bounds
/// only if one of their children is hit by the hit test.
deferToChild,
/// Opaque targets can be hit by hit tests, causing them to both receive
/// events within their bounds and prevent targets visually behind them from
/// also receiving events.
opaque,
/// Translucent targets both receive events within their bounds and permit
/// targets visually behind them to also receive events.
translucent,
}
用来控制GestureDetector内子组件与空白空间是否接受触控的逻辑
一般让GestureDetector的所有范围都可以被点击,采用opaque较多。
onTap相关
onTap: (){
print("onTap");
},
onTapUp: (e){
print("onTapUp");
print(e.globalPosition);
},
onTapDown: (e){
print("onTapDown");
print(e.globalPosition);
},
onTapCancel: (){
print("onTapCancel");
},
onTap
这个是GestureDetector中最常用的方法,指用户点击一次且没有滑动取消点击后的触发方法。
onTapDown
单击按下后即触发
onTapDown: (e){
print("onTapDown");
print(e.globalPosition);
},
这里的e为:TapDownDetails details
TapDownDetails({
this.globalPosition = Offset.zero,
Offset? localPosition,
this.kind,
}) : assert(globalPosition != null),
localPosition = localPosition ?? globalPosition;
this.globalPosition : 指针与屏幕接触的全局位置。
this.kind : 引发事件的设备类型。
localPosition : 指针与屏幕接触的本地位置。
这里可以用来记录用户常用点击位置与点击起始位置。
onTapUp
单击且没有移动取消,放开手指后触发。
onTapUp: (e){
print("onTapUp");
print(e.globalPosition);
},
e同onTapDown。
这里可以用来记录用户点击结束位置。
onTapCancel
单击取消,一般在点击后不放开手指拖动到其他位置可以触发
onTapCancel: (){
print("onTapCancel");
},
这里可以收集用户取消点击的频率与场景,优化UI引导点击。
onSecondaryTap
这个其实就是右键,具体逻辑与onTap系列相同,不作详细展开。
onTertiaryTap
这里为鼠标第三键即鼠标中键。但是我在测试中感觉触发不是太稳定,由于几乎不会使用,之后再补充吧。
onDoubleTap
双击触发
onDoubleTap: (){
print("onDoubleTap");
},
onDoubleTapDown: (e){
print("onDoubleTapDown");
print(e.globalPosition);
},
onDoubleTapCancel: (){
print("onDoubleTapCancel");
},
onDoubleTapDown
双击只有Down触发,没有Up,这里需要注意。
另外在某些较为卡顿或极端条件下,可能会一起触发单击方法,所以需要注意。
onLongPress
长按触发
长按方法的触发逻辑链为:onLongPressDown -> onLongPressStart -> onLongPress -> onLongPressMoveUpdate -> onLongPressEnd -> onLongPressUp
onLongPress: (){
print("onLongPress");
},
onLongPressDown: (e){
print("onLongPressDown");
},
onLongPressUp: (){
print("onLongPressUp");
},
onLongPressStart: (e){
print("onLongPressStart");
},
onLongPressMoveUpdate: (e){
print("onLongPressMoveUpdate");
},
onLongPressEnd: (e){
print("onLongPressEnd");
},
onLongPressCancel: (){
print("onLongPressCancel");
},
onLongPress
最为常用的长按触发方法
onLongPress: (){
print("onLongPress");
},
onLongPressDown
长按时,首先触发的方法,用来获取长按的起点
onLongPressDown: (e){
print("onLongPressDown");
},
这里的e为:LongPressDownDetails details
const LongPressDownDetails({
this.globalPosition = Offset.zero,
Offset? localPosition,
this.kind,
}) : assert(globalPosition != null),
localPosition = localPosition ?? globalPosition;
用法基本与onTap的TapDownDetails details相似
onLongPressStart
长按后,触发的方法,在onLongPressStart后就会对onLongPress方法进行触发。
onLongPressMoveUpdate
长按中,用户拖动手指时会进行触发,来判定获取用户手指、鼠标的长按位置变更。
onLongPressCancel
长按中,当用户手指或鼠标移动到屏幕或指定位置外时会触发取消动作。
onSecondaryLongPress/onTertiaryLongPress
与长按类似,这里为右键长按与中键长按的方法。
onVerticalDrag类
垂直拖拽方法,触发顺序为:onVerticalDragDown -> onVerticalDragStart -> onVerticalDragUpdate -> onVerticalDragEnd
onVerticalDragDown: (e){
print(e.localPosition);
print("onVerticalDragDown");
},
onVerticalDragStart: (e){
print("onVerticalDragStart");
},
onVerticalDragUpdate: (e){
print("onVerticalDragUpdate");
},
onVerticalDragEnd: (e){
print("onVerticalDragEnd");
},
onVerticalDragCancel: (){
print("onVerticalDragCancel");
},
onVerticalDragDown
垂直拖拽时首先触发的方法,用来获取垂直拖拽的起点
onVerticalDragDown: (e){
print(e.localPosition);
print("onVerticalDragDown");
},
这里的e为:DragDownDetails details
DragDownDetails({
this.globalPosition = Offset.zero,
Offset? localPosition,
}) : assert(globalPosition != null),
localPosition = localPosition ?? globalPosition;
相比点击类方法,少了kind属性。
onVerticalDragCancel
垂直拖拽取消时触发方法,一般为用户拖动鼠标或手指离开屏幕或Widget范围外时。
onHorizontalDrag类
水平拖动时触发的方法,用法与onVerticalDrag类相似
onHorizontalDragDown: (e){
print("onHorizontalDragDown");
},
onHorizontalDragStart: (e){
print("onHorizontalDragStart");
},
onHorizontalDragUpdate: (e){
print("onHorizontalDragStart");
},
onHorizontalDragEnd: (e){
print("onHorizontalDragEnd");
},
onHorizontalDragCancel: (){
},
onPan类
onPan类基本就是onHorizontalDrag类与onVerticalDrag类之和。
onPanDown: (e){
print("onPanDown");
},
onPanStart: (e){
print("onPanStart");
},
onPanUpdate: (e){
print("onPanUpdate");
},
onPanEnd: (e){
print("onPanEnd");
},
onPanCancel: (){
},
onForcePress类
用力按压时能触发的方法,需要一定的设备支持
onForcePressPeak: (e){
print("onForcePressPeak");
},
onForcePressStart: (e){
print("onForcePressStart");
},
onForcePressUpdate: (e){
print("onForcePressUpdate");
},
onForcePressEnd: (e){
print("onForcePressEnd");
},
这里的e为:ForcePressDetails details
ForcePressDetails({
required this.globalPosition,
Offset? localPosition,
required this.pressure,
}) : assert(globalPosition != null),
assert(pressure != null),
localPosition = localPosition ?? globalPosition;
this.pressure : 按在屏幕上的压力
由于场景和设备的限制,不做展开后续有相应设备后补充。
右键点击的个人用法:右键菜单
//右键
onSecondaryTap: (){
showMenu(context: context, position: RelativeRect.fromLTRB(_x!, _y!, _x!, _y!), items: [
PopupMenuItem(child: Text('option1'), value: 'btn1',),
PopupMenuItem(child: Text('option2'), value: 'btn2',),
PopupMenuItem(child: Text('option3'), value: 'btn3',),
]).then((value){
print(value);
});
},
onSecondaryTapDown: (details){
setState(() {
_x = details.globalPosition.dx;
_y = details.globalPosition.dy;
});
},
这串代码可以在组件范围内点击右键触发一个菜单。
部分桌面端,对于鼠标的拖拽失效处理
class MyCustomScrollBehavior extends MaterialScrollBehavior {
// Override behavior methods and getters like dragDevices
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
// etc.
};
}
ScrollConfiguration(
behavior: MyCustomScrollBehavior(),
Comments NOTHING