Flutter开发-屏蔽Widget的多点触控行为
前言
在Flutter中我们在Widget
实现一些手势交互通常会使用GestureDetector
装饰器来实现,但是默认情况下,widget
是支持多点触控,但是在一些特定需求下,我们不需要多点触控的操作,或者多点触控反而给一些功能带来麻烦,而且比较不方便的是,多点触控无法通过操作widget
或者GestureDetector
来屏蔽掉。查阅了官方文档发现的这个玩意:RawGestureDetector
RawGestureDetector class
A widget that detects gestures described by the given gesture factories.
For common gestures, use a GestureRecognizer. RawGestureDetector is useful primarily when developing your own gesture recognizers.
大概意思是:一个小部件,用于检测由给定手势工厂描述的手势。对于常用手势,请使用GestureRecognizer。 RawGestureDetector主要在开发自己的手势识别器时很有用。
例如:
RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(),
(TapGestureRecognizer instance) {
instance
..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
..onTap = () { setState(() { _last = 'tap'; }); }
..onTapCancel = () { setState(() { _last = 'cancel'; }); };
},
),
},
child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
)
我们可以通过RawGestureDetector来自定义手势。
开始
有时,你可能需要禁用多点触摸或在Flutter应用程序中点击小部件。 例如,有一个列表,并且一次只能单击其中一项。 您不希望用户同时用三个手指点击或触摸并立即选择三个项。基本上,您要防止用户多次点击或多点触摸。
我们先创建一个简单的页面,页面加载一个ListView.builder()
,
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class MyList extends StatelessWidget {
List<String> myList = [
"January",
"February",
"March",
"April",
"June",
"July",
"August"
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ListView.builder(
shrinkWrap: true,
itemCount: myList.length,
physics: ClampingScrollPhysics(),
itemBuilder: (BuildContext cxtListBuilder, int itemIdx) {
return ListTile(
title: Text(myList[itemIdx]),
onTap: () => print('tapped'),
);
})));
}
}
这个列表上的cell都支持多点触控,效果图:
【图】
创建自定义手势识别器
Flutter允许在GestureRecognizer
基类的帮助下创建自定义手势识别器小部件。 该类已经有两个抽象的实现,可以实现多次轻击和单次轻击手势。
- OneSequenceGestureRecognizer
-
MultiTapGestureRecognizer
我们要实现点击手势,所以这里我们基于OneSequenceGestureRecognizer
创建一个子类。
单点触摸的实现
首先创建一个自定义窗口小部件,以使其子窗口小部件只能具有单一触摸手势。
class SingleTouchRecognizerWidget extends StatelessWidget {
final Widget child;
SingleTouchRecognizerWidget({this.child});
@override
Widget build(BuildContext context) {
// TODO: implement build
return null;
}
}
在build()
方法中,我们将返回仅支持单点触摸手势的手势检测器小部件。 因此,我们为此创建另一个名为_SingleTouchRecognizer
的类
class _SingleTouchRecognizer extends OneSequenceGestureRecognizer {
@override
// TODO: implement debugDescription
String get debugDescription => null;
@override
void didStopTrackingLastPointer(int pointer) {
// TODO: implement didStopTrackingLastPointer
}
@override
void handleEvent(PointerEvent event) {
// TODO: implement handleEvent
}
}
现在我们回到我们的SingleTouchRecognizerWidget
中,通过RawGestureDetector
装饰器来定义我们刚才创建的单击手势检测器:
class SingleTouchRecognizerWidget extends StatelessWidget {
final Widget child;
SingleTouchRecognizerWidget({this.child});
@override
Widget build(BuildContext context) {
return RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
_SingleTouchRecognizer: GestureRecognizerFactoryWithHandlers<_SingleTouchRecognizer>(
() => _SingleTouchRecognizer(),
(_SingleTouchRecognizer instance) {},
),
},
child: child,
);
}
}
现在,build()
方法返回了一个RawGestureDetector
,该RawGestureDetector
处理_SingleTouchRecognizer
类中的手势。接下来,我们需要在识别器类中实现这些方法。我们首先覆盖GestureRecognizer
的addAllowedPointer
方法。
class _SingleTouchRecognizer extends OneSequenceGestureRecognizer {
int _p = 0;
@override
void addAllowedPointer(PointerDownEvent event) {
//first register the current pointer so that related events will be handled by this recognizer
startTrackingPointer(event.pointer);
//ignore event if another event is already in progress
if (_p == 0) {
resolve(GestureDisposition.rejected);
_p = event.pointer;
} else {
resolve(GestureDisposition.accepted);
}
}
...
在这里,startTrackingPointer
方法注册了将由识别器处理的相关事件。 然后,resolve()
负责确保是否允许继续进行触摸事件。
解析参数
如果我们传入GestureDisposition.rejected
,则当前的触摸事件无法处理。 因此,此触摸事件将被传递并允许其继续。 但是,如果传递了GestureDisposition.accepted
,则将解析触摸事件,并且不会再调用其他事件。
通过handleEvent函数重置控制变量_p的值。
...
@override
void handleEvent(PointerEvent event) {
if (!event.down && event.pointer == _p) {
_p = 0;
}
}
...
这样就完成了_SingleTouchRecognizer类的实现。
现在,只需要将该Widget
包裹在想要支持单点触控的widget
外层即可。
应用
...
child: SingleTouchRecognizerWidget(
child: ListView.builder(
...
参考文献:
disable-multi-touch-on-a-widget-in-flutter
api.flutter.dev