amit.flutter
amit.flutter

Reputation: 1141

Scroll issue Flutter : Custom Sliver app bar and header with tab bar and Silver fill remain widget

Here is my code want to implement custom header which goes up when user scroll.

And stick tabbar to top and bottom view widget will show.. for that I user Sliver Widgets..

Here are some code snippets:

class ProfileMyGroupDetailsScreen extends StatefulWidget {
  const ProfileMyGroupDetailsScreen({Key? key}) : super(key: key);

  @override
  State<ProfileMyGroupDetailsScreen> createState() => _ProfileMyGroupDetailsScreenState();
}

class _ProfileMyGroupDetailsScreenState extends State<ProfileMyGroupDetailsScreen> with SingleTickerProviderStateMixin {
  TabController? _tabController;

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      _tabController = TabController(initialIndex: 0, length: 4, vsync: this);
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: CustomChatAppBar(
        title: AppLabels.myGroupDetails,
        systemOverlayStyle: Global.whiteOverLay,
        actions: [],
      ),
      body: buildDataTabWidget(),
    );
  }

  DefaultTabController buildDataTabWidget() {
    return DefaultTabController(
      length: 4,
      child: CustomScrollView(
        slivers: [
          SliverToBoxAdapter(
            child: Column(
              children: [
                ListTileType1(
                  padding: padXY(24, 8.5),
                  imageUrl: AppImages.dummyProfileImage1,
                  imageWidth: 44,
                  imageHeight: 44,
                  title1: 'Meme team',
                  title2: 'Jack & Chris',
                ),
                Padding(
                  padding: padXY(24, 16),
                  child: CustomImage(
                    imageUrl: AppImages.dummyProfileImage1,
                    height: MediaQuery.of(context).size.width - (24 * 2),
                    width: MediaQuery.of(context).size.width,
                    fit: BoxFit.cover,
                    borderRadius: 12,
                  ),
                ),
              ],
            ),
          ),
          SliverPersistentHeader(
            pinned: true,
            delegate: _SliverAppBarDelegate(
              TabBar(
                controller: _tabController,
                dividerColor: AppColors.kBorderColor,
                dividerHeight: 2,
                isScrollable: true,
                padding: EdgeInsets.zero,
                tabAlignment: TabAlignment.center,
                indicatorSize: TabBarIndicatorSize.tab,
                indicatorWeight: 1,
                labelColor: AppColors.kSecondaryColor,
                unselectedLabelColor: AppColors.kAppGrey,
                labelStyle: context.bodyLarge.w500,
                unselectedLabelStyle: context.bodyLarge,
                onTap: (index) {
                  // moduleDetailController.selectedIndex.value = index;
                  // moduleDetailController.selectedIndex.refresh();
                },
                tabs: [
                  Tab(text: AppLabels.basicInformation),
                  Tab(text: AppLabels.myLikes),
                  Tab(text: AppLabels.matches),
                  Tab(text: AppLabels.sentRequests),
                ],
              ),
            ),
          ),
          SliverFillRemaining(
            child: TabBarView(
              controller: _tabController,
              children: [
                BuildBasicInfoTab(),
                MyLikeTab(),
                MatchesTab(),
                SentRequestTab(),
              ],
            ),
          )
        ],
      ),
    );
  }
}

And I made a custom delegate method to stick tab bar:

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) {
    return Container(color: AppColors.kWhiteColor, child: _tabBar);
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return false;
  }
}

But main issue is that when tab bar pin to tap after that it scroll under to it.

And then not able to scroll down the content.

Please assist me how we can achieve this type of UI ?

enter image description here

Upvotes: 1

Views: 42

Answers (2)

Sahil Kundaliya
Sahil Kundaliya

Reputation: 85

as Salman said you have to use NestedScrollView, please refer for more details NestedScroll Widget

import 'package:flutter/material.dart';

class ProfileMyGroupDetailsScreen extends StatefulWidget {
const ProfileMyGroupDetailsScreen({Key? key}) : super(key: key);

@override
State<ProfileMyGroupDetailsScreen> createState() => 
_ProfileMyGroupDetailsScreenState();
}

class _ProfileMyGroupDetailsScreenState extends 
State<ProfileMyGroupDetailsScreen> with SingleTickerProviderStateMixin {
late TabController _tabController;

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

@override
Widget build(BuildContext context) {
return Scaffold(
  backgroundColor: Colors.white,
  appBar: CustomChatAppBar(
    title: AppLabels.myGroupDetails,
    systemOverlayStyle: Global.whiteOverLay,
    actions: [],
  ),
  body: buildDataTabWidget(),
);
}

Widget buildDataTabWidget() {
return NestedScrollView(
  headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
    return [
      SliverToBoxAdapter(
        child: Column(
          children: [
            ListTileType1(
              padding: padXY(24, 8.5),
              imageUrl: AppImages.dummyProfileImage1,
              imageWidth: 44,
              imageHeight: 44,
              title1: 'Meme team',
              title2: 'Jack & Chris',
            ),
            Padding(
              padding: padXY(24, 16),
              child: CustomImage(
                imageUrl: AppImages.dummyProfileImage1,
                height: MediaQuery.of(context).size.width - (24 * 2),
                width: MediaQuery.of(context).size.width,
                fit: BoxFit.cover,
                borderRadius: 12,
              ),
            ),
          ],
        ),
      ),
      SliverPersistentHeader(
        pinned: true,
        delegate: _SliverAppBarDelegate(
          TabBar(
            controller: _tabController,
            dividerColor: AppColors.kBorderColor,
            dividerHeight: 2,
            isScrollable: true,
            padding: EdgeInsets.zero,
            tabAlignment: TabAlignment.center,
            indicatorSize: TabBarIndicatorSize.tab,
            indicatorWeight: 1,
            labelColor: AppColors.kSecondaryColor,
            unselectedLabelColor: AppColors.kAppGrey,
            labelStyle: context.bodyLarge.w500,
            unselectedLabelStyle: context.bodyLarge,
            tabs: [
              Tab(text: AppLabels.basicInformation),
              Tab(text: AppLabels.myLikes),
              Tab(text: AppLabels.matches),
              Tab(text: AppLabels.sentRequests),
            ],
          ),
        ),
      ),
    ];
  },
  body: TabBarView(
    controller: _tabController,
    children: [
      BuildBasicInfoTab(),
      MyLikeTab(),
      MatchesTab(),
      SentRequestTab(),
     ],
   ),
  );
 }
}

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) {
return Container(color: AppColors.kWhiteColor, child: _tabBar);
}

@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
 }
}

Upvotes: 1

Salman Ahmed
Salman Ahmed

Reputation: 379

You should use a NestedScrollView instead of a CustomScrollView within the DefaultTabController. Inside the headerSliverBuilder, place your CustomChatAppBar widget inside a SliverAppBar, followed by the SliverPersistentHeader widget. Finally, in the body, include the TabBarView widget.

Upvotes: 0

Related Questions