Anonymous
Как правильно настроить вложенную прокрутку с помощью SliverOverlapAbsorber во Flutter
Сообщение
Anonymous » 17 июл 2024, 06:38
Я создаю экран профиля во Flutter, используя NestedScrollView. У меня уже есть SliverOverlapAbsorber для моего SliverAppBar, но мне нужно добавить еще один для моего TabBar. Из-за динамического контента с разной высотой посередине я не могу использовать SliverOverlapAbsorber в качестве нижнего виджета. Как мне правильно настроить виджет моего профиля, чтобы справиться с этой ситуацией?
Вот мои текущие настройки:
Код: Выделить всё
• NestedScrollView with SliverAppBar.
• SliverOverlapAbsorber for the SliverAppBar.
• Dynamic content in the middle section.
• Need to add SliverOverlapAbsorber for TabBar without affecting the existing setup.
Будем благодарны за любые рекомендации и примеры!
Мой полный код:
Код: Выделить всё
class SimpleProfile extends StatefulWidget {
const SimpleProfile({
super.key,
});
@override
_SimpleProfileState createState() => _SimpleProfileState();
}
class _SimpleProfileState extends State with SingleTickerProviderStateMixin {
late ScrollController _scrollController;
late TabController _tabController;
@override
void initState() {
_scrollController = ScrollController();
super.initState();
_tabController = TabController(length: 6, vsync: this);
}
@override
void dispose() {
_scrollController.dispose();
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tabController.length,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
_profileAppBar(innerBoxIsScrolled),
_profileMainInfo(),
_tabBarSelector(),
];
},
body: Builder(
builder: (context) => _tabBarView(),
),
),
),
);
}
Widget _profileAppBar(bool innerBoxIsScrolled) {
return SliverAppBar(
floating: false,
pinned: true,
stretch: true,
forceElevated: innerBoxIsScrolled,
surfaceTintColor: Colors.white,
expandedHeight: 300,
backgroundColor: Colors.black45,
title: null,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Column(
children: [
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
final bgHeight = constraints.maxHeight * 0.85;
const avatarSize = 104.0;
const borderWidth = 2;
final radius = avatarSize / 2;
final imgSize = (radius - borderWidth) * 2;
return Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
child: SizedBox(
height: bgHeight,
child: Container(
color: Colors.blue,
),
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
spreadRadius: 2,
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: CircleAvatar(
radius: radius,
backgroundColor: Colors.white,
child: ClipOval(
child: Container(
color: Colors.blueAccent,
width: imgSize,
height: imgSize,
),
),
),
),
),
],
);
},
),
),
SizedBox(
height: 20,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Text(
'First and Last Names',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 28, fontWeight: FontWeight.w600),
),
),
],
),
),
);
}
Widget _profileMainInfo() {
return SliverToBoxAdapter(
child: Container(
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//some horizontal list
Container(color: Colors.amber, height: 35),
const SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: //some static height Container
Container(color: Colors.green, height: 75),
),
if (true) ...[
const SizedBox(
height: 20,
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Text(
'Some short/medium long text',
textAlign: TextAlign.justify,
style: TextStyle(
fontSize: 17,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w300,
height: 22 / 17,
letterSpacing: -0.408,
textBaseline: TextBaseline.alphabetic,
),
),
),
],
const SizedBox(
height: 20,
),
],
),
),
);
}
Widget _tabBarSelector() {
return SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
TabBar(
padding: const EdgeInsets.symmetric(horizontal: 12),
controller: _tabController,
tabAlignment: TabAlignment.start,
isScrollable: true,
labelPadding: const EdgeInsets.symmetric(horizontal: 12),
labelColor: Colors.black,
indicatorColor: Colors.black,
unselectedLabelColor: Colors.grey,
tabs: const [
Tab(text: 'tab 1'),
Tab(text: 'tab 2'),
Tab(text: 'tab 3'),
Tab(text: 'tab 4'),
Tab(text: 'tab 5'),
Tab(text: 'tab 6'),
],
),
),
);
}
Widget _tabBarView() {
return TabBarView(
controller: _tabController,
children: List.generate(6, (index) {
return Builder(
builder: (BuildContext context) {
return CustomScrollView(
key: PageStorageKey('SomeWidget$index'),
slivers: [
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverFixedExtentList(
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 30,
),
),
),
],
);
},
);
}),
);
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
@override
double get minExtent => _tabBar.preferredSize.height + 1;
@override
double get maxExtent => _tabBar.preferredSize.height + 1;
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Column(
children: [
Container(
color: Colors.white,
child: _tabBar,
),
Divider(
height: 1,
color: Colors.grey.withOpacity(0.2),
thickness: 1,
),
],
);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
Каждый раз, когда я переключаю вкладки, содержимое внутренней прокрутки начинается с верхней части экрана под панелью приложения. Мне нужно добавить вставку, чтобы сохранить положение прокрутки.
введите здесь описание изображения
Подробнее здесь:
https://stackoverflow.com/questions/787 ... in-flutter
1721187511
Anonymous
Я создаю экран профиля во Flutter, используя NestedScrollView. У меня уже есть SliverOverlapAbsorber для моего SliverAppBar, но мне нужно добавить еще один для моего TabBar. Из-за динамического контента с разной высотой посередине я не могу использовать SliverOverlapAbsorber в качестве нижнего виджета. Как мне правильно настроить виджет моего профиля, чтобы справиться с этой ситуацией? Вот мои текущие настройки: [code]• NestedScrollView with SliverAppBar. • SliverOverlapAbsorber for the SliverAppBar. • Dynamic content in the middle section. • Need to add SliverOverlapAbsorber for TabBar without affecting the existing setup. [/code] Будем благодарны за любые рекомендации и примеры! Мой полный код: [code] class SimpleProfile extends StatefulWidget { const SimpleProfile({ super.key, }); @override _SimpleProfileState createState() => _SimpleProfileState(); } class _SimpleProfileState extends State with SingleTickerProviderStateMixin { late ScrollController _scrollController; late TabController _tabController; @override void initState() { _scrollController = ScrollController(); super.initState(); _tabController = TabController(length: 6, vsync: this); } @override void dispose() { _scrollController.dispose(); _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return DefaultTabController( length: _tabController.length, child: Scaffold( body: NestedScrollView( headerSliverBuilder: (context, innerBoxIsScrolled) { return [ _profileAppBar(innerBoxIsScrolled), _profileMainInfo(), _tabBarSelector(), ]; }, body: Builder( builder: (context) => _tabBarView(), ), ), ), ); } Widget _profileAppBar(bool innerBoxIsScrolled) { return SliverAppBar( floating: false, pinned: true, stretch: true, forceElevated: innerBoxIsScrolled, surfaceTintColor: Colors.white, expandedHeight: 300, backgroundColor: Colors.black45, title: null, flexibleSpace: FlexibleSpaceBar( collapseMode: CollapseMode.pin, background: Column( children: [ Expanded( child: LayoutBuilder( builder: (context, constraints) { final bgHeight = constraints.maxHeight * 0.85; const avatarSize = 104.0; const borderWidth = 2; final radius = avatarSize / 2; final imgSize = (radius - borderWidth) * 2; return Stack( children: [ Positioned( top: 0, left: 0, right: 0, child: SizedBox( height: bgHeight, child: Container( color: Colors.blue, ), ), ), Positioned( left: 0, right: 0, bottom: 0, child: Container( decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), spreadRadius: 2, blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: CircleAvatar( radius: radius, backgroundColor: Colors.white, child: ClipOval( child: Container( color: Colors.blueAccent, width: imgSize, height: imgSize, ), ), ), ), ), ], ); }, ), ), SizedBox( height: 20, ), Padding( padding: EdgeInsets.symmetric(horizontal: 24), child: Text( 'First and Last Names', textAlign: TextAlign.center, style: TextStyle(fontSize: 28, fontWeight: FontWeight.w600), ), ), ], ), ), ); } Widget _profileMainInfo() { return SliverToBoxAdapter( child: Container( color: Colors.white, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ //some horizontal list Container(color: Colors.amber, height: 35), const SizedBox( height: 20, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: //some static height Container Container(color: Colors.green, height: 75), ), if (true) ...[ const SizedBox( height: 20, ), const Padding( padding: EdgeInsets.symmetric(horizontal: 24), child: Text( 'Some short/medium long text', textAlign: TextAlign.justify, style: TextStyle( fontSize: 17, fontStyle: FontStyle.italic, fontWeight: FontWeight.w300, height: 22 / 17, letterSpacing: -0.408, textBaseline: TextBaseline.alphabetic, ), ), ), ], const SizedBox( height: 20, ), ], ), ), ); } Widget _tabBarSelector() { return SliverPersistentHeader( pinned: true, delegate: _SliverAppBarDelegate( TabBar( padding: const EdgeInsets.symmetric(horizontal: 12), controller: _tabController, tabAlignment: TabAlignment.start, isScrollable: true, labelPadding: const EdgeInsets.symmetric(horizontal: 12), labelColor: Colors.black, indicatorColor: Colors.black, unselectedLabelColor: Colors.grey, tabs: const [ Tab(text: 'tab 1'), Tab(text: 'tab 2'), Tab(text: 'tab 3'), Tab(text: 'tab 4'), Tab(text: 'tab 5'), Tab(text: 'tab 6'), ], ), ), ); } Widget _tabBarView() { return TabBarView( controller: _tabController, children: List.generate(6, (index) { return Builder( builder: (BuildContext context) { return CustomScrollView( key: PageStorageKey('SomeWidget$index'), slivers: [ SliverPadding( padding: const EdgeInsets.all(8.0), sliver: SliverFixedExtentList( itemExtent: 48.0, delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return ListTile( title: Text('Item $index'), ); }, childCount: 30, ), ), ), ], ); }, ); }), ); } } class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { _SliverAppBarDelegate(this._tabBar); final TabBar _tabBar; @override double get minExtent => _tabBar.preferredSize.height + 1; @override double get maxExtent => _tabBar.preferredSize.height + 1; @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return Column( children: [ Container( color: Colors.white, child: _tabBar, ), Divider( height: 1, color: Colors.grey.withOpacity(0.2), thickness: 1, ), ], ); } @override bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { return false; } } [/code] Каждый раз, когда я переключаю вкладки, содержимое внутренней прокрутки начинается с верхней части экрана под панелью приложения. Мне нужно добавить вставку, чтобы сохранить положение прокрутки. введите здесь описание изображения [img]https://i.sstatic.net/0bLPbbGC.png[/img] Подробнее здесь: [url]https://stackoverflow.com/questions/78757319/how-to-properly-configure-nested-scroll-with-sliveroverlapabsorber-in-flutter[/url]