SliverAppBar с floating:true не разворачивается если начать скролить вниз

Когда я скролю список не с самого верха (есть элементы выше) SliverAppBar не будет отображаться до тех пор пока я не достигну верхней части списка, но в Telegram, AppBar начинает показываться сразу после того как я начинаю скролить вниз.

Я хочу достичь того же поведения что и AppBar в Telegram.

Хотелось бы уточнить что snap: true дает похожее, но не то поведение кторое нужно.

Пример поведения AppBar в Telegram

Вот код который у меня сейчас есть (Живой пример):

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  TestPage({Key? key, this.title}) : super(key: key);

  final String? title;

  @override
  _TestPageState createState() => _TestPageState();
}

class TabElement {
  String id;
  int? count;
  String? label;

  TabElement({required this.id, this.count, this.label}) {
    label ??= id;
  }
}

class Element {
  late String id;
}

class _TestPageState extends State<TestPage> {
  final Map<String, List<Element>?> _orders = {
    'step1': null,
    'step2': null,
    'step3': null,
    'step4': null,
    'step5': null,
    'step6': null,
    'step7': null,
    'step8': null,
    'step9': null,
    'step10': null,
    'step11': null,
    'step12': null,
    'step13': null,
    'step14': null,
  };

  List<TabElement> get _tabs => _orders.entries
      .map((el) => TabElement(id: el.key, count: el.value?.length))
      .toList();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: DefaultTabController(
        length: _tabs.length,
        child: _nestedScrollView(widget, _tabs, _orders),
      ),
    );
  }
}

Widget _tab(TabElement tab) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      if (tab.label != null)
        Text(
          tab.label as String,
          style: TextStyle(color: Colors.black),
        ),
    ],
  );
}

Widget _nestedScrollView(TestPage widget, List<TabElement> tabs, orders) {
  return NestedScrollView(
    headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
      return <Widget>[
        SliverOverlapAbsorber(
          handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
          sliver: _sliverAppBar(context, widget, innerBoxIsScrolled, tabs),
        ),
      ];
    },
    body: _tabBarView(tabs, orders),
  );
}

Widget _sliverAppBar(BuildContext context, TestPage widget,
    bool innerBoxIsScrolled, List<TabElement> tabs) {
  return SliverAppBar(
    title: Text(
      widget.title as String,
      style: TextStyle(color: Colors.black),
    ),
    centerTitle: false,
    backgroundColor: Colors.white,
    pinned: true,
    floating: true,
    snap: false,
    forceElevated: innerBoxIsScrolled,
    bottom: TabBar(
      isScrollable: true,
      tabs:
          tabs.map<Widget>((TabElement tab) => Tab(child: _tab(tab))).toList(),
    ),
  );
}

Widget _tabBarView(List<TabElement> tabs, orders) {
  return TabBarView(
      children: tabs
          .map<Widget>((tab) => SafeArea(
              top: false,
              bottom: false,
              child: Builder(builder: (BuildContext context) {
                return CustomScrollView(
                  key: PageStorageKey<String>(tab.id),
                  slivers: <Widget>[
                    SliverOverlapInjector(
                      handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
                          context),
                    ),
                    SliverFixedExtentList(
                      itemExtent: 48.0,
                      delegate: SliverChildBuilderDelegate(
                        (BuildContext context, int index) {
                          return ListTile(
                            title: Text('Item $index'),
                          );
                        },
                        childCount: 80,
                      ),
                    ),
                  ],
                );
              })))
          .toList());
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: TestPage(title: 'Test'),
    );
  }
}


Ответы (1 шт):

Автор решения: Vasiliy Rusin

Ответ оказался поразительно прост. У NestedScrollView есть метод floatHeaderSlivers, он и добавляет необходимое поведение.

  NestedScrollView(
    floatHeaderSlivers: true,
    headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
      return <Widget>[];
    },
    body: _tabBarView(tabs, orders),
  )
→ Ссылка