Синхронно прокручивайте несколько прокручиваемых виджетов

проще говоря:

есть ли способ синхронизировать несколько прокручиваемых виджетов (скажем, 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'),
              ],)
            ),
          ])
      )
}}

я полагаю, что этот вопрос уже задавался на нескольких форумах, но никто не сделал вывод или решение по этому поводу. (см. здесь)

зачем вам такой странный UX-дизайн? почему бы не использовать один вид прокрутки?

pskink 25.02.2019 08:03

@pskink, я просто хочу знать, как прокручивать 2 виджета вместе. в конечном итоге я хотел сделать что-то вроде это

Chris 25.02.2019 08:53

@pskink эй, я не могу открыть твою ссылку. можете ли вы вместо этого вставить содержимое, чтобы ответить на этот вопрос?

Chris 25.02.2019 09:52

@pskink привет!! ваш кусок кода помогает мне. это вроде работает, с моими предыдущими фрагментами кода, которые я пробовал. Пока он выглядит хорошо, я попытаюсь добавить это в свой проект и посмотреть, работает ли он так, как ожидалось. СКОРО ОБНОВИТСЯ!!!!

Chris 25.02.2019 10:42

как я уже сказал: вы не должны следовать этому пути: это плохой обходной путь

pskink 25.02.2019 10:43

@pskink, но пока это единственное решение, которое делает то, что я хотел.

Chris 25.02.2019 10:44

вместо этого вы должны изменить свой дизайн пользовательского интерфейса

pskink 25.02.2019 10:45

@pskink хорошо тогда. но все равно спасибо :) это все же помогло мне лучше понять флаттер. привет дружище!!

Chris 25.02.2019 10:47
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
14
8
7 908
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

мне удалось синхронизировать несколько прокручиваемых с помощью их 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 и т. д.)

@sivakumar посмотри мой ответ, это должно решить твою проблему

Kimmax 30.06.2019 15:23

Спасибо за ваш ответ @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 с этим помощником, чтобы у него была ссылка на каждый виджет, который нужно прокручивать. Вы можете сделать это, передав массив ScrollControllers конструктору SyncScrollControllers или позже, вызвав registerScrollController и передав ScrollController в качестве параметра функции.
Вам нужно будет связать метод processNotification с обработчиком событий NotificationListener. Вероятно, все это можно было бы реализовать в самом виджете, но у меня еще недостаточно опыта для этого.

Использование класса будет выглядеть примерно так:

Создание полей для scollControllers

  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<ScrollNotification>(
  child: SingleChildScrollView(
    controller: _firstScroller,
    child: Container(
    ),
  ),
  onNotification: (ScrollNotification scrollInfo) {
    _syncScroller.processNotification(scrollInfo, _firstScroller);
  }
),

Обязательно реализуйте приведенный выше пример для каждого прокручиваемого виджета и соответствующим образом отредактируйте параметры SyncController (параметр контроллер:) и processNotification scrollController (выше _firstScroller). Вы можете реализовать еще несколько отказоустойчивых средств, таких как проверка _syncScroller != null и т. д., Применяется выше PSA :)

Продолжая двигаться вперед, я понимаю, почему это было бы не так оптимально, производительность явно страдает, если все начинает расти.

Kimmax 30.06.2019 20:35

Чувак, твое решение с прослушиванием просто потрясающее. Мне нравится, как вы избавились от рекурсивных изменений положения прокрутки и обработали их как отдельный класс Sync.

Александр Бабич 06.10.2019 12:35

Мне нужно было установить начальное смещение для контроллеров прокрутки, а linked_scroll_controller, похоже, сейчас это не поддерживает. Этот подход работает в моем случае, так как я могу установить смещение для каждого ScrollController. Спасибо!

Cleber de Souza Alcântara 25.02.2021 20:30

Я только что столкнулся с той же проблемой, и теперь для этого есть официальный пакет: linked_scroll_controller.

Используя этот пакет, вам просто нужно создать мастер LinkedScrollControllerGroup, чтобы отслеживать смещение прокрутки, а затем он предоставит вам отдельные ScrollControllers (синхронизированные) через LinkedScrollControllerGroup.addAndGet().

Я протестировал его, и хорошо, если все прокручиваемые виджеты имеют одинаковую высоту. Но мои виджеты имеют разную высоту, и мне нужно что-то, что принимает не фактическое количество пикселей, а процент прокрутки. Если у кого-то есть решение для меня, пожалуйста, дайте мне знать.

Hyung Tae Carapeto Figur 12.10.2020 16:49

Я нашел этот и работал на меня

static final tracking = TrackingScrollController();

// implementation
child: ListView(
  controller: tracking,

а также работать с несколькими просмотрами страниц.

Привет @malik kurosaki, не могли бы вы расширить свой ответ, он не работает. Почему вы сделали его статичным?

Atamyrat Babayev 20.12.2021 13:39

Другие вопросы по теме