同步滚动多个可滚动小部件

问题描述 投票:0回答:6

简单来说:

有没有办法让多个可滚动小部件(例如,

SingleSchildScrollView
)同步在一起?


我只想要 2 个可滚动项,当我滚动一个时,可以滚动另一个。

这样我就可以使用

Stack
将它们放在一起,后面的可以跟随前面的滚动。

或者可以将它们放在另一组

Column
Row
中,以便它们是分开的,但仍然只需滚动其中一个即可滚动。

我尝试使用

controller
,但它似乎没有按照我的想法进行。


例如尝试下面的代码, “右”将位于“左”的前面,如果我尝试滚动它们,只有右会移动。那么我该如何同时将它们移动到一起呢?

请不要告诉我将堆栈放入

ListView
中,这不是我需要的。

class _MyHomePageState extends State<MyHomePage> {

  final ScrollController _mycontroller = new ScrollController();

  @override
  Widget build(BuildContext context) {
    body:
      Container(
        height: 100,
        child:
          Stack( children: <Widget>[
            SingleChildScrollView(
              controller: _mycontroller,
              child: Column( children: <Widget>[
                Text('LEFT            '),
                Text('LEFT            '),
                Text('LEFT            '),
                Text('LEFT            '),
                Text('LEFT            '),
                Text('LEFT            '),
              ],)
            ),
            SingleChildScrollView(
              controller: _mycontroller,
              child: Column(children: <Widget>[
                Text('          RIGHT'),
                Text('          RIGHT'),
                Text('          RIGHT'),
                Text('          RIGHT'),
                Text('          RIGHT'),
                Text('          RIGHT'),
              ],)
            ),
          ])
      )
}}

我相信这个问题之前已经在多个论坛上被问过,但根本没有人对此做出结论或解决方案。 (参见此处

scroll dart controller flutter synchronization
6个回答
13
投票

我刚刚面临同样的问题,现在有一个官方包:linked_scroll_controller

使用该包,您只需创建一个主

LinkedScrollControllerGroup
来跟踪滚动偏移,然后它将通过
ScrollController
为您提供单独的
LinkedScrollControllerGroup.addAndGet()
(保持同步)。


12
投票

我成功地通过使用他们的

offset
来同步多个可滚动项,利用他们的
ScrollNotification

这是一个粗略的代码示例:

class _MyHomePageState extends State<MyHomePage> {

  ScrollController _mycontroller1 = new ScrollController(); // make seperate controllers
  ScrollController _mycontroller2 = new ScrollController(); // for each scrollables

  @override
  Widget build(BuildContext context) {
    body:
      Container(
        height: 100,
        child: NotificationListener<ScrollNotification>( // this part right here is the key
          Stack( children: <Widget>[

            SingleChildScrollView( // this one stays at the back
              controller: _mycontroller1,
              child: Column( children: <Widget>[
                Text('LEFT            '),
                Text('LEFT            '),
                Text('LEFT            '),
                Text('LEFT            '),
                Text('LEFT            '),
                Text('LEFT            '),
              ],)
            ),
            SingleChildScrollView( // this is the one you scroll
              controller: _mycontroller2,
              child: Column(children: <Widget>[
                Text('          RIGHT'),
                Text('          RIGHT'),
                Text('          RIGHT'),
                Text('          RIGHT'),
                Text('          RIGHT'),
                Text('          RIGHT'),
              ],)
            ),
          ]),

          onNotification: (ScrollNotification scrollInfo) {  // HEY!! LISTEN!!
            // this will set controller1's offset the same as controller2's
            _mycontroller1.jumpTo(_mycontroller2.offset); 

            // you can check both offsets in terminal
            print('check -- offset Left: '+_mycontroller1.offset.toInt().toString()+ ' -- offset Right: '+_mycontroller2.offset.toInt().toString()); 
          }
        )
      )
}}

基本上每个

SingleChildScrollView
都有自己的
controller
。 每个
controller
都有自己的
offset
值。 每当滚动时,使用
NotificationListener<ScrollNotification>
通知任何移动。

然后对于每个滚动手势(我相信这是逐帧的基础), 我们可以添加

jumpTo()
命令来以我们喜欢的任何方式设置
offset

干杯!!

PS。如果列表的长度不同,那么偏移量也会不同,如果您尝试滚动超过其限制,您将收到堆栈溢出错误。确保添加一些异常或错误处理。 (即

if else
等)


5
投票

感谢您的回答@Chris,我遇到了同样的问题,并在您的基础上构建了我的解决方案。它适用于多个小部件,并允许从“组”中的任何小部件同步滚动。


PSA:这似乎工作正常,但我才刚刚开始,这可能会以你能想象到的最美妙的方式打破


它使用

NotificationListener<ScrollNotification>
以及每个应同步的可滚动小部件的独立
ScrollController
来工作。
类看起来像这样:

class SyncScrollController {
  List<ScrollController> _registeredScrollControllers = new List<ScrollController>();

  ScrollController _scrollingController;
  bool _scrollingActive = false;

  SyncScrollController(List<ScrollController> controllers) {
    controllers.forEach((controller) => registerScrollController(controller));
  }

  void registerScrollController(ScrollController controller) {
    _registeredScrollControllers.add(controller);
  }

  void processNotification(ScrollNotification notification, ScrollController sender) {
    if (notification is ScrollStartNotification && !_scrollingActive) {
      _scrollingController = sender;
      _scrollingActive = true;
      return;
    }

    if (identical(sender, _scrollingController) && _scrollingActive) {
      if (notification is ScrollEndNotification) {
        _scrollingController = null;
        _scrollingActive = false;
        return;
      }

      if (notification is ScrollUpdateNotification) {
        _registeredScrollControllers.forEach((controller) => {if (!identical(_scrollingController, controller)) controller..jumpTo(_scrollingController.offset)});
        return;
      }
    }
  }
}

这个想法是,您使用该帮助程序注册每个小部件

ScrollController
,以便它可以引用每个应该滚动的小部件。您可以通过将
ScrollController
数组传递给
SyncScrollController
s 构造函数来完成此操作,或者稍后调用
registerScrollController
并将
ScrollController
作为参数传递给函数来完成此操作。
您需要将
processNotification
方法绑定到
NotificationListener
的事件处理程序。这可能全部在小部件本身中实现,但我还没有足够的经验。

使用该类看起来像这样:

为 scolControllers 创建字段

  ScrollController _firstScroller = new ScrollController();
  ScrollController _secondScroller = new ScrollController();
  ScrollController _thirdScroller = new ScrollController();

  SyncScrollController _syncScroller;

初始化SyncScrollController

@override
void initState() {
  _syncScroller = new SyncScrollController([_firstScroller , _secondScroller, _thirdScroller]);
  super.initState();
}

完整的NotificationListener示例

NotificationListener<ScrollNotification>(
  child: SingleChildScrollView(
    controller: _firstScroller,
    child: Container(
    ),
  ),
  onNotification: (ScrollNotification scrollInfo) {
    _syncScroller.processNotification(scrollInfo, _firstScroller);
  }
),

显然,为每个可滚动小部件实现上述示例,并相应地编辑

SyncController
(参数controller:)和
processNotification
scrollController参数(位于_firstScroller上方)。您可以实施更多的故障保险,例如检查
_syncScroller != null
等,以上 PSA 适用:)


