Flutter手势命中拦截插件flutter_gesture_hit_intercept的使用
Flutter手势命中拦截插件flutter_gesture_hit_intercept的使用
flutter_gesture_hit_intercept
Flutter 实现命中拦截, 用于解决手势冲突, 阻止其它小部件获取手势事件。
使用场景: 比如在ListView
中, 有一个tile
想要获取手势事件, 同时又想要ListView
阻止滚动, 这时候就可以使用这个库。
安装
安装最新版本:
flutter pub add flutter_gesture_hit_intercept
快速开始
导入对应的包, 使用 GestureHitInterceptScope
小部件包裹 ListView
, 然后在需要拦截手势的RenderBox
上使用 GestureHitInterceptScope.of(context)?.interceptHitBox = this
方法即可。
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: GestureHitInterceptScope(
child: ListView(
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
children: [
const GestureTestWidget(), // 测试小部件
buildItem(context),
...
],
),
),
),
);
}
示例代码
以下是一个完整的示例代码,展示了如何使用 flutter_gesture_hit_intercept
插件来实现手势命中拦截。
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_gesture_hit_intercept/flutter_gesture_hit_intercept.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
Widget buildItem(BuildContext context) {
return GestureDetector(
onTap: () {
debugPrint('点击了');
},
child: Container(
height: 100,
color: Colors.transparent,
child: Stack(
children: [
const Text('在此区域的手势不会阻止`ListView`的滚动'),
Align(
alignment: Alignment.center,
child: SizedBox(
width: 100,
height: 40,
child: FilledButton(
onPressed: () {
debugPrint('点击了');
},
child: const Text("按钮"),
),
)),
],
),
),
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: GestureHitInterceptScope(
child: ListView(
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
children: [
const GestureTestWidget(),
buildItem(context),
for (var i = 0; i < 100; i++)
if (i % 3 == 0)
const GestureTestWidget()
else
buildItem(context),
],
),
),
),
);
}
}
class GestureTestWidget extends LeafRenderObjectWidget {
const GestureTestWidget({super.key});
[@override](/user/override)
RenderObject createRenderObject(BuildContext context) {
return GestureTestBox(context);
}
}
Color randomColor({int min = 120, int max = 200}) => Color.fromARGB(
255,
nextInt(max, min: min),
nextInt(max, min: min),
nextInt(max, min: min),
);
int nextInt(int max, {int min = 0}) => min + Random().nextInt(max);
class GestureTestBox extends RenderBox {
/// 用来保存所有的手指事件
final pointerMap = <int, PointerEvent>{};
/// 用来保存所有的手指颜色
final pointerColorMap = <int, Color>{};
BuildContext context;
GestureTestBox(this.context);
Color getPointerColor(int pointer) {
return pointerColorMap.putIfAbsent(pointer, () => randomColor());
}
[@override](/user/override)
void performLayout() {
size = Size(constraints.maxWidth, constraints.maxWidth / 2);
}
/// 如果为true, 会影响[PointerEvent.localPosition]位置信息
[@override](/user/override)
bool get isRepaintBoundary => false;
[@override](/user/override)
void paint(PaintingContext context, Offset offset) {
//context.canvas.drawColor(Colors.redAccent, BlendMode.src);
final canvas = context.canvas;
canvas.drawRect(
Rect.fromLTWH(
paintBounds.left + offset.dx,
paintBounds.top + offset.dy,
paintBounds.width,
paintBounds.height,
),
Paint()..color = Colors.grey,
);
TextPainter(
text: TextSpan(
text: "在此区域的手势会阻止`ListView`的滚动\n${pointerMap.length}",
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12,
)),
textDirection: TextDirection.ltr,
)
..layout(maxWidth: paintBounds.width)
..paint(canvas, offset);
//绘制map
const radius = 30.0;
final paint = Paint()
..strokeWidth = 1
..style = PaintingStyle.stroke;
//debugger();
pointerMap.forEach((key, pointer) {
paint.color = getPointerColor(key);
canvas.drawLine(Offset(offset.dx, pointer.localPosition.dy),
Offset(offset.dx + size.width, pointer.localPosition.dy), paint);
canvas.drawLine(Offset(pointer.localPosition.dx, offset.dy),
Offset(pointer.localPosition.dx, offset.dy + size.height), paint);
canvas.drawCircle(pointer.localPosition, radius, paint);
});
}
[@override](/user/override)
bool hitTestSelf(Offset position) {
return true;
}
/// 只有命中通过之后, 才会回调事件
/// [GestureBinding.handlePointerEvent] -> [GestureBinding._handlePointerEventImmediately] -> [HitTestResult.addWithPaintTransform]
[@override](/user/override)
void handleEvent(PointerEvent event, covariant BoxHitTestEntry entry) {
//debugger();
final hitInterceptBox = GestureHitInterceptScope.of(context);
hitInterceptBox?.interceptHitBox = this;
if (!event.synthesized) {
pointerMap[event.pointer] = event;
}
if (event is PointerUpEvent || event is PointerCancelEvent) {
pointerMap.remove(event.pointer);
}
markNeedsPaint();
super.handleEvent(event, entry);
}
}
更多关于Flutter手势命中拦截插件flutter_gesture_hit_intercept的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter手势命中拦截插件flutter_gesture_hit_intercept的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
flutter_gesture_hit_intercept
是一个用于处理Flutter中手势命中的插件,它允许你在多个手势识别器之间进行命中测试的拦截和处理。这在一些复杂的UI交互场景中非常有用,特别是当你有重叠的Widget,并且需要控制哪个Widget应该优先响应手势事件时。
安装
首先,你需要在 pubspec.yaml
文件中添加依赖:
dependencies:
flutter:
sdk: flutter
flutter_gesture_hit_intercept: ^1.0.0 # 请使用最新版本
然后运行 flutter pub get
来安装依赖。
基本使用
flutter_gesture_hit_intercept
提供了一个 GestureHitIntercept
组件,它可以包裹在你的Widget树中,允许你控制手势的命中测试。
import 'package:flutter/material.dart';
import 'package:flutter_gesture_hit_intercept/flutter_gesture_hit_intercept.dart';
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Gesture Hit Intercept Example'),
),
body: GestureHitIntercept(
onIntercept: (PointerEvent event, GlobalKey key) {
// 在这里处理命中拦截逻辑
print('Intercepted gesture at ${event.position}');
return true; // 返回 true 表示拦截手势
},
child: Center(
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: GestureDetector(
onTap: () {
print('Container tapped');
},
child: Center(
child: Text('Tap Me'),
),
),
),
),
),
);
}
}
参数解释
-
onIntercept
: 一个回调函数,当手势事件发生时,这个回调会被调用。它接收两个参数:PointerEvent
和GlobalKey
。你可以在这里决定是否要拦截手势。如果返回true
,手势将被拦截,不会传递给子Widget;如果返回false
,手势将继续传递给子Widget。 -
child
: 你要包裹的Widget树。
示例场景
假设你有两个重叠的Widget,一个是一个大背景按钮,另一个是一个小的覆盖按钮。你希望当用户点击小的覆盖按钮时,只有小的按钮响应点击事件,而背景按钮不响应。你可以使用 GestureHitIntercept
来实现这个逻辑。
GestureHitIntercept(
onIntercept: (event, key) {
if (key == smallButtonKey) {
return false; // 不拦截小按钮的点击事件
}
return true; // 拦截背景按钮的点击事件
},
child: Stack(
children: [
GestureDetector(
key: backgroundButtonKey,
onTap: () {
print('Background button tapped');
},
child: Container(
width: 300,
height: 300,
color: Colors.green,
),
),
Positioned(
top: 100,
left: 100,
child: GestureDetector(
key: smallButtonKey,
onTap: () {
print('Small button tapped');
},
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
),
],
),
)