Skip to content

NestedScrollView does not work well with floated app bars on the outside or pinned app bars on the inside #11222

@nonameden

Description

@nonameden

Video with issue:

I made app bar background semi transparent to see that inner scroll container scrolled under pinned part of AppBar

https://youtu.be/d5nMB8eJrdw

Code to Reproduce

class TestPage2 extends StatefulWidget {

  @override
  _TestPageState2 createState() => new _TestPageState2();
}

class _TestPageState2 extends State<TestPage2> {

  Widget _buildItem(BuildContext context, int index) {
    return new ListTile(
      title: new Text('Title $index'),
      subtitle: new Text('Subtitle $index'),
    );
  }

  Widget _buildTab(int listSize) {
    return new TabBarView(
      children: [
        new Stack(
          children: <Widget>[
            new Positioned(
              top: 0.0,
              left: 0.0,
              right: 0.0,
              child: new Material(
                elevation: 4.0,
                color: Colors.white,
                child: new Container(
                  height: 48.0,
                  child: new Center(
                      child: const Text('Here my fixed header 1')),
                ),
              ),
            ),
            new Positioned(
              top: 48.0,
              left: 0.0,
              right: 0.0,
              bottom: 0.0,
              child: new RefreshIndicator(
                  onRefresh: () {},
                  child: new ListView.builder(
                    itemBuilder: _buildItem,
                    itemCount: listSize,
                  )
              ),
            ),
          ],
        ),
        new Stack(
          children: <Widget>[
            new Positioned(
              top: 0.0,
              left: 0.0,
              right: 0.0,
              child: new Material(
                elevation: 4.0,
                color: Colors.white,
                child: new Container(
                  height: 48.0,
                  child: new Center(
                      child: const Text('Here my fixed header 2')),
                ),
              ),
            ),
            new Positioned(
              top: 48.0,
              left: 0.0,
              right: 0.0,
              bottom: 0.0,
              child: new RefreshIndicator(
                  onRefresh: () {},
                  child: new ListView.builder(
                    itemBuilder: _buildItem,
                    itemCount: 5,
                  )
              ),
            ),
          ],
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return new DefaultTabController(
      length: 2,
      child: new Scaffold(
        body: new NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return [
              new SliverAppBar(
                backgroundColor: Colors.black26,
                title: new Text('Here title'),
                pinned: true,
                floating: true,
                bottom: new TabBar(
                    tabs: [
                      new Tab(text: 'Tab1'),
                      new Tab(text: 'Tab2'),
                    ]
                ),
              ),
            ];
          },
          body: new TabBarView(
            children: [
              _buildTab(30),
              _buildTab(5),
            ],
          ),
        ),
      ),
    );
  }
}

Flutter Doctor

[✓] Flutter (on Mac OS X 10.12.5 16F73, locale en-NZ, channel master)
    • Flutter at /Users/nonameden/Documents/flutter
    • Framework revision cf96c7db4a (2 hours ago), 2017-07-13 16:19:11 -0700
    • Engine revision 431a251151
    • Tools Dart version 1.25.0-dev.4.0

[✓] Android toolchain - develop for Android devices (Android SDK 26.0.0)
    • Android SDK at /Users/nonameden/Library/Android/sdk
    • Platform android-26, build-tools 26.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_112-release-b06)

[✓] iOS toolchain - develop for iOS devices (Xcode 8.3.3)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 8.3.3, Build version 8E3004b
    • ios-deploy 1.9.1
    • CocoaPods version 1.2.1

[✓] Android Studio
    • Android Studio at /Applications/Android Studio 2.4 Preview.app/Contents
    • Java version OpenJDK Runtime Environment (build 1.8.0_112-release-b736)

[✓] Android Studio
    • Android Studio at /Applications/Android Studio 3.0 Preview.app/Contents
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-884-b01)

[✓] Android Studio (version 2.3)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Java version OpenJDK Runtime Environment (build 1.8.0_112-release-b06)

[✓] IntelliJ IDEA Ultimate Edition (version 2017.1.5)
    • Flutter plugin version 15.1
    • Dart plugin version 171.4694.29

[✓] Connected devices
    • Android SDK built for x86 • emulator-5554 • android-x86 • Android 7.0 (API 24) (emulator)

Might be a Fix

I was playing with Nested Scroll View and found that scrollExtent for outer scroll does not respect minExtent from SliverAppBar delegate so I modified RenderSliverFloatingPinnedPersistentHeader:

abstract class RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFloatingPersistentHeader {
  RenderSliverFloatingPinnedPersistentHeader({
    RenderBox child,
    FloatingHeaderSnapConfiguration snapConfiguration,
  }) : super(child: child, snapConfiguration: snapConfiguration);

  @override
  double updateGeometry() {
    final double minExtent = this.maxExtent;
    final double maxExtent = this.maxExtent;
    final double paintExtent = (maxExtent - _effectiveScrollOffset);
    final double layoutExtent = (maxExtent - constraints.scrollOffset);
    geometry = new SliverGeometry(
      scrollExtent: maxExtent,
      paintExtent: paintExtent.clamp(minExtent, constraints.remainingPaintExtent),
      layoutExtent: layoutExtent.clamp(0.0, constraints.remainingPaintExtent - minExtent),
      maxPaintExtent: maxExtent,
      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
    );
    return 0.0;
  }
}

to

abstract class RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFloatingPersistentHeader {
  RenderSliverFloatingPinnedPersistentHeader({
    RenderBox child,
    FloatingHeaderSnapConfiguration snapConfiguration,
  }) : super(child: child, snapConfiguration: snapConfiguration);

  @override
  double updateGeometry() {
    final double minExtent = this.minExtent;
    final double maxExtent = this.maxExtent;
    final double paintExtent = (maxExtent - _effectiveScrollOffset);
    final double layoutExtent = (maxExtent - constraints.scrollOffset);
    geometry = new SliverGeometry(
      scrollExtent: maxExtent - minExtent,
      paintExtent: paintExtent.clamp(minExtent, constraints.remainingPaintExtent),
      layoutExtent: layoutExtent.clamp(0.0, constraints.remainingPaintExtent - minExtent),
      maxPaintExtent: maxExtent,
      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
    );
    return 0.0;
  }
}

result for this on video: https://youtu.be/1y210APVEwo

Metadata

Metadata

Assignees

No one assigned

    Labels

    f: scrollingViewports, list views, slivers, etc.frameworkflutter/packages/flutter repository. See also f: labels.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions