flutter中和点击事件相关的分为两大类事件:指针和手势。
一、指针
描述了屏幕上由触摸板、鼠标、指示笔等触发指针的位置和移动。源码如下:
常用的指针属性:
- onPointerDown :指针在特定位置与屏幕接触
- onPointerMove:指针从屏幕的一个位置移动到另外一个位置
- onPointerUp:指针与屏幕停止接触
- onPointerCancel:指针的输入已经不再指向此应用
- behavior:命中行为,描述哪些区域可以点击。
- child:子组件
1、简单使用
class PointApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('指针事件'),
),
body: Listener(
child: Container(
width: 300,
height: 300,
color: Colors.red,
),
onPointerDown: (event) {
print(event.toString());
},
onPointerUp: (event) {
print(event.toString());
},
onPointerMove: (event) {
print(event.toString());
},
),
);
}
}
打印结果如下:
分别对应PointerDownEvent与PointerUpEvent。
2、behavior使用
注意代码中不能设置背景颜色。
(1) 看如下代码:
class PointApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('指针事件'),
),
body: Listener(
child: Container(
width: 300,
height: 300,
child: Center(
child: Text('命中我了'),
),
),
onPointerDown: (event) {
print(event.toString());
},
),
);
}
}
测试效果:点击文本以外的区域没有效果,其他区域都是透明区域
(2) HitTestBehavior.opaque
class PointApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('指针事件'),
),
body: Listener(
child: Container(
width: 300,
height: 300,
child: Container(
child: Text('命中我了'),
alignment: Alignment.center,
width: 200,
height: 200,
),
),
onPointerDown: (event) {
print(event.toString());
},
//触摸行为
behavior: HitTestBehavior.opaque,
),
);
}
}
测试效果:整个点击区域都有效果了。
HitTestBehavior.opaque:在命中测试时,将当前组件当不透明处理(即使本身透明)。
(3) HitTestBehavior.translucent
没有加该行为的测试代码:
class PointApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('指针事件'),
),
body: Stack(
children: <Widget>[
Listener(
child: Container(
width: 300,
height: 300,
color: Colors.red,
),
onPointerUp: (event) {
print('外层事件');
},
),
Listener(
child: Container(
width: 200,
height: 200,
child: Center(
child: Text('命中我了'),
),
),
onPointerUp: (event) {
print('内层事件');
},
)
],
),
);
}
}
测试效果:
在内部Listener中添加HitTestBehavior.translucent。代码省略。效果如下:
HitTestBehavior.opaque:当点击组件透明区域时,可以对自身边界内及底部可视区域都进行命中测试。
3、忽略指针事件
- AbsorbPointer:阻止子组件接收指针事件,本身参与命中测试。
- IgnorePointer:阻止子组件接收指针事件,本身也不参与命中测试。
(1) AbsorbPointer
代码如下:
class PointApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('指针事件'),
),
body: Listener(
child: AbsorbPointer(
child: Listener(
child: Container(
width: 300,
height: 300,
color: Colors.red,
),
onPointerDown: (event) {
print('内部事件');
},
)),
onPointerDown: (event) {
print('外部事件');
},
));
}
}
效果如下:只打印外部事件,内部事件不打印。
(2) IgnorePointer
将上述代码AbsorbPointer改为IgnorePointer。其他不变,再次点击区域发现什么打印都没有。都不参与命中测试。
二、手势
手势事件分为两类:
- GestureDetector:用于手势识别的功能性组件,可以识别各种手势,是指针事件的语义化封装,内部使用一个或多个GestureRecognizer。
- GestureRecognizer:通过Listener将原始指针事件转化为语义手势
1、GestureDetector
(1) 属性:
常用的属性如下:
- 点击:onTapDown、onTapUp、onTap、onTapCancel
- 双击:onDoubleTap
- 长按:onLongPress
- 纵向拖动:onVerticalDragStart、onVerticalDragUpdate、onVerticalDragEnd
- 横向拖动:onHorizontalDragStart、onHorizontalDragUpdate、onHorizontalDragEnd
- 移动:onPanStart、onPanUpdate、onPanEnd。
(2) 简单使用:
class GestureWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('GestureWidget'),
),
body: GestureDetector(
child: Container(
width: 300,
height: 300,
color: Colors.red,
),
onTap: () {
print('单击事件');
},
onDoubleTap: () {
print('双击事件');
},
onLongPress: () {
print('长按事件');
},
),
);
}
}
(3) 拖动使用:
class GestureState extends State<GestureWidget> {
double _top;
double _left;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('GestureState'),
),
body: GestureDetector(
child: Stack(
children: <Widget>[
Positioned(
left: _left,
top: _top,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),
],
),
onPanUpdate: (details) {
setState(() {
_left += details.delta.dx;
_top += details.delta.dy;
});
},
),
);
}
}
效果如下:
2、GestureRecognizer
常见就是TextSpan中的使用:
class GestureState extends State<GestureWidget> {
TapGestureRecognizer _recognizer = new TapGestureRecognizer();
@override
void dispose() {
super.dispose();
//解绑,占用资源
_recognizer.dispose();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('GestureState'),
),
body: Center(
child: Text.rich(TextSpan(children: [
TextSpan(text: '链接:'),
TextSpan(
text: 'www.baidu.com',
style: TextStyle(color: Colors.blue),
recognizer: _recognizer
..onTap = () {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return Scaffold(
appBar: AppBar(title: Text('recognizer跳转而来')),
);
}));
}),
])),
));
}
}
效果如下: 点击链接跳转到下一界面。
三、手势消歧
- 在屏幕指定的位置上,可能有多个手势捕捉器。所有的手势捕捉器坚挺了指针输入流事件并判断出特定手势。
- 在任何时候,识别器都可以宣告失败离开竞技场。如果竞技场中只有一个识别器,那么这个识别器就是胜者。
- 在任何时候,任何识别器都可以宣告胜利,这将导致这个识别器胜出,其他识别器失败。
比如下面一段代码:
class GestureState extends State<GestureWidget> {
double _left;
double _top;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('GestureState'),
),
body: Center(
child: GestureDetector(
child: Stack(
children: <Widget>[
Positioned(
left: _left,
top: _top,
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
)
],
),
onPanUpdate: (details) {
print('移动事件》》》');
},
onTap: () {
print('点击事件》》》');
},
),
));
}
}
同时设置两个回调方法onPanUpdate和onTap。但是实际发现onTap最终不会触发回调。这是因为onPanUpdate事件就是胜出的识别器。那么如何同时触发两个回调呢?就要回归原始指针事件了。修改成如下代码就行了:
class GestureState extends State<GestureWidget> {
double _left;
double _top;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('GestureState'),
),
body: Listener(
onPointerDown: (event){
print('onPointerDown事件》》》');
},
child: GestureDetector(
child: Stack(
children: <Widget>[
Positioned(
left: _left,
top: _top,
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
)
],
),
onPanUpdate: (details) {
print('移动事件》》》');
},
/* onTap: () {
print('点击事件》》》');
},*/
),
));
}
}
外层套一个Listener,并使用onPointerDown回调替换onTap事件。