NestedScrollView with two SliverAppBars - one pinned, one not pinned

65 views Asked by At

I'm having an issue using a NestedScrollView with two SliverAppBars - one pinned and one not pinned. In the non-pinned app bar I'd like to put a CupertinoSlidingSegmentedControl (tab selector of a sort, that also scrolls with the page). Then inside the body of the scroll view, I'd like to put a PageView or similar, where I can switch between the pages, based on the segmented control's selection.

I've also set

debugDefaultTargetPlatformOverride = TargetPlatform.iOS;

inside main().

Here's my code:

class MyNestedScrollView extends StatefulWidget {
  const MyNestedScrollView({super.key});

  @override
  State<StatefulWidget> createState() => _MyNestedScrollViewState();
}

class _MyNestedScrollViewState extends State<MyNestedScrollView> {
  final GlobalKey<NestedScrollViewState> _nestedScrollViewKey = GlobalKey();
  int segmentedControlValue = 0;

  var overlapAbsorberHandle = SliverOverlapAbsorberHandle();

  Widget _buildSegmentedControl() {
    return CupertinoSlidingSegmentedControl<int>(
      children: const {
        0: Text('First'),
        1: Text('Second'),
      },
      onValueChanged: (int? newValue) {
        setState(() {
          segmentedControlValue = newValue ?? 0;
        });
      },
      groupValue: segmentedControlValue,
    );
  }

  Widget _buildPage(int pageIndex, String title) {
    return Builder(builder: (context) {
      return CustomScrollView(
        key: PageStorageKey<String>('page$pageIndex'),
        slivers: [
          // Add the Overlap Injector to get the correct overlap
          SliverOverlapInjector(
            handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
          ),
          SliverToBoxAdapter(
            child: Container(
              color: Colors.green,
              child: const TextField(
                decoration: InputDecoration(
                  hintText: 'Search',
                  prefixIcon: Icon(Icons.search),
                ),
              ),
            ),
          ),
          SliverList.builder(
            itemBuilder: (context, index) => ListTile(
              title: Text('$title $index'),
            ),
          ),
        ],
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        key: _nestedScrollViewKey,
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            // Add the Overlap Absorber widget to the SliverAppBar
            SliverOverlapAbsorber(
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
              sliver: const SliverAppBar(
                pinned: true,
                backgroundColor: Colors.blue,
                title: Text('Pinned AppBar'),
              ),
            ),
            SliverPadding(
              padding: const EdgeInsets.only(top: 56 + 24),
              sliver: SliverAppBar(
                pinned: false,
                backgroundColor: Colors.yellow,
                flexibleSpace: FlexibleSpaceBar(
                  background: _buildSegmentedControl(),
                ),
              ),
            ),
          ];
        },
        body: IndexedStack(
          index: segmentedControlValue,
          children: [
            _buildPage(0, 'First Page'),
            _buildPage(1, 'Second Page'),
          ],
        ),
      ),
    );
  }
}

Target layout: Target layout

Current layout: Current laoyt

I tried various approaches using SliverOverlapAbsorber, but none succeeded. Without it, whenever I start typing in the text field, it jumps behind the app bar (a big no-no). Whenever I add the absorber and an injector, I get this empty space as seen on the screenshots bellow. How do I fix that?

0

There are 0 answers