英文:
Persist scroll position of TabBarViews in NestedScrollView in flutter during swipes
问题
我有一个选项卡栏中的选项卡列表。
然而,我遇到了一个问题。如果您运行下面的示例代码并滚动到选项卡1中的项目30,然后滑动到选项卡2,再返回到选项卡1,您会立即注意到选项卡1中的项目30不再处于焦点状态。滚动位置已跳转到项目1。这很奇怪。
我已经尝试了一切可能的方法,以确保每个ListView的滚动位置都会在我多次滑动离开并返回时得以保留。
这里是一个完整的可运行示例代码,准确展示了我面临的问题。
import 'package:flutter/material.dart';
class SampleListApp extends StatefulWidget {
const SampleListApp({Key? key}) : super(key: key);
@override
State<SampleListApp> createState() => _SampleListAppState();
}
class _SampleListAppState extends State<SampleListApp>
with TickerProviderStateMixin {
TabController? _tabController;
late ScrollController _scrollViewController;
int _currentTabIndex = 0;
_handleTabChange() {
setState(() {
if (_tabController != null) {
_currentTabIndex = _tabController!.index;
}
});
}
_initTabController() {
_tabController?.dispose();
_tabController =
TabController(initialIndex: _currentTabIndex, vsync: this, length: 2);
_tabController?.addListener(_handleTabChange);
}
@override
void initState() {
super.initState();
_initTabController();
_scrollViewController = ScrollController(initialScrollOffset: 0.0);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
controller: _scrollViewController,
headerSliverBuilder: (BuildContext context, bool boxIsScrolled) => [
SliverAppBar(
title: const Text("Hello World"),
bottom: TabBar(
indicatorColor: Colors.white,
isScrollable: true,
labelColor: Colors.orange,
unselectedLabelColor: Colors.grey,
tabs: const [
Tab(
text: "Tab 1",
),
Tab(
text: "Tab 2",
)
],
controller: _tabController,
),
pinned: true,
floating: true,
forceElevated: boxIsScrolled,
),
],
body: TabBarView(
key: UniqueKey(),
controller: _tabController,
children: const [
ChildView(),
ChildView(),
],
),
),
);
}
}
class ChildView extends StatefulWidget {
const ChildView({Key? key}) : super(key: key);
@override
State<ChildView> createState() => _ChildViewState();
}
class _ChildViewState extends State<ChildView> {
@override
Widget build(BuildContext context) {
final List<String> itemList =
List.generate(100, (index) => "Item ${index + 1}");
return SingleChildScrollView(
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(
top: 16.0,
left: 16,
right: 16,
),
child: TextField(
textInputAction: TextInputAction.search,
),
),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(itemList[index]),
);
},
),
],
),
);
}
}
在滑动离开并返回时,如何始终保留我的ListView的滚动位置?
英文:
I have a List of Tabs in a Tabbar.
However, I have an issue. If you run the sample code below and scroll to item 30 on Tab 1 then swipe to Tab 2 and back to Tab 1 you will immediately notice that item 30 in Tab 1 is no longer focused. The scroller has jumped to item 1 instead. This is weird.
I have tried everything possible to ensure the scroll position of each ListView is preserved irrespective of how many times I swipe away and back to it.
Here is a full runnable sample code showing exactly the issue I'm facing.
import 'package:flutter/material.dart';
class SampleListApp extends StatefulWidget {
const SampleListApp({Key? key}) : super(key: key);
@override
State<SampleListApp> createState() => _SampleListAppState();
}
class _SampleListAppState extends State<SampleListApp>
with TickerProviderStateMixin {
TabController? _tabController;
late ScrollController _scrollViewController;
int _currentTabIndex = 0;
_handleTabChange() {
setState(() {
if (_tabController != null) {
_currentTabIndex = _tabController!.index;
}
});
}
_initTabController() {
_tabController?.dispose();
_tabController =
TabController(initialIndex: _currentTabIndex, vsync: this, length: 2);
_tabController?.addListener(_handleTabChange);
}
@override
void initState() {
super.initState();
_initTabController();
_scrollViewController = ScrollController(initialScrollOffset: 0.0);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
controller: _scrollViewController,
headerSliverBuilder: (BuildContext context, bool boxIsScrolled) => [
SliverAppBar(
title: const Text("Hello World"),
bottom: TabBar(
indicatorColor: Colors.white,
isScrollable: true,
labelColor: Colors.orange,
unselectedLabelColor: Colors.grey,
tabs: const [
Tab(
text: "Tab 1",
),
Tab(
text: "Tab 2",
)
],
controller: _tabController,
),
pinned: true,
floating: true,
forceElevated: boxIsScrolled,
),
],
body: TabBarView(
key: UniqueKey(),
controller: _tabController,
children: const [
ChildView(),
ChildView(),
],
),
),
);
}
}
class ChildView extends StatefulWidget {
const ChildView({Key? key}) : super(key: key);
@override
State<ChildView> createState() => _ChildViewState();
}
class _ChildViewState extends State<ChildView> {
@override
Widget build(BuildContext context) {
final List<String> itemList =
List.generate(100, (index) => "Item ${index + 1}");
return SingleChildScrollView(
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(
top: 16.0,
left: 16,
right: 16,
),
child: TextField(
textInputAction: TextInputAction.search,
),
),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(itemList[index]),
);
},
),
],
),
);
}
}
What can I do to always preserve the scroll positions of my ListViews when swiped away and back
答案1
得分: 1
如果你从TabBarView中移除UniqueKey,并在类似以下方式中添加AutomaticKeepAliveClientMixin混入,TabBarView将不会在你调用setState时重新构建:
class ChildView extends StatefulWidget {
const ChildView({Key? key}) : super(key: key);
@override
State<ChildView> createState() => _ChildViewState();
}
class _ChildViewState extends State<ChildView>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
final List<String> itemList =
List.generate(100, (index) => "Item ${index + 1}");
return SingleChildScrollView(
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(
top: 16.0,
left: 16,
right: 16,
),
child: TextField(
textInputAction: TextInputAction.search,
),
),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(itemList[index]),
);
},
),
],
),
);
}
@override
bool get wantKeepAlive => true;
}
请注意,我只翻译了代码部分,没有其他内容。
英文:
If you remove the UniqueKey from TabBarView, the TabBarView won't rebuild when you setState and add AutomaticKeepAliveClientMixin mixin like this:
class ChildView extends StatefulWidget {
const ChildView({Key? key}) : super(key: key);
@override
State<ChildView> createState() => _ChildViewState();
}
class _ChildViewState extends State<ChildView>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
final List<String> itemList =
List.generate(100, (index) => "Item ${index + 1}");
return SingleChildScrollView(
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(
top: 16.0,
left: 16,
right: 16,
),
child: TextField(
textInputAction: TextInputAction.search,
),
),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(itemList[index]),
);
},
),
],
),
);
}
@override
bool get wantKeepAlive => true;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论