0

I tried to acheive from NestedScrollView by creating a sample code. But its not exactly which I want. Please help me. I want to achieve this ui. When scrolling appbar should dock at the top. Image top should start from status bar to the fixed height 300. After Image there would be some dynamic content like description. Then I have to add TabBar, which will dock just below the Appbar when scroll. Body will scroll on top of Header Image, like a overlay.

Image Attachments

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class StickyTabbarExample extends StatefulWidget {
  @override
  _StickyTabbarExampleState createState() => _StickyTabbarExampleState();
}

class _StickyTabbarExampleState extends State<StickyTabbarExample>
    with SingleTickerProviderStateMixin {
  // TabController? _tabController;

  final List<String> _tabs = <String>[
    "Featured",
    "Popular",
  ];

  @override
  void initState() {
    super.initState();
    // _tabController = TabController(vsync: this, length: _tabs.length);
  }

  @override
  void dispose() {
    // _tabController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        body: DefaultTabController(
          length: _tabs.length,
          child: NestedScrollView(
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) => <Widget>[
              SliverAppBar(
                pinned: true,
                // floating: true,
                toolbarHeight: 70,
                title: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Icon(
                        Icons.arrow_back_ios_new_sharp,
                        color: Colors.white,
                        size: 15,
                      ),
                      Icon(
                        Icons.shop,
                        color: Colors.white,
                        size: 15,
                      )
                    ]),
                backgroundColor: Colors.green,
                expandedHeight: 300,
                flexibleSpace: FlexibleSpaceBar(
                  background: Image.network(
                    "https://images.unsplash.com/photo-1673942393203-fe61f45b4479?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80%20870w",
                    fit: BoxFit.cover,
                    width: double.maxFinite,
                  ),
                ),
              ),
              SliverToBoxAdapter(
                child: Positioned.fill(
                  child: Transform.translate(
                    offset: Offset(0, -10.0),
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Container(
                          height: 30,
                          clipBehavior: Clip.antiAlias,
                          decoration: BoxDecoration(
                            color: Colors.red,
                            borderRadius: BorderRadius.only(
                              topRight: Radius.circular(100),
                              topLeft: Radius.circular(100),
                            ),
                          ),
                        ),
                        Text("Challenge description"),
                      ],
                    ),
                  ),
                ),
              ),
              SliverPersistentHeader(
                delegate: _SliverAppBarDelegate(
                  TabBar(
                    indicator: UnderlineTabIndicator(
                        borderSide: BorderSide(
                          width: 4,
                          color: Color(0xFF646464),
                        ),
                        insets: EdgeInsets.only(left: 0, right: 8, bottom: 4)),
                    isScrollable: true,
                    labelPadding: EdgeInsets.only(left: 0, right: 0),
                    tabs: [
                      Tab(
                        child: Text(
                          "Tab 1",
                          style: TextStyle(color: Colors.black),
                        ),
                      ),
                      Tab(
                          child: Text(
                        "Tab 2",
                        style: TextStyle(color: Colors.black),
                      )),
                    ],
                  ),
                ),
                pinned: true,
              ),
            ],
            body: TabBarView(children: [TabA(), TabB()]),
          ),
        ),
      );
}

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate(this._tabBar);

  final TabBar _tabBar;

  @override
  double get minExtent => _tabBar.preferredSize.height;

  @override
  double get maxExtent => _tabBar.preferredSize.height;

  @override
  Widget build(
          BuildContext context, double shrinkOffset, bool overlapsContent) =>
      Container(
        color: Colors.white,
        child: _tabBar,
      );

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) => false;
}

class TabA extends StatelessWidget {
  const TabA({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => CustomScrollView(
        // primary: false,
        key: PageStorageKey<String>("Tab1"),
        slivers: [
          SliverPadding(
            padding: EdgeInsets.symmetric(vertical: 24, horizontal: 16),
            sliver: SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) => Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Container(
                    color: index % 2 == 0 ? Colors.green : Colors.greenAccent,
                    height: 80,
                    alignment: Alignment.center,
                    child: Text(
                      "Item $index",
                      style: const TextStyle(fontSize: 30),
                    ),
                  ),
                ),
                // 40 list items
                childCount: 40,
              ),
            ),
          )
        ],
      );
}

class TabB extends StatelessWidget {
  const TabB({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => CustomScrollView(
        key: PageStorageKey<String>("Tab2"),
        slivers: [
          SliverPadding(
            padding: EdgeInsets.symmetric(vertical: 24, horizontal: 16),
            sliver: SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) => Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Container(
                    color: index % 2 == 0 ? Colors.green : Colors.greenAccent,
                    height: 80,
                    alignment: Alignment.center,
                    child: Text(
                      "Item $index",
                      style: const TextStyle(fontSize: 30),
                    ),
                  ),
                ),
                childCount: 40,
              ),
            ),
          )
        ],
      );
}
  • You will need to play around sliver app bar pinned / floating and flexibable space (observe the remaining space and do a animation on it : fading, ...), and get a sliverOverlapObserver / injector to avoid problem under each tab scroll. Look at https://stackoverflow.com/questions/63231817/custom-flexiblespacebar-widget and https://stackoverflow.com/questions/55187332/flutter-tabbar-and-sliverappbar-that-hides-when-you-scroll-down – Pierre Bouttier Jan 23 '23 at 08:54

0 Answers0