具有动态容器高度的 TabBarView
Posted
技术标签:
【中文标题】具有动态容器高度的 TabBarView【英文标题】:TabBarView with dynamic Container height 【发布时间】:2019-07-05 15:57:09 【问题描述】:这就是我的布局结构
这是主屏幕
ListView(
children: <Widget>[
_buildCarousel(),
_buildHomeTabBar(),
],
)
这是 HomeTabBar 屏幕
SingleChildScrollView(
child:
DefaultTabController(
length: myTabs.length,
initialIndex: 0,
child: Column(
children: [
TabBar(
isScrollable: true,
tabs: myTabs,
),
Container(
height: 600,
child:
TabBarView(
children: [
ChildScreen(),
ChildScreen2(),
ChildScreen3(),
],
)
)
],
))
);
我想摆脱Container
的高度。我们如何做到这一点?
ChildScreen
正在从 REST 获取数据,它实际上是一个 GridView.Builder
,所以Container
的高度应该是动态的。
抱歉,我错过了 ChildScreen
的布局,实际上是这样的
SingleChildScrollView(
// shrinkWrap: true,
child: Column(
children: <Widget>[
StreamBuilder(
stream: categoryBloc.categoryList,
builder: (context, AsyncSnapshot<List<Category>> snapshot)
if (snapshot.hasData && snapshot!=null)
if(snapshot.data.length > 0)
return buildCategoryList(snapshot);
else if(snapshot.data.length==0)
return Text('No Data');
else if (snapshot.hasError)
return ErrorScreen(errMessage: snapshot.error.toString());
return Center(child: CircularProgressIndicator());
,
),
]
)
);
所以StreamBuilder
内部是GridView.Builder
。
我要删除的主要内容Container
高度。在不同的设备上看起来很难看...
因此,如果我删除高度,它将不会显示在屏幕上并引发错误
I/flutter (493): #2 RenderObject.layout (package:flutter/src/rendering/object.dart:1578:12) I/flutter(493):#3 RenderSliverList.performLayout.advance(包:flutter/src/rendering/sliver_list.dart:200:17) I/flutter (493): #4 RenderSliverList.performLayout (package:flutter/src/rendering/sliver_list.dart:233:19) I/flutter (493): #5 RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7) I/flutter (493): #6 RenderSliverPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:182:11) I/flutter (493): #7 RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7) I/flutter (493): #8 RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:405:13) I/flutter (493): #9 RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1316:12) I/flutter (493): #10 RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1234:20) I/flutter (493): #11 RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7) I/flutter (493): #12 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:13) I/flutter (493): #13 RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
【问题讨论】:
你得到什么错误 - 当你删除容器高度时。 -height: 600,
?
它不会出现在屏幕上。我将更新错误消息。
在 GridView.Builder 中是 shrinkWrap: true,
吗?
@anmol.majhail 是 shrinkWrap: true
在 GridView.Builder 上
这就是我解决类似问题的方法***.com/a/66540036/4150479
【参考方案1】:
我的项目中有类似的任务。问题的根源是您试图将TabBarView
(试图尽可能大的可滚动小部件)放在没有高度的列小部件中。
其中一个解决方案是在此示例中为 ListView
+ Column
去掉包裹 TabBarView
的可滚动小部件。并使用NestedScrollView
代替它们。您需要将_buildCarousel()
和TabBar
放在headerSliverBuilder
部分中,并将TabBarView
放在NestedScrollView
的正文中。
我不知道你的设计看起来如何,但NestedScrollView
默认打开屏幕视图的 100% 高度。因此,如果您想使所有内容都在一个屏幕上,只需在需要时更改 ScrollController
行为即可更轻松地阻止滚动。
在此示例中,我阻止了第三个选项卡上的滚动,但您可以检查网格视图的最后一项的可见性。只需将键添加到最后一项,并检查其在屏幕上的位置。
希望对你有所帮助。
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin
final bodyGlobalKey = GlobalKey();
final List<Widget> myTabs = [
Tab(text: 'auto short'),
Tab(text: 'auto long'),
Tab(text: 'fixed'),
];
TabController _tabController;
ScrollController _scrollController;
bool fixedScroll;
Widget _buildCarousel()
return Stack(
children: <Widget>[
Placeholder(fallbackHeight: 100),
Positioned.fill(child: Align(alignment: Alignment.center, child: Text('Slider'))),
],
);
@override
void initState()
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
_tabController = TabController(length: 3, vsync: this);
_tabController.addListener(_smoothScrollToTop);
super.initState();
@override
void dispose()
_tabController.dispose();
_scrollController.dispose();
super.dispose();
_scrollListener()
if (fixedScroll)
_scrollController.jumpTo(0);
_smoothScrollToTop()
_scrollController.animateTo(
0,
duration: Duration(microseconds: 300),
curve: Curves.ease,
);
setState(()
fixedScroll = _tabController.index == 2;
);
_buildTabContext(int lineCount) => Container(
child: ListView.builder(
physics: const ClampingScrollPhysics(),
itemCount: lineCount,
itemBuilder: (BuildContext context, int index)
return Text('some content');
,
),
);
@override
Widget build(BuildContext context)
return Scaffold(
body: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (context, value)
return [
SliverToBoxAdapter(child: _buildCarousel()),
SliverToBoxAdapter(
child: TabBar(
controller: _tabController,
labelColor: Colors.redAccent,
isScrollable: true,
tabs: myTabs,
),
),
];
,
body: Container(
child: TabBarView(
controller: _tabController,
children: [_buildTabContext(2), _buildTabContext(200), _buildTabContext(2)],
),
),
),
);
.
另一种获得所需内容的方法是创建自定义 TabView
小部件。
【讨论】:
不是OP,但你能展示一个示例代码吗?我仍然被困在这里。 @Kherel Ketchil 如何在将列表滚动到顶部时将标签栏保持在屏幕顶部? 确定我们只需要将 TabBar 从 NestedScrollView 的 headerSliverBuilder 部分移动到正文部分 singlechildscrollview 浪费了大约一个小时......非常感谢 经过 6 天的思考和尝试不同的方法。这解决了我的问题。谢谢【参考方案2】:如果您也遇到 Expanded 错误,请不要使用 TabBarView 创建自定义 TabBarView
-
从 onTap 获取索引,是 TabBar 的属性。
然后在 onTap 函数中使用 if else 来选择要选择的索引并使用索引初始化全局变量。
然后使用 Builder Widgets 来构建您的 TabBarView。再次在 Builder 中,您将使用 if else 来获取选择的索引。然后在 if else 中返回您的小部件。
在这种情况下,您可以获得小部件的最大或最小高度。您无需指定容器高度。
_selectedTabBar 是一个全局变量
Column(children: [
TabBar(
onTap: (index)
print(index);
setState(()
_selectedTabbar = index;
);
,
tabs: [
Tab(
text: 'Tab1',
),
Tab(
text: 'Tab2',
),
Tab(
text: 'Tab3',
),
],
),
Builder(builder: (_)
if (_selectedTabbar == 0)
return Container();//1st custom tabBarView
else if (_selectedTabbar == 1)
return Container();//2nd tabView
else
return Container(); //3rd tabView
),
]
)
【讨论】:
【参考方案3】:1- 您需要将内容添加到列表中
2- 然后将你的 tabBarView 放入一个容器中
3- 假设您的项目具有固定高度,将容器的高度定义为小部件的高度,然后乘以您当前的索引内容或列表长度
例如:height: 90*_items.length.toDouble(),
【讨论】:
【参考方案4】:用Expanded 小部件包装您的列表视图:
使用Expanded
小部件可以使Row
、Column
或Flex
的子级扩展以填充主轴上的可用空间(例如,水平用于行或垂直用于列)。如果展开多个children,则根据flex factor在它们之间分配可用空间。
Expanded(
child: ListView.builder(
itemCount: 120,
itemExtent: 32,
itemBuilder: (BuildContext context, int index)
return new Row(
children: <Widget>[
Icon(Icons.access_alarm),
Text(' Entry $index'),
],
);
,
))
【讨论】:
不工作。抱歉,我错过了 ChildScreen 的布局,我将更新... 这不起作用,因为Expanded
(类似TabBarView
)会尽可能长。【参考方案5】:
工作解决方案: 你需要在两个地方扩展
-
高于 DefaultTabController。
容器中的容器。
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: Colors.grey, width: 0.5))),
child: TabBarView(
children: <Widget>[
Container(),
Container(),
Container(),
])),),
一个是不够的。
【讨论】:
【参考方案6】:我有同样的要求,但需要在底部表格中显示具有动态高度的标签栏,所以我尝试了下面的代码,它对我来说非常好
class BottomSheetScreen extends StatefulWidget
@override
_BottomSheetScreenState createState() => _BottomSheetScreenState();
class _BottomSheetScreenState extends State<BottomSheetScreen>
with SingleTickerProviderStateMixin
late TabController _tabController;
int _selectedTabbar = 0;
@override
Widget build(BuildContext context)
return Center(
child: ElevatedButton(
child: const Text('showModalBottomSheet'),
onPressed: ()
showModalBottomSheet(
context: context,
builder: (BuildContext context)
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState)
return Wrap(
children: [
Column(children: [
TabBar(
controller: _tabController,
onTap: (index)
print(index);
setState(()
_selectedTabbar = index;
);
,
tabs: [
Tab(text: 'Tab1'),
Tab(text: 'Tab2'),
Tab(text: 'Tab3'),
],
),
IntrinsicHeight(
child: Column(
children: [
_selectedTabbar == 0 ? Text("Tab1 content") : _selectedTabbar == 1 ? Text("Tab2 content") : Text("Tab3 content"),
],
),
),
SizedBox(height: 10),
Text("bottom content"),
]),
],
);
);
,
);
,
),
);
【讨论】:
以上是关于具有动态容器高度的 TabBarView的主要内容,如果未能解决你的问题,请参考以下文章