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

Chr*_*ris 7 scroll synchronization controller dart flutter

简而言之:

有没有一种方法可以将多个可滚动的小部件(例如SingleSchildScrollView)同步在一起?


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

这样,我可以Stack将它们放在彼此的顶部,而后面的一个可以跟随前面的一个滚动。

或将它们放在另一组中ColumnRow以便它们是分开的,但仍可以通过滚动其中一个来滚动。

我尝试使用,controller但似乎并没有按照我的想法做。


尝试下面的代码,例如,“ RIGHT”将在“ LEFT”的前面,如果我尝试滚动它们,则只有RIGHT会移动。那么如何同时将它们一起移动?

请不要告诉我将堆栈放在a内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'),
              ],)
            ),
          ])
      )
}}
Run Code Online (Sandbox Code Playgroud)

我相信之前在多个论坛中都曾问过这个问题,但没有人对此提出任何结论或解决方案。(请参阅此处

Jon*_*nke 9

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

使用该包,您只需要创建一个 masterLinkedScrollControllerGroup来跟踪滚动偏移,然后它将ScrollController通过LinkedScrollControllerGroup.addAndGet().


Chr*_*ris 5

我设法通过使用他们offsetScrollNotification.

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

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()); 
          }
        )
      )
}}
Run Code Online (Sandbox Code Playgroud)

基本上每个SingleChildScrollView都有自己的controller。每个人controller都有自己的offset价值观。使用NotificationListener<ScrollNotification>来通知任何移动,无论何时滚动。

然后对于每个滚动手势(我相信这是逐帧的),我们可以添加jumpTo()命令以offset我们喜欢的任何方式设置s。

干杯!!

附注。如果列表的长度不同,则偏移量将不同,如果您尝试滚动超过其限制,则会出现堆栈溢出错误。确保添加一些异常或错误处理。(即if else等)


Kim*_*max 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;
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这个想法是你ScrollController用那个助手注册每个小部件,这样它就会有一个对每个应该滚动的小部件的引用。您可以通过将ScrollControllers数组传递给SyncScrollControllers 构造函数,或稍后通过调用 sregisterScrollController并将其ScrollController作为参数传递给函数来完成此操作。
您需要将该processNotification方法绑定到NotificationListener. 这可能全部在小部件本身中实现,但我对此还没有足够的经验。

使用该类看起来像这样:

为 scollControllers 创建字段

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

  SyncScrollController _syncScroller;

Run Code Online (Sandbox Code Playgroud)

初始化 SyncScrollController

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

完整的 NotificationListener 示例

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

显然,为每个可滚动小部件实现上述示例,并相应地编辑SyncController(参数控制器processNotification:)和scrollController 参数(在_firstScroller上方)。您可能会_syncScroller != null在 PSA 之上实施更多故障保险,例如检查等:)


eva*_*als 5

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

SingleChildScrollView(
  scrollDirection: Axis.vertical,
  child: Row(
    children: [
      //first column
      Column(),
      //second column
      Column(),
    ],
  ),
)
Run Code Online (Sandbox Code Playgroud)

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

  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();
  }
Run Code Online (Sandbox Code Playgroud)

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

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

SingleChildScrollView(
  scrollDirection: Axis.vertical,
  controller: scrollController2,
  child: Column(), // column 2
)
Run Code Online (Sandbox Code Playgroud)