3
投票

如果您的 UI 很简单,请包装两个滚动小部件 - 比方说两个 Column - 和一个相反的小部件 - 例如 Row - 然后使用 SingleChildScrollView 像这样

SingleChildScrollView(
  scrollDirection: Axis.vertical,
  child: Row(
    children: [
      //first column
      Column(),
      //second column
      Column(),
    ],
  ),
)

如果比较复杂,你可以使用这样的滚动控制器

  late final ScrollController scrollController1;

  late final ScrollController scrollController2;

  @override
  void initState() {
    scrollController1 = ScrollController();
    scrollController2 = ScrollController();

    scrollController1.addListener(() {
      if (scrollController1.offset != scrollController2.offset) {
        scrollController2.jumpTo(scrollController1.offset);
      }
    });
    scrollController2.addListener(() {
      if (scrollController1.offset != scrollController2.offset) {
        scrollController1.jumpTo(scrollController2.offset);
      }
    });
    super.initState();
  }

然后将每个滚动控制器添加到您拥有的每个滚动小部件中

SingleChildScrollView(
  scrollDirection: Axis.vertical,
  controller: scrollController1,
  child: Column(), // column 1
)

SingleChildScrollView(
  scrollDirection: Axis.vertical,
  controller: scrollController2,
  child: Column(), // column 2
)

0
投票

我知道这是一个古老的问题,但可能对某人有用.. 包 linked_scroll_controller 正是您正在寻找的,它的性能比

NotificationListener
或向您的scrollController 添加侦听器并使用
.jump
..

好得多
  late ScrollController controller1;
  late ScrollController controller2;
  late LinkedScrollControllerGroup parentController;
  
  
  @override
  void initState() {
    super.initState();
    parentController = LinkedScrollControllerGroup();
    controller1 = parentController.addAndGet();
    controller2 = parentController.addAndGet();
  }

就是这样,将controller1连接到第一个可滚动小部件,将controller2连接到另一个,然后它们就同步了。


-1
投票

我找到了这个并为我工作

static final tracking = TrackingScrollController();

// implementation
child: ListView(
  controller: tracking,

也可以处理多个综合浏览量。

© www.soinside.com 2019 - 2024. All rights reserved.