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,日拱一卒,持续更新

相关推荐
jian1105813 小时前
Android studio配置flutter,mac Android studio 发现苹果手机设备
android·flutter·android studio
We་ct13 小时前
LeetCode 36. 有效的数独:Set实现哈希表最优解
前端·算法·leetcode·typescript·散列表
ujainu13 小时前
Flutter + OpenHarmony 实战:《圆环跳跃》——完整游戏架构与视觉优化
flutter·游戏·架构·openharmony
2501_9444480013 小时前
Flutter for OpenHarmony衣橱管家App实战:统计分析实现
flutter·信息可视化
爱吃大芒果13 小时前
Flutter for OpenHarmony 实战:mango_shop 路由系统的配置与页面跳转逻辑
开发语言·javascript·flutter
weixin_3954489113 小时前
main.c_cursor_0129
前端·网络·算法
ujainu13 小时前
Flutter + OpenHarmony 实战:从零开发小游戏(一)——主菜单与最高分存储
flutter·游戏·app
2501_9400078913 小时前
Flutter for OpenHarmony三国杀攻略App实战 - 性能优化与最佳实践
android·flutter·性能优化
2501_9400078913 小时前
Flutter for OpenHarmony三国杀攻略App实战 - 战绩记录功能实现
开发语言·javascript·flutter
灰灰勇闯IT13 小时前
Flutter for OpenHarmony:TabBar 与 PageView 联动 —— 构建高效的内容导航系统
flutter