skip to Main Content

In this example I have 3 sliders, dragging one slider should update the secondary track of all the sliders

This however does not work when the sliders are wrapped with a ListView, notice how the top two tracks do not update until I mouse over them:

Slider ListView

Changing the ListView to a Column yeilds the desired behaviour:

Slider Column

Any idea how I can benefit from the virtualisation of ListView and get the on-screen items to update?

class ShareData with ChangeNotifier {
  double share;
  double shareAvailable;
  ShareData({required this.share, required this.shareAvailable});

  void setShareAvaialble(double value) {
    shareAvailable = value;
    notifyListeners();
  }
}

class ShareSlider extends StatefulWidget {
  const ShareSlider({super.key});
  @override
  State<ShareSlider> createState() => _ShareSliderState();
}

class _ShareSliderState extends State<ShareSlider> {
  late List<ShareData> _shares;

  @override
  void initState() {
    _shares = List<ShareData>.generate(3, (index) => ShareData(share: 1.0 / 3, shareAvailable: 1.0 / 3));
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ListView( //<---- Change me to Column
        children: _shares
            .map(
              (s) => ListenableBuilder(
                listenable: s,
                builder: (c, o) => Slider(
                    value: s.share,
                    secondaryTrackValue: s.shareAvailable,
                    onChanged: (e) {
                      s.share = e;
                      for (int i = 0; i < _shares.length; ++i) {
                        _shares[i].setShareAvaialble(1 - e);
                      }
                    }),
              ),
            )
            .toList());
  }
}

Expecting all 3 bars to update

2

Answers


  1. Chosen as BEST ANSWER

    Fixed it, in a slightly hacky way

    Using UniqueKey correctly re-renders everything on updates. However this has the side-effect of re-creating the widget the user is currently interacting - breaking dragging.

    So instead I use a ValueKey for each widget and only update the keys of the siders we are not interacting with:

    class ShareData with ChangeNotifier {
      double share;
      double shareAvailable;
      //This will store a unique key per update
      int updateKey = 0;
      ShareData({required this.share, required this.shareAvailable, required this.updateKey});
    
      void setShareAvaialble(double value) {
        shareAvailable = value;
        notifyListeners();
      }
    }
    
    class ShareSlider extends StatefulWidget {
      final int count;
    
      const ShareSlider({super.key, required this.count});
      @override
      State<ShareSlider> createState() => _ShareSliderState();
    }
    
    class _ShareSliderState extends State<ShareSlider> {
      late List<ShareData> _shares;
      int nonce = 0;
      @override
      void initState() {
        _shares =
            List<ShareData>.generate(3, (index) => ShareData(share: 1.0 / 3, shareAvailable: 1.0 / 3, updateKey: nonce++));
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          itemCount: _shares.length,
          itemBuilder: (ctx, index) => ListenableBuilder(
            listenable: _shares[index],
            builder: (c, o) => Slider(
    
                //Use the update key here
                key: ValueKey(_shares[index].updateKey),
                value: _shares[index].share,
                secondaryTrackValue: _shares[index].shareAvailable,
                onChanged: (e) {
                  _shares[index].share = e;
                  for (int i = 0; i < _shares.length; ++i) {
                    //Update the key of the other siders except the slider we're interacting with
                    if (i != index) _shares[i].updateKey = nonce++;
                    _shares[i].setShareAvaialble(1 - e);
                  }
                }),
          ),
        );
      }
    

  2. You’re changing State without calling setState. Fix that first.

    My theory:

    • It works when it’s a column, because the slider updates, causing the column it’s in to update as well.

    • It fails in a ListView because the items of a ListView are separately considered, and so the item holding the slider is refreshed, but the neighbors aren’t.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search