Flutter tabsView 和 NestedScrollView 滚动问题
Posted
技术标签:
【中文标题】Flutter tabsView 和 NestedScrollView 滚动问题【英文标题】:Flutter tabsView and NestedScrollView scroll issue 【发布时间】:2019-07-08 10:22:17 【问题描述】:我有一个“NestedScrollView”,其中包含多个“TabView”小部件,每个选项卡都有一个列表构建器。问题是当我滚动浏览特定选项卡中的一个列表时,滚动位置会影响其他选项卡中的所有其他列表。
即使我确实将“ScrollController”添加到每个列表视图(在选项卡中),选项卡滚动内的 listBuilder 也会与“NestedScrollView”分离 这是一个示例代码:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: MyApp()
,
)
);
class MyApp extends StatefulWidget
MyAppState createState() => MyAppState();
class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin
TabController tabController;
Widget _tabBarView;
@override
void initState()
super.initState();
tabController = TabController(length: 2, vsync: this,);
_tabBarView = TabBarView(
children: [
DemoTab(),
DemoTab(),
]);
@override
Widget build(BuildContext context)
return Scaffold(
body: NestedScrollView(
controller: ScrollController(keepScrollOffset: true),
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled)
return <Widget>[
SliverList(
delegate: SliverChildListDelegate(
[
Container(height: 300, color: Colors.blue)
]
),
),
];
,
body: DefaultTabController(
length: 2,
child: Column(
children: <Widget>[
Expanded(
child: Container(
child: _tabBarView
),
),
],
),
)
),
);
class DemoTab extends StatefulWidget
DemoTabState createState() => DemoTabState();
class DemoTabState extends State<DemoTab> with AutomaticKeepAliveClientMixin<DemoTab>
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context)
return ListView.builder(
key: UniqueKey(),
itemBuilder: (b, i)
return Container(
height: 50,
color: Colors.green,
margin: EdgeInsets.only(bottom: 3),
child: Text(i.toString(),),
);
, itemCount: 30,) ;
【问题讨论】:
【参考方案1】:3天后我发现这是解决这个问题的最佳方法,但还需要改进,因为sliver header扩展和收缩太快,你可以改进代码,分享给我们
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget
MyAppState createState() => MyAppState();
class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin
TabController tabController;
Widget _tabBarView;
var scrollController = ScrollController();
@override
void initState()
super.initState();
tabController = TabController(
length: 2,
vsync: this,
);
_tabBarView = TabBarView(children: [
DemoTab(parentController : scrollController),
DemoTab(parentController : scrollController),
]);
@override
Widget build(BuildContext context)
return Scaffold(
body: NestedScrollView(
controller: scrollController,
physics: ScrollPhysics(parent: PageScrollPhysics()),
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled)
return <Widget>[
SliverList(
delegate: SliverChildListDelegate(
[Container(height: 300, color: Colors.blue)]),
),
];
,
body: DefaultTabController(
length: 2,
child: Column(
children: <Widget>[
Container(
child: TabBar(labelColor: Colors.grey, tabs: [
Tab(
text: 'One',
),
Tab(
text: 'two',
)
]),
),
Expanded(
child: Container(child: _tabBarView),
),
],
),
)),
);
class DemoTab extends StatefulWidget
DemoTab(
this.parentController
);
final ScrollController parentController;
DemoTabState createState() => DemoTabState();
class DemoTabState extends State<DemoTab>
with AutomaticKeepAliveClientMixin<DemoTab>
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
ScrollController _scrollController;
ScrollPhysics ph;
@override
void initState()
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(()
var innerPos = _scrollController.position.pixels;
var maxOuterPos = widget.parentController.position.maxScrollExtent;
var currentOutPos = widget.parentController.position.pixels;
if(innerPos >= 0 && currentOutPos < maxOuterPos)
//print("parent pos " + currentOutPos.toString() + "max parent pos " + maxOuterPos.toString());
widget.parentController.position.jumpTo(innerPos+currentOutPos);
else
var currenParentPos = innerPos + currentOutPos;
widget.parentController.position.jumpTo(currenParentPos);
);
widget.parentController.addListener(()
var currentOutPos = widget.parentController.position.pixels;
if(currentOutPos <= 0)
_scrollController.position.jumpTo(0);
);
@override
Widget build(BuildContext context)
return ListView.builder(
key: UniqueKey(),
controller: _scrollController,
itemBuilder: (b, i)
return Container(
height: 50,
color: Colors.green,
margin: EdgeInsets.only(bottom: 3),
child: Text(
i.toString(),
),
);
,
itemCount: 30,
);
【讨论】:
任何其他解决方案 @gowthamanC 目前正在研究一种解决此问题的新方法,我将在本周末尝试覆盖此答案 如果bottomnavbar也添加到这里怎么办? @LOG_TAG 之间没有关系,但是如果有任何意外行为,请分享您的代码,我很感兴趣 @abdalmonem,你能回答这个问题吗?会有很大的帮助! ***.com/questions/69795503/…【参考方案2】:您可以使用 SliverSafeArea 和 SliverOverlapAbsorber。
class NewsScreen extends StatefulWidget
@override
State<StatefulWidget> createState() => _NewsScreenState();
class _NewsScreenState extends State<NewsScreen>
final List<String> listItems = [];
final List<String> _tabs = <String>[
"Featured",
"Popular",
"Latest",
];
@override
Widget build(BuildContext context)
return Material(
child: Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled)
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: SliverSafeArea(
top: false,
sliver: SliverAppBar(
title: const Text('Books'),
floating: true,
pinned: true,
snap: false,
primary: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
),
];
,
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name)
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is "inside"
// the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
// find the NestedScrollView.
builder: (BuildContext context)
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 60.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index)
// This builder is called for each child.
// In this example, we just number each list item.
return Container(
color: Color((math.Random().nextDouble() *
0xFFFFFF)
.toInt() <<
0)
.withOpacity(1.0));
,
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 30,
),
),
),
],
);
,
),
);
).toList(),
),
),
),
),
);
【讨论】:
谢谢爸爸的帮助以上是关于Flutter tabsView 和 NestedScrollView 滚动问题的主要内容,如果未能解决你的问题,请参考以下文章
如何从 Prism 中的 RequestNavigate 方法获取回调?