web-dev-qa-db-ja.com

複数のスクロール可能なウィジェットを同期してスクロールする

簡単に言えば:

複数のスクロール可能なウィジェット(たとえば、SingleSchildScrollView)を一緒に同期させる方法はありますか?


1つをスクロールするときにもう1つをスクロールできる2つのスクロール可能ファイルが必要です。

このようにして、Stackを使用してそれらを互いに重ね、後ろの方が前の方に続いてスクロールできます。

または、それらをColumnまたはRowの別のセットに入れて別々にしますが、どちらかをスクロールするだけでスクロールします。

controllerを使用してみましたが、思ったとおりに動作していないようです。


たとえば、以下のコードを試してください。「RIGHT」は「LEFT」の前にあり、スクロールしようとすると、RIGHTのみが移動します。では、どうすれば両方を同時に移動できますか?

スタックを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'),
              ],)
            ),
          ])
      )
}}

この質問は以前に複数のフォーラムで行われたことがあると思いますが、誰もこれに対する結論や解決策を提示していません。 ( ここ を参照)

8
Chris

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()コマンドを追加して、offsetsを好きなように設定できます。

乾杯!!

PS。リストの長さが異なる場合、オフセットが異なり、制限を超えてスクロールしようとするとスタックオーバーフローエラーが発生します。必ずいくつかの例外またはエラー処理を追加してください。 (つまり、_if else_など)

1
Chris

@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の例

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

スクロール可能なウィジェットごとに上記の例を実装し、SyncController(パラメーターcontroller:)およびprocessNotificationscrollControllerパラメーターを編集します。 (上記_ firstScroller)それに応じて。上記のPSAが適用される_syncScroller != nullのチェックなど、さらにいくつかのフェイルセーフを実装する場合があります:)

2
Kimmax

私はちょうど同じ問題に直面していました、そして今そのための公式パッケージがあります: linked_scroll_controller

そのパッケージを使用して、マスターを作成する必要があります LinkedScrollControllerGroup スクロールオフセットを追跡すると、個別のScrollControllersが提供されます(同期が維持されます) LinkedScrollControllerGroup.addAndGet() 経由。

1
Jonas Wanke