Вопрос или проблема
Вот мой код, я хочу реализовать настраиваемый заголовок, который будет подниматься, когда пользователь прокручивает. И прикрепить таббар к верхней части экрана, а в нижней части виджета будет показано содержимое. Для этого я использую Sliver Widgets.
Вот фрагменты кода.
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: 'Команда мемов',
title2: 'Джек и Крис',
),
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(),
],
),
)
],
),
);
}
}
И я создал собственные методы делегата, чтобы прикрепить таббар.
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;
}
}
Но основная проблема в том, что когда таббар закреплен вверху, затем он прокручивается под ним.
И тогда невозможно прокрутить контент вниз.
Пожалуйста, помогите мне, как мы можем добиться такого пользовательского интерфейса?
Вместо CustomScrollView
внутри DefaultTabController
вам следует использовать NestedScrollView
. Внутри headerSliverBuilder
поместите ваш виджет CustomChatAppBar
внутрь SliverAppBar
, за которым следует виджет SliverPersistentHeader
. Наконец, в теле добавьте виджет TabBarView
.
Ответ или решение
Для решения вашей проблемы с использованием CustomScrollView
и TabBar
, я рекомендую заменить CustomScrollView
на NestedScrollView
. Это обеспечит правильное поведение при прокрутке, когда TabBar
будет "прикреплен" к верхней части экрана, а контент будет прокручиваться под ним.
Вот как вы можете изменить ваш код:
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,
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: CustomChatAppBar(
title: AppLabels.myGroupDetails,
systemOverlayStyle: Global.whiteOverLay,
actions: [],
),
pinned: true,
floating: false,
expandedHeight: 200.0, // высота вашего заголовка
flexibleSpace: FlexibleSpaceBar(
background: Container(
// Добавьте здесь ваш контент, который будет находиться в заголовке
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,
isScrollable: true,
indicatorColor: AppColors.kSecondaryColor,
labelColor: AppColors.kSecondaryColor,
unselectedLabelColor: AppColors.kAppGrey,
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;
}
}
Объяснение изменений
-
Использование
NestedScrollView
: Это позволяет вам иметь прокручиваемый контент, который будет правильно вести себя вместе сSliverAppBar
иTabBar
. -
headerSliverBuilder
: В этот метод я добавилSliverAppBar
, который содержит ваш пользовательский заголовок. После этого добавляетсяSliverPersistentHeader
дляTabBar
. -
TabBarView
: Теперь он помещен вbody
NestedScrollView
, что позволяет ему прокручиваться под прикрепленнымTabBar
.
Это изменение должно устранить проблемы с прокруткой вашего контента под закрепленным TabBar
, позволяя пользователю беспрепятственно взаимодействовать с содержимым.