颤动中的自定义标签栏

Posted

技术标签:

【中文标题】颤动中的自定义标签栏【英文标题】:Custom Tab bar in flutter 【发布时间】:2022-01-23 23:30:39 【问题描述】:

我需要实现一个粘性标签栏。同一个标签栏可以有不同的长度,因为不同的商家可以有不同数量的类别。

我当前的商家页面代码如下:

class _MerchantPageState extends State<MerchantPage>
    with TickerProviderStateMixin 
  ScrollController _scrollController = ScrollController();
  double _scrollPosition = 0;
  late TabController _tabController;
  _scrollListener() 
    setState(() 
      _scrollPosition = _scrollController.position.pixels;
    );
  

  GridContainerData? _products;
  GridContainerData _getProductData() 
    return productGridContainerData;
  

  @override
  void initState() 
    super.initState();
    _products = _getProductData();
    _scrollController.addListener(_scrollListener);
    _tabController = TabController(length: 2, vsync: this);
  

  @override
  Widget build(BuildContext context) 
    return SafeArea(
      child: Scaffold(
        body: DefaultTabController(
          length: 2,
          child: NestedScrollView(
            controller: _scrollController,
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) 
              return [
                MerchantSliverAppbar(
                  merchant: widget.merchant!,
                  tabController: _tabController,
                ),
              ];
            ,
            body: Column(
              children: [
                TabBar(
                  controller: _tabController,
                  unselectedLabelColor: Colors.redAccent,
                  indicatorSize: TabBarIndicatorSize.tab,
                  indicator: BoxDecoration(
                    gradient: LinearGradient(
                      colors: [Colors.redAccent, Colors.orangeAccent],
                    ),
                    borderRadius: BorderRadius.circular(50),
                    color: Colors.redAccent,
                  ),
                  isScrollable: true,
                  tabs: [
                    Tab(
                      child: Align(
                        alignment: Alignment.center,
                        child: Text("Category 1"),
                      ),
                    ),
                    Tab(
                      child: Align(
                        alignment: Alignment.center,
                        child: Text("Category 2"),
                      ),
                    ),
                  ],
                ),
                Expanded(
                  child: TabBarView(
                    controller: _tabController,
                    children: [
                      _buildProductFeed(),
                      _buildProductFeed(),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
        floatingActionButton: ChatButton(),
      ),
    );
  

  Widget _buildProductFeed() 
    return Container(
      padding: EdgeInsets.all(10),
      child: GridContainer(
        data: _products,
        isScrollable: true,
      ),
    );
  

我无法在我的MerchantSliverAppbar 中实现标签栏,因为它有一个背景图像,在MerchantSliverAppbarbottom: 中实现标签栏会将标签栏放在图像顶部。

这是MerchantSliverAppbar的完整代码。

我的一个解决方案是:

            SliverPersistentHeader(
              delegate: _SliverAppBarDelegate(
                TabBar(
                  controller: _tabController,
                  unselectedLabelColor: Colors.redAccent,
                  indicatorSize: TabBarIndicatorSize.tab,
                  indicator: BoxDecoration(
                    gradient: LinearGradient(
                      colors: [Colors.redAccent, Colors.orangeAccent],
                    ),
                    borderRadius: BorderRadius.circular(50),
                    color: Colors.redAccent,
                  ),
                  physics: NeverScrollableScrollPhysics(),
                  isScrollable: true,
                  tabs: [
                    Tab(
                      child: Align(
                        alignment: Alignment.center,
                        child: Text("Category 1"),
                      ),
                    ),
                    Tab(
                      child: Align(
                        alignment: Alignment.center,
                        child: Text("Category 2"),
                      ),
                    ),
                  ],
                ),
              ),
              pinned: true,
            ),

在同一个文件中:

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate 
  _SliverAppBarDelegate(this._tabBar);

  final TabBar _tabBar;

  @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(
      child: _tabBar,
    );
  

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) 
    return false;
  

这个实现的问题是:

    我无法在不同页面中使用相同的应用栏。我需要将其模块化。

我该如何解决这个问题?

【问题讨论】:

【参考方案1】:

事实证明我不需要这些。由于我使用的是名为 MerchantSliverAppbar 的 CustomSliverAppbar,因此我使用 SliverOverlapAbsorber 以下列方式将其包裹起来:

  Widget build(BuildContext context) 
    return SafeArea(
      child: Scaffold(
        backgroundColor: CustomColors.white,
        body: NestedScrollView(
          controller: _scrollController,
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) 
            return [
              SliverOverlapAbsorber(
                handle:
                    NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                sliver: SliverSafeArea(
                  sliver: MerchantSliverAppbar(merchant: widget.merchant!),
                ),
              )
            ];
          ,
          body: FeedSliderContainer(),
        ),
      ),
    );
  

注意:

SliverOverlapAbsorber(
   handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
   sliver: SliverSafeArea(
      sliver: MerchantSliverAppbar(merchant: widget.merchant!),
   ),
)

根据docs:

包裹另一个的条子,强制将其布局范围视为重叠。

其余部分与构建普通的 TabBar 非常相似,只需创建一个新的有状态小部件并放入以下内容:

  List<String> categories = ['All', 'Women', 'Men', 'Kids', 'Gadgets', 'Shoes'];

  late TabController _tabController;


  @override
  void initState() 
    super.initState();
    _tabController = TabController(vsync: this, length: categories.length);
  

  @override
  void dispose() 
    _tabController.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    return Container(
      child: Builder(
        builder: (BuildContext context) 
          _tabController.addListener(() 
            if (!_tabController.indexIsChanging) 
              int index = _tabController.index;
              setState(() );
            
          );
          return Column(
            children: <Widget>[
              getTabBar(context, _tabController),
              Expanded(
                child: Container(
                  child: TabBarView(
                    controller: _tabController,
                    children: [
                      ... data you wanna show
                    ],
                  ),
                ),
              ),
            ],
          );
        ,
      ),
    );
  

  List<Widget> getTabs(TabController tabController) 
    return categories.map((category) 
      return Tab(
        ..customize the tabs
      );
    ).toList();
  

  Widget getTabBar(BuildContext context, TabController tabController) 
    return TabBar(
      tabs: getTabs(tabController),
      labelColor: CustomColors.primary,
      unselectedLabelColor: CustomColors.secondary.withOpacity(0.5),
      indicatorColor: Colors.transparent,
      isScrollable: true,
      padding: const EdgeInsets.all(3),
      controller: tabController,
    );
   

【讨论】:

以上是关于颤动中的自定义标签栏的主要内容,如果未能解决你的问题,请参考以下文章

iOS7标签栏自定义图标高度 - 高度减小直到图标变得不可见

请问asp中的自定义标签是怎么自定义

如何在ios中更改标签栏项目的背景图像?

ecshop按自定义导航栏上面的标签最下面的一排文章标签不居中 偏左

如何在颤动中自定义标签栏宽度?

UITableViewCell 中的自定义标签问题