【Flutter】如何动态切换ScrollBehavior.dragDevices ?
起源
今天有位群友抛出了个奇怪的问题:怎么改RawGuestureDetector的gestures ? Flutter Issue
经过一番友好交流,才弄清楚原来他是要做一个支持滚动的设备(手写笔)切换的功能。就是在一个列表上方放一个切换button,点击一下,手写笔可以滚动内容,再点一下就不能滚动内容,可以在内容区做一些其它操作。
为什么不能更新?
了解清楚需求,首先想到部分Scroll组件和ScrollConfiguration,MaterialApp 都有一个scrollBehavior属性,可以自定义一个ScrollBehavior,来提供dragDevices。并且我自己平常测试,在win desktop端做初步排版,都有在全局启用鼠标拖动,这个behavior似乎是没有问题的。
但是经过简单测试,确实不能直接切换
/// 部分代码
ScrollConfiguration(
behavior: mouseCanDrag ? EnableMouseBehavior() : DisableMouseBehavior(),
child: ListView.builder(
itemBuilder: (context, index) {
return Container(
height: 50,
alignment: Alignment.center,
child: Text('$index'),
);
},
),
)
class EnableMouseBehavior extends MaterialScrollBehavior {
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
PointerDeviceKind.trackpad,
PointerDeviceKind.mouse,
PointerDeviceKind.unknown,
};
}
class DisableMouseBehavior extends MaterialScrollBehavior {
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
PointerDeviceKind.trackpad,
PointerDeviceKind.unknown,
};
}
发现首次设置了某个behavior,动态切换的时候dragDevices参数并不能同步过去。
如何强制它更新?
看了下 Scrollable 源码,里面有个setCanDrag方法,是初始化_gestureRecognizers 的。在 value 不变的情况下,它是不会重新设置gestures的。但是,在当前文件内并没有查到setCanDrag的调用。于是flutter lib里全局搜索了下,发现有两个类有对它调用,一个是NestedScrollView,一个是ScrollPositionWithSingleContext,在某些情况下会对它进行更新。并且,ScrollPosition.context就是这个定义了setCanDrag的ScrollContext。 那问题不就简单了吗,直接调用setCanDrag(false)再调用setCanDrag(true)不就强制它更新了吗。
然而事情并没有那么简单,不管怎么调用,它总抛给我一个
To set the gesture recognizers at other times, trigger a new build using setState()
and provide the new gesture recognizers as constructor arguments to the corresponding
RawGestureDetector or GestureDetector object.
的错误,意思是需要通过setState来提供一个新的gesture recognizers给GestureDetector来更新。但是,从scrollBehavior里提供你不用啊喂!
曲径通幽
经过一翻思考尝试,NeverScrollablePhysics 又来发光发热了。我发现它真是个有用的东西。 需要更新behavior的时候不直接更新behavior,先设置physics,然后下一帧再取消physics,通过这个切换来让它强制更新gestures。
关键代码:
class _ScrollBehaviorPageState extends State<ScrollBehaviorPage> {
bool mouseCanDrag = true;
bool mouseCanDragChange = false;
final controller = ScrollController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('scrollBehavior'),
actions: [
Switch(
value: mouseCanDrag,
onChanged: (v) {
setState(() {
mouseCanDrag = v;
mouseCanDragChange = true;
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
mouseCanDragChange = false;
});
});
})
],
),
body: ScrollConfiguration(
behavior: mouseCanDrag ? EnableMouseBehavior() : DisableMouseBehavior(),
child: ListView.builder(
controller: controller,
physics:
mouseCanDragChange ? const NeverScrollableScrollPhysics() : null,
itemBuilder: (context, index) {
return Container(
height: 50,
alignment: Alignment.center,
child: Text('$index'),
);
},
),
),
);
}
}
具体效果可在桌面端配合鼠标体验:
启用时鼠标可通过拖动来滚动,禁用时鼠标拖动无法滚动,但鼠标滚轮仍然可以滚动。
虽然做法上绕了一下,但并没有增加太多操作,还是可以接受的。不知道各位大佬有没有更好的方法,欢迎评论交流。
转载自:https://juejin.cn/post/7143623032006213640