我有一个具有多种类型的项目的ListView,其中之一是TabBar和TabBarView的小部件。
问题是每个标签页的高度都不同,我希望ListView根据它是高度
但是TabBarView不接受无限的高度,并且ListView不能为其子代提供高度。
反正可以这样做吗?还是我必须将TabBar与可以包裹其内容(例如列)的东西一起使用,并牺牲在选项卡之间滑动的能力?
答案 0 :(得分:5)
请勿使用TabBarView
,而应将IndexedStack
与Visibility
一起使用。
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
TabController tabController;
int selectedIndex = 0;
@override
void initState() {
super.initState();
tabController = TabController(
initialIndex: selectedIndex,
length: 2,
vsync: this,
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: ListView(
children: [
Container(
height: 128,
color: Colors.blue,
),
Container(
height: 256,
color: Colors.green,
),
TabBar(
tabs: <Tab>[
Tab(text: 'Tab Left'),
Tab(text: 'Tab Right'),
],
controller: tabController,
onTap: (int index) {
setState(() {
selectedIndex = index;
tabController.animateTo(index);
});
},
),
Divider(height: 0),
IndexedStack(
children: <Widget>[
Visibility(
child: Container(
height: 200,
color: Colors.yellow,
child: Center(
child: Text('Content left'),
),
),
maintainState: true,
visible: selectedIndex == 0,
),
Visibility(
child: Container(
height: 1000,
color: Colors.red,
child: Center(
child: Text('Content right'),
),
),
maintainState: true,
visible: selectedIndex == 1,
),
],
index: selectedIndex,
),
],
),
),
);
}
}
IndexedStack
将基于index
显示一个孩子列表中的一个孩子,而Visibility
将保持显示或隐藏视图。隐藏视图时,不会显示多余的空白(堆栈高度等于其子级的最大高度)。
答案 1 :(得分:2)
您无需具有TabView即可显示Tabs内容。 这种方法的缺点是您失去了动画和滑动,因此,如果您确实需要它,则需要自己进行。
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
final List<Widget> myTabs = [
Tab(text: 'one'),
Tab(text: 'two'),
Tab(text: 'three'),
];
TabController _tabController;
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
void initState() {
_tabController = TabController(length: 3, vsync: this);
_tabController.addListener(_handleTabSelection);
super.initState();
}
_handleTabSelection() {
if (_tabController.indexIsChanging) {
setState(() {});
}
}
_listItem() {
return Container(
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.blueAccent,
),
),
height: 120,
child: Center(
child: Text('List Item', style: TextStyle(fontSize: 20.0)),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
_listItem(),
TabBar(
controller: _tabController,
labelColor: Colors.redAccent,
tabs: myTabs,
),
Center(
child: [
Text('first tab'),
Column(
children: [
Text('second tab'),
...List.generate(10, (index) => Text('line: $index'))
],
),
Column(
children: [
Text('third tab'),
...List.generate(20, (index) => Text('line: $index'))
],
),
][_tabController.index],
),
_listItem(),
_listItem(),
],
),
);
}
}
答案 2 :(得分:0)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return HomeState();
}
}
class HomeState extends State<Home> with SingleTickerProviderStateMixin {
TabController tabController;
@override
void initState() {
// TODO: implement initState
super.initState();
tabController = TabController(length: 3, vsync: this, initialIndex: 0);
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: ListView(
children: <Widget>[
DummySection(height: 100.0,color: Colors.red,),
DummySection(height: 150.0,color: Colors.yellow,),
Container(
height: 350.0,
color: Colors.blue,
child: Column(
children: <Widget>[
TabBar(
unselectedLabelColor: Colors.blue[100],
indicator: BoxDecoration(
color: Colors.lightBlue
),
controller: tabController,
tabs: <Widget>[
Tab(text: "Home",),
Tab(text: "Fav",),
Tab(text: "Star",)
],
),
Expanded(
child: TabBarView(
controller: tabController,
children: [
DummyList(),
DummyList(),
DummyList()
]
),
)
],
),
),
DummySection(height: 100.0,color: Colors.red,),
DummySection(height: 100.0,color: Colors.pink,)
],
),
);
}
}
// Dummy List Container
class DummySection extends StatelessWidget{
Color color;
double height;
DummySection({this.color,this.height});
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
color: color,
height: height,
);
}
}
// Dummy Listing for tab
class DummyList extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return ListView(
children: <Widget>[
Card(
child: Container(
height: 200.0,
alignment: Alignment.center,
child: Text("hello"),
),
),
Card(
child: Container(
height: 200.0,
alignment: Alignment.center,
child: Text("hello"),
),
),
],
);
}
}
答案 3 :(得分:0)
这是我使用AnimatedSwitcher
小部件(带有3个标签)在某种程度上模仿标签动画过渡的方法:
TabController _tabController;
ValueNotifier<int> _currentScreen = ValueNotifier<int>(0);
int _previousScreen = 0;
@override
void initState() {
_tabController = new TabController(length: 3, vsync: this);
super.initState();
}
@override
void dispose() {
_currentScreen.dispose();
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CustomScrollBar.Scrollbar(
child: ListView(
physics: BouncingScrollPhysics(),
children: <Widget>[
/// Tabbar
TabBar(
controller: _tabController,
indicatorColor: ColorData.accentColor,
labelColor: Colors.black,
labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w700),
indicatorPadding: EdgeInsets.symmetric(horizontal: 21),
labelPadding: EdgeInsets.all(6),
onTap: (i) {
_previousScreen = _currentScreen.value;
_currentScreen.value = i;
},
tabs: <Widget>[
Text('Screen 1'),
Text('Screen 2'),
Text('Screen 3'),
],
),
/// Tab View
ValueListenableBuilder<int>(
valueListenable: _currentScreen,
builder: (context, screen, child) {
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeIn,
transitionBuilder: (Widget child, Animation<double> animation) {
final inAnimation =
Tween<Offset>(begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0)).animate(animation);
final outAnimation =
Tween<Offset>(begin: Offset(-1.0, 0.0), end: Offset(0.0, 0.0)).animate(animation);
final Widget inTransition = ClipRect(
child: SlideTransition(
position: inAnimation,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: child,
),
),
);
final Widget outTransition = ClipRect(
child: SlideTransition(
position: outAnimation,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: child,
),
),
);
//// Transition for two screens
// if (child.key == ValueKey<int>(1)) {
// print(_previousScreen);
//// if (_previousScreen < screen) {
// return ClipRect(
// child: SlideTransition(
// position: inAnimation,
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: child,
// ),
// ),
// );
// } else {
// return ClipRect(
// child: SlideTransition(
// position: outAnimation,
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: child,
// ),
// ),
// );
// }
//// Transition for three screens
if (child.key == ValueKey<int>(1)) {
if (_previousScreen == 0 || _previousScreen == 1 && screen != 2) return inTransition;
return outTransition;
} else if (child.key == ValueKey<int>(2)) {
return inTransition;
} else {
return outTransition;
}
},
child: _returnTab(screen),
);
}),
],
),
);
}
_returnTab(int screen) {
switch (screen) {
case 0:
return ScreenOne(
key: ValueKey<int>(0),
);
break;
case 1:
return ScreenTwo(
key: ValueKey<int>(1),
);
break;
case 2:
return ScreenThree(
key: ValueKey<int>(2),
);
break;
}
对我很好:)