Josh Kahane
Josh Kahane

Reputation: 17160

Nested ListView with ScrollController Not Scrolling

I am building a widget with a custom navigation bar which includes a Stack with the main body and the navigation bar.

@override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        _buildBody(),
        _buildNavigationBar(),
      ],
    );
  }

The body consists of a ListView with a header and a ListView.builder nested below that for the contents.

Widget _buildBody() {
    return ListView(
      padding: EdgeInsets.only(top: 44.0 + MediaQuery.of(context).padding.top),
      controller: _scrollController,
      children: [
        Opacity(
          opacity: 1.0 - _navigationBarOpacity,
          child: Padding(
            padding: const EdgeInsets.only(bottom: 15.0),
            child: _buildHeaderTitle(
              title: widget.title,
              fontSize: _largeFontSize,
              fontWeight: _largeFontWeight,
              color: widget.color,
            ),
          ),
        ),
        ListView.builder(
          padding: EdgeInsets.zero,
          shrinkWrap: true,
          physics: NeverScrollableScrollPhysics(),
          itemCount: widget.itemCount,
          itemBuilder: (context, index) {
            return widget.itemBuilder(context, index);
          },
        ),
      ],
    );
  }

I'm using a ScrollController on the primary/root ListView in order to fade my navigation bar in and out. This works as expected.

However, adding the ScrollController to the ListView stops any scrolling/bouncing interaction. How can I fix this?

ScrollController _scrollController = ScrollController();

@override
  void initState() {
    super.initState();

    _scrollController.addListener(() {
      setState(() {
        double min = 0.0;
        double max = 25.0;
        _navigationBarOpacity = (_scrollController.offset - min) / (max - min);
        if (_navigationBarOpacity < 0) _navigationBarOpacity = 0;
        if (_navigationBarOpacity > 1) _navigationBarOpacity = 1;
      });
    });  
  }

Interestingly, if I move the ScrollController to the nestedt ListView.builder, the root ListView scrolls/bounces, but then I can no longer adjust the UI based on the offset as it's on the wrong ListView.

Another point of interest, the root ListView scrolls normally if it's children exceed it's height. However, there are cases where it won't have that and I'd expect it just to bounce on pulling.


Update

Even if I remove the stack and nested ListView, a simple, singlular ListView with the ScrollController won't scroll on drag, without it, it will. This is the root issue that needs resolving.

@override
  Widget build(BuildContext context) {
    return ListView(
        padding: EdgeInsets.only(top: 44.0 + MediaQuery.of(context).padding.top),
        controller: _scrollController,
        children: [
          Opacity(
            opacity: 1.0 - _navigationBarOpacity,
            child: Padding(
              padding: const EdgeInsets.only(bottom: 15.0),
              child: _buildHeaderTitle(
                title: widget.title,
                fontSize: _largeFontSize,
                fontWeight: _largeFontWeight,
                color: widget.color,
              ),
            ),
          ),
        ],
      );
  }

Upvotes: 2

Views: 5041

Answers (1)

Josh Kahane
Josh Kahane

Reputation: 17160

Resolved by wrapping the parent ListView in a PrimaryScrollController then setting primary = true on the ListView.

This lets PrimaryScrollController manage the controller, but the ListView maintains typical scrolling behaviour as it no longer manages the ScrollController.

See Flutter docs: https://api.flutter.dev/flutter/widgets/ScrollView/primary.html

Upvotes: 3

Related Questions