Flutter TabBar与TabBarView联动及获取当前点击栏目索引

TabBar还有TabBarView都是谷歌flutter官方组件库------Material组件库提供的组件,其中TabBar用于导航切换,TabBarView则是配合其切换显示的对应的视图,官网参考地址:TabBarView class - material library - Dart API

实现一体联动有两种实现方式:使用默认控制器(DefaultTabController)和自定义控制器。使用自定义控制器灵活性更高,但是需要指定TabController的length属性,但有些情况下栏目的实际数据是从网络上异步加载读取的,这个TabController的length无法动态更新或后面重新指定,非常扯淡,具体实现参考:flutter 之 TabBar、TabBarView的使用 - 简书

本文主要介绍在使用DefaultTabController下,实现获取点击当前栏目的索引,包括点击TabBar和滑动TabBarView以及视图状态保持,示例如下:

Kotlin 复制代码
  late int _selectIndex = 0;
  late final ScrollController _scrollController;
  late final NewsPageViewModel _vm = NewsPageViewModel();
  late final _scaffoldKey = GlobalKey<_NewsPageViewState>();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    //栏目滑动索引改变
    _scrollController = ScrollController(onDetach: (ScrollPosition position) {
      if (_scaffoldKey.currentContext != null) {
        final controller =
            DefaultTabController.of(_scaffoldKey.currentContext!);
        controller.addListener(() {
          if (controller.index.toDouble() == controller.animation?.value) {
            //loadListDataFor(controller.index);
            debugPrint(controller.index);
          }
        });
      }
    });
  }

@override
  Widget build(BuildContext context) {
    // TODO: implement build
    return BaseView<NewsPageViewModel>(
        viewModel: _vm,
        build: (context, viewModel, child) {
          if (viewModel.state == ViewState.Busy) {
            return BaseView.loadingWidget();
          } else {
            return DefaultTabController(
                initialIndex: _selectIndex,
                length: viewModel.arrCategory.length,
                child: NestedScrollView(
                  controller: _scrollController,
                  headerSliverBuilder:
                      (BuildContext context, bool innerBoxIsScrolled) {
                    return <Widget>[
                      SliverOverlapAbsorber(
                        handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
                            context),
                        sliver: SliverAppBar(
                          title: Utils.shareInstance.customPageTitle('资讯'),
                          //标题横向间距
                          titleSpacing: 15,
                          //左侧的图标或文字,多为返回箭头
                          leading: null,
                          //左边按钮的宽度
                          leadingWidth: 15,
                          //居中
                          centerTitle: false,
                          //标题栏是否固定
                          pinned: true,
                          //滑动时是否悬浮
                          floating: false,
                          //配合floating使用
                          snap: false,
                          //是否显示在状态栏的下面,false就会占领状态栏的高度
                          primary: true,
                          //设置分栏区域上面的高度
                          // expandedHeight: 0,
                          elevation: 0,
                          //是否显示阴影,直接取值innerBoxIsScrolled,展开不显示阴影,合并后会显示
                          forceElevated: innerBoxIsScrolled,
                          //自定义导航和中间内容的展示
                          flexibleSpace: null,
                          //导航栏背景色
                          backgroundColor: K_APP_NAVIGATION_BACKGROUND_COLOR,
                          //TabBar 分栏标题
                          bottom: _setCategoryTabBar(viewModel),
                        ),
                      )
                    ];
                  },
                  //分栏展示的页面信息
                  body: _setCategoryTabBarView(context, viewModel),
                ));
          }
        },
        onModelReady: (viewModel) {
          //加载栏目
          viewModel.categoryLoad(context, isLoading: false);
        });
  }

@override
  void dispose() {
    _scrollController.dispose();

    // TODO: implement dispose
    super.dispose();
  }

//MARK: - 扩展
extension on _NewsPageViewState {
  //分栏菜单
  TabBar _setCategoryTabBar(NewsPageViewModel viewModel) {
    return TabBar(
      key: _scaffoldKey,
      //设置对齐方式(否则左边有空白)
      tabAlignment: TabAlignment.start,
      //是否允许滚动
      isScrollable: true,
      //未选中的颜色
      unselectedLabelColor: Utils.shareInstance.hexToInt(0x505050),
      //未选中的样式
      unselectedLabelStyle: const TextStyle(fontSize: 15),
      //选中的颜色
      labelColor: K_APP_TINT_COLOR,
      //选中的样式
      labelStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
      //底部滑动条样式
      indicatorSize: TabBarIndicatorSize.label,
      //底部滑动条颜色
      indicatorColor: K_APP_TINT_COLOR,
      //菜单项目
      tabs: viewModel.arrCategory.map((item) {
        return Tab(text: item.name);
      }).toList(),
      //点击事件
      onTap: (index) {
        debugPrint(index);
        //loadListDataFor(index);
        setState(() {
          _selectIndex = index;
        });
      },
    );
  }

  // tabBar 分栏菜单各个页面
  Widget _setCategoryTabBarView(
      BuildContext context, NewsPageViewModel viewModel) {
    return TabBarView(
        children: viewModel.arrCategory.map((item) {
      //设置UI
      return SafeArea(
          top: false,
          bottom: false,
          child: Builder(
            builder: (BuildContext context) {
              return CustomScrollView(
                slivers: [
                  SliverOverlapInjector(
                    handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
                        context),
                  ),
                  SliverPadding(
                    padding: EdgeInsets.zero,
                    sliver: SliverFixedExtentList(
                        delegate: SliverChildBuilderDelegate(
                            (BuildContext context, int index) {
                          final m = viewModel.arrList[index];
                          
                          if (m.className == '快讯') {
                            //7x24快讯
                            return NewsPageViewInformationCell(
                                index, m, context, viewModel);
                          }

                          return NewsPageViewCell(index, m, context, viewModel);
                        }, childCount: viewModel.arrList.length),
                        itemExtent: 50.0 //item高度或宽度,取决于滑动方向
                        ),
                  )
                ],
              );
            },
          ));
    }).toList());
  }

}

上面方法进过实际测试,可以实现点击和滑动时获取当前栏目的索引,以便根据索引执行加载当前页面的栏目数据的业务逻辑。随之会产生新的问题就是栏目来回切换没有保持页面的状态,会重复加载数据,解决思路是借助 AutomaticKeepAliveClientMixin 并设置 wantKeepAlive 为true,在PageView和PageController组合中同样适用,效果如下:

86_1720350605

参考Demo,日拱一卒,持续更新

相关推荐
我要洋人死7 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人19 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人19 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR25 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香27 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969330 分钟前
前端预览word、excel、ppt
前端·word·excel
小华同学ai35 分钟前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
problc40 分钟前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
Gavin_91544 分钟前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