我正在尝试显示每个主要标签的标签(嵌套标签栏)。
我有一个类(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(),
)

10-08 13:47
查看更多