likes
comments
collection
share

【Flutter】如何动态切换ScrollBehavior.dragDevices ?

作者站长头像
站长
· 阅读数 51

起源

今天有位群友抛出了个奇怪的问题:怎么改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
评论
请登录