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 ?
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;
}
}