我正在尝试显示每个主要标签的标签(嵌套标签栏)。
我有一个类(class)页面,该页面以SliverAppBar()
显示该类(class)的信息。每个类(class)都有很多部分,每个部分都有很多考试。
这是我的Build方法:
@override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double statusBarHeight =
MediaQuery.of(context).padding.top + 56; // 56 is height of Appbar.
return Scaffold(
body: Container(
child: DefaultTabController(
length: _sections.length,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(
elevation: 0,
title: Text(
widget._course.shortName +
' ' +
widget._course.code.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
),
),
actionsIconTheme: IconThemeData(color: widget._course.color),
expandedHeight: height / 2,
floating: true,
pinned: true,
centerTitle: true,
titleSpacing: 5,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
tooltip: 'Back',
splashColor: Colors.transparent,
onPressed: () => Navigator.pop(context),
),
backgroundColor: widget._course.color,
flexibleSpace: Container(
padding: EdgeInsets.only(top: statusBarHeight),
child: Text('Course information will be here'),
),
),
SliverPersistentHeader(
floating: false,
delegate: _SliverAppBarDelegate(
TabBar(
indicatorSize: TabBarIndicatorSize.label,
labelPadding: EdgeInsets.symmetric(horizontal: 10),
indicator: CircleTabIndicator(
color: Colors.white,
radius: 2.5,
),
indicatorColor: Colors.white,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
unselectedLabelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w300),
labelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
tabs: List<Widget>.generate(
_sections.length,
(int index) {
return customTab(_sections[index].id);
},
),
),
widget._course.color,
),
pinned: false,
),
];
},
body: Center(
child: getTabBarConten(),
),
),
),
),
);
}
我从互联网上获取
_SliverAppBarDelegate()
类来处理TabBar()
中的NestedScrollView()
,这是代码:class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar, this._color);
TabBar _tabBar;
final Color _color;
@override
double get minExtent => _tabBar.preferredSize.height;
@override
double get maxExtent => _tabBar.preferredSize.height;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
color: _color,
alignment: Alignment.center,
child: _tabBar,
);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
现在,我在Build方法中创建的每个部分标签内容都有许多考试标签。
getTabBarConten()
方法:Widget getTabBarConten() {
return TabBarView(
children: List<Widget>.generate(
_sections.length,
(int index) {
return CustomTabView(
color: widget._course.color,
initPosition: initPosition,
itemCount: _sections[index].exams.length,
tabBuilder: (context, index) => customTab(
_sections[index].exams[index].type.toString(),
isSection: true,
),
pageBuilder: (context, index) => Center(
child: Text(_sections[index].exams[index].supervisor +
' ' +
_sections[index].instructor)),
onPositionChange: (index) {
setState(() {
initPosition = index;
});
},
);
},
),
);
}
此
getTabBarConten()
方法适用于部分标签内容,返回的CustomTabView
返回每次考试的标签。问题是:
RangeError (index): Invalid value: Not in range 0..1, inclusive: 2
在此示例中,类(class)分为2个部分,每个部分有3项考试。因此
itemCount
中的CustomTabView
为3,段的长度为2,这是错误的来源。如果我将
itemCount
设置为相同的部分长度,则可以正常工作(即使itemCount
小于部分的长度):See the image here
但是,如果
itemCount
大于段的长度,则它不起作用!See the error here
为什么会发生此错误,我的意思是它们之间没有关系,在
getTabBarConten()
方法中,部分标签返回TabBarView()
,对于每个标签,它返回CustomTabView()
并返回每次考试的标签。因此,为什么这个错误,谁能帮助我呢?请 :(
最佳答案
感谢chunhunghan,他在question中的回答对我有所帮助。这是另一种方式,但是有效。
[更新:9月]
我将尝试为同一示例编写代码。也许它将帮助某人:)
这里的代码:
import 'package:flutter/material.dart';
class CustomTabView extends StatefulWidget {
final int itemCount;
final IndexedWidgetBuilder tabBuilder;
final IndexedWidgetBuilder pageBuilder;
final Widget stub;
final ValueChanged<int> onPositionChange;
final ValueChanged<double> onScroll;
final int initPosition;
final Color color;
final bool isExamTabs;
final TabController controller;
CustomTabView({
@required this.itemCount,
@required this.tabBuilder,
@required this.pageBuilder,
this.stub,
this.onPositionChange,
this.onScroll,
this.initPosition,
this.color,
this.isExamTabs = false,
this.controller,
});
@override
_CustomTabsState createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabView>
with TickerProviderStateMixin {
TabController controller;
int _currentCount;
int _currentPosition;
@override
void initState() {
if (widget.controller == null) {
_currentPosition = widget.initPosition ?? 0;
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
_currentCount = widget.itemCount;
} else {
controller = widget.controller;
}
super.initState();
}
@override
void didUpdateWidget(CustomTabView oldWidget) {
if (_currentCount != widget.itemCount) {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
if (widget.initPosition != null) {
_currentPosition = widget.initPosition;
}
if (_currentPosition > widget.itemCount - 1) {
_currentPosition = widget.itemCount - 1;
_currentPosition = _currentPosition < 0 ? 0 : _currentPosition;
if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
widget.onPositionChange(_currentPosition);
}
});
}
}
_currentCount = widget.itemCount;
setState(() {
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
});
} else if (widget.initPosition != null) {
controller.animateTo(widget.initPosition);
}
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.itemCount < 1) return widget.stub ?? Container();
double height = MediaQuery.of(context).size.height;
return Container(
height: height - 100,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
color: widget.color,
alignment: Alignment.center,
child: widget.isExamTabs
? TabBar(
controller: controller,
indicatorSize: TabBarIndicatorSize.label,
indicatorWeight: 3.5,
indicatorColor: Colors.white,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
unselectedLabelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w300),
labelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
tabs: List.generate(
widget.itemCount,
(index) => widget.tabBuilder(context, index),
),
)
: TabBar(
controller: controller,
indicatorSize: TabBarIndicatorSize.label,
labelPadding: EdgeInsets.symmetric(horizontal: 10),
indicatorColor: Colors.white,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
unselectedLabelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w300),
labelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
tabs: List.generate(
widget.itemCount,
(index) => widget.tabBuilder(context, index),
),
),
),
Expanded(
child: TabBarView(
controller: controller,
children: List.generate(
widget.itemCount,
(index) => widget.pageBuilder(context, index),
),
),
),
],
),
);
}
onPositionChange() {
if (!controller.indexIsChanging) {
_currentPosition = controller.index;
if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange(_currentPosition);
}
}
}
onScroll() {
if (widget.onScroll is ValueChanged<double>) {
widget.onScroll(controller.animation.value);
}
}
}
我在类(class)页面的正文中称它为:(请参阅代码中的注释)CustomTabView(
initPosition: 0,
itemCount: _course.sections.length,
tabBuilder: (context, index) =>
secionTab(_course.sections[index].id), // Sections tabs
pageBuilder: (context, index) => getSectionTabBarConten(index), // Content for each section. To show exams for "_course.sections[index]" call inside it "CustomTabView()" again for exams. It's mean all Exams per secion.
onPositionChange: (index) {},
// onScroll: (position) => print("POS : " + '$position'),
color: _course.getColor(),
)