2

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,
              ),
            ),
          ),
        ],
      );
  }
Josh Kahane
  • 16,765
  • 45
  • 140
  • 253
  • Consider using `CustomScrollView` with `SliverAppBar` and `SliverList` (`SliverChildBuilderDelegate`) – Pavel Aug 17 '20 at 10:41
  • This doesn't create the effect I desire, nor does it provide me the flexibility to customise it further to me needs going forward. Hence the need to resolve, or at least understand why I am facing the `ScrollController` issue. – Josh Kahane Aug 17 '20 at 11:43

1 Answers1

3

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

Josh Kahane
  • 16,765
  • 45
  • 140
  • 253