Flutter控件之Tab选项卡封装

简介: Tab选项卡,这是一个非常常见且权重很高的一个组件,随便打开一个App,比如掘金,如下图,首页顶部就是一个Tab选项卡,这个功能可以说,几乎每个App都会存在。

Tab选项卡,这是一个非常常见且权重很高的一个组件,随便打开一个App,比如掘金,如下图,首页顶部就是一个Tab选项卡,这个功能可以说,几乎每个App都会存在。



在Android中,我们可以使用TabLayout+ViewPager,轻松的实现一个Tab指示器+页面滑动,而在Flutter当中呢,可以很负责任的告诉大家,也是很简单的就可以实现,主要使用到了TabBar和TabBarView,举一个特别简单的例子,如下代码所示,就是非常简单的Tab选项卡+底部页面的效果。

@overrideWidgetbuild(BuildContextcontext) {
List<Widget>tabs= []; //tab指示器List<Widget>bodyList= []; //tab指示器下面的内容Widgetfor (inti=0; i<9; i++) {
tabs.add(Tab(text: "条目$i"));
bodyList.add(Text("条目$i"));//内容可以是任意的Widget,比如列表等    }
returnDefaultTabController(
// 标签数量length: tabs.length,
child: Scaffold(
appBar: TabBar(
// 多个标签时滚动加载isScrollable: true,
// 标签指示器的颜色indicatorColor: Colors.red,
// 标签的颜色labelColor: Colors.red,
// 未选中标签的颜色unselectedLabelColor: Colors.black,
// 指示器的大小indicatorSize: TabBarIndicatorSize.label,
// 指示器的权重,即线条高度indicatorWeight: 4.0,
tabs: tabs),
// 标签页所对应的页面body: TabBarView(children: bodyList)));
  }


代码效果如下:



在Flutter当中实现起来是不是也是非常的简单呢,既然已经如此的简单了,为什么我们还要再封装一层呢?说白了一是为了扩展,扩展一下系统无法满足的功能,二是为了调用起来得心应手。ok,废话不多说,开始今天的概述。

今天的内容大概如下:

1、封装效果一览

2、确定封装属性和拓展属性

3、源码和具体使用

4、相关总结

一、封装效果一览


所有的效果都是基于原生而实现的,如下图所示:


二、确定封装属性和拓展属性


基本上封装的效果就如上图所示,要封装哪些属性,关于系统的属性,比如指示器的颜色,标签选中和未选中的颜色等等,都可以抛出去,让使用者选择性进行使用。


而需要的拓展的属性,就使得自定义的Tab更加的灵活,满足不同的实际的需求,比如,文本指示器,图片指示器,图文指示器等等,都可以灵活的添加一下。


具体的属性如下,大家在实际封装中,可以根据自身需要来动态的灵活的设置。


属性

类型

概述

tabTitleList

List<String>

tab指示器的标题集合,文字形式

tabImageList

List<String>

tab指示器的标题集合,图片形式

tabWidgetList

List<Widget>

tab指示器的标题集合,Widget形式

tabIconAndTextList

List<TabBarBean>

tab指示器的标题集合,左图右文形式

tabBodyList

List<Widget>

tab指示器对应的页面

onPageChange

Function(int)

页面滑动回调

indicatorColor

Color

指示器的颜色

labelColor

Color

标签的颜色

unselectedLabelColor

Color

未选中标签的颜色

indicatorSize

TabBarIndicatorSize

指示器的大小 是和文字宽度一样还是充满

indicatorHeight

double

indicatorHeight

isScrollable

bool

指示器是否支持滑动

tabImageWidth

double

图片指示器的宽 仅用于图片指示器和图文指示器

tabImageHeight

double

图片指示器的高 仅用于图片指示器和图文指示器

tabIconAndTextMargin

double

左图右文指示器,icon距离文字的距离

tabHeight

double

tab高度


三、源码和具体使用


源码相对比较的简单,仅仅对TabBar和TabBarView做了简单的封装,支持了多种格式的Tab类型,由于需要Tab控制器,这里使用了有状态的StatefulWidget。源码整体如下:


import'package:flutter/material.dart';
import'package:vip_flutter/ui/widget/vip_text.dart';
///AUTHOR:AbnerMing///DATE:2023/5/18///INTRODUCE:TabBar组件classVipTabBarViewextendsStatefulWidget {
finalList<String>?tabTitleList; //tab指示器的标题集合,文字形式finalList<String>?tabImageList; //tab指示器的标题集合,图片形式finalList<Widget>?tabWidgetList; //tab指示器的标题集合,Widget形式finalList<VipTabBarBean>?tabIconAndTextList; //tab指示器的标题集合,左图右文形式finalList<Widget>?tabBodyList; //tab指示器的页面finalFunction(int)?onPageChange; //页面滑动回调finalColor?indicatorColor; //指示器的颜色finalColor?labelColor; //标签的颜色finalColor?unselectedLabelColor; //未选中标签的颜色finalTabBarIndicatorSize?indicatorSize; //指示器的大小 是和文字宽度一样还是充满finaldouble?indicatorHeight; //指示器的高度finalbool?isScrollable; //指示器是否支持滑动finaldouble?tabImageWidth; //图片指示器的宽 仅用于图片指示器和图文指示器finaldouble?tabImageHeight; //图片指示器的高 仅用于图片指示器和图文指示器finaldouble?tabIconAndTextMargin; //左图右文指示器,icon距离文字的距离finaldouble?tabHeight; //tab高度constVipTabBarView(
      {this.tabTitleList,
this.tabImageList,
this.tabWidgetList,
this.tabIconAndTextList,
this.tabBodyList,
this.onPageChange,
this.indicatorColor=Colors.black,
this.labelColor=Colors.black,
this.unselectedLabelColor=Colors.grey,
this.indicatorSize=TabBarIndicatorSize.tab,
this.indicatorHeight=2,
this.isScrollable=true,
this.tabImageWidth=15,
this.tabImageHeight=15,
this.tabIconAndTextMargin=5,
this.tabHeight=44,
super.key});
@overrideState<VipTabBarView>createState() =>_GWMTabBarViewState();
}
///左图右文的对象classVipTabBarBean {
Stringtitle;
Stringicon;
VipTabBarBean(this.title, this.icon);
}
class_GWMTabBarViewStateextendsState<VipTabBarView>withSingleTickerProviderStateMixin {
// 标签控制器lateTabController_tabController;
@overridevoidinitState() {
super.initState();
// 定义控制器_tabController=TabController(
vsync: this,
length: widget.tabBodyList!=null?widget.tabBodyList!.length : 0,
    );
// 添加监听事件_tabController.addListener(() {
//滑动的索引if (widget.onPageChange!=null&&!_tabController.indexIsChanging) {
widget.onPageChange!(_tabController.index);
      }
    });
  }
@overridevoiddispose() {
super.dispose();
// 杀死控制器_tabController.dispose();
  }
/** 指示器点击*/voidonPage(position) {}
@overrideWidgetbuild(BuildContextcontext) {
List<Widget>tabList= []; //tab指示器List<Widget>bodyList= []; //tab指示器对应的页面//文字形式if (widget.tabTitleList!=null) {
tabList=widget.tabTitleList!          .map((e) =>Tab(
text: e,
height: widget.tabHeight,
              ))
          .toList();
    }
//图片形式if (widget.tabImageList!=null) {
tabList=widget.tabImageList!.map((e) {
Widgetview;
if (e.contains("http")) {
//网络图片view=Image.network(
e,
width: widget.tabImageWidth,
height: widget.tabImageHeight,
          );
        } else {
view=Image.asset(
e,
width: widget.tabImageWidth,
height: widget.tabImageHeight,
          );
        }
returnTab(icon: view, height: widget.tabHeight);
      }).toList();
    }
//自定义Widgetif (widget.tabWidgetList!=null) {
tabList=widget.tabWidgetList!;
    }
//左图右文形式if (widget.tabIconAndTextList!=null) {
tabList=widget.tabIconAndTextList!.map((e) {
returnVipText(
e.title,
leftIcon: e.icon,
height: widget.tabHeight,
leftIconWidth: widget.tabImageWidth,
leftIconHeight: widget.tabImageHeight,
iconMarginRight: widget.tabIconAndTextMargin,
        );
      }).toList();
    }
//指示器对应的页面if (widget.tabBodyList!=null) {
bodyList=widget.tabBodyList!.map((e) =>e).toList();
    }
returnScaffold(
appBar: TabBar(
// 加上控制器controller: _tabController,
tabs: tabList,
// 标签指示器的颜色indicatorColor: widget.indicatorColor,
// 标签的颜色labelColor: widget.labelColor,
// 未选中标签的颜色unselectedLabelColor: widget.unselectedLabelColor,
// 指示器的大小indicatorSize: widget.indicatorSize,
// 指示器的权重,即线条高度indicatorWeight: widget.indicatorHeight!,
// 多个标签时滚动加载isScrollable: widget.isScrollable!,
onTap: onPage,
      ),
body: TabBarView(
// 加上控制器controller: _tabController,
children: bodyList,
      ),
    );
  }
}


简单使用


传一个标题集合和页面集合就可以轻松实现了。


@overrideWidgetbuild(BuildContextcontext) {
returnconstVipTabBarView(
tabTitleList:  ["条目一", "条目二"],
tabBodyList: [
Text("第一个页面"),//可以是任意的WidgetText("第二个页面"),//可以是任意的Widget      ],
    );
  }


所有案例


对应第一条的封装效果,可直接复制查看效果。


import'package:flutter/material.dart';
import'../widget/vip_tab_bar_view.dart';
import'../widget/vip_text.dart';
///AUTHOR:AbnerMing///DATE:2023/5/20///INTRODUCE:TabBar组件效果页面classTabBarPageextendsStatefulWidget {
constTabBarPage({super.key});
@overrideState<TabBarPage>createState() =>_TabBarPageState();
}
class_TabBarPageStateextendsState<TabBarPage> {
@overrideWidgetbuild(BuildContextcontext) {
vartabs= ["条目一", "条目二", "条目三", "条目四", "条目五", "条目六", "条目七", "条目八"];
vartabs2= ["条目一", "条目二", "条目三"];
vartabImages= [
"https://www.vipandroid.cn/ming/pic/new_java.png",
"https://www.vipandroid.cn/ming/pic/new_android.png",
"https://www.vipandroid.cn/ming/pic/new_kotlin.png"    ]; //图片指示器varbodyList=tabs        .map((e) =>VipText(e, backgroundColor: Colors.amberAccent))
        .toList();
varbodyList2=tabs2        .map((e) =>VipText(e, backgroundColor: Colors.amberAccent))
        .toList();
returnColumn(children: [
constVipText("多个Tab滑动",
alignment: Alignment.topLeft,
marginTop: 10,
style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(
height: 80,
child: VipTabBarView(
tabTitleList: tabs,
tabBodyList: bodyList,
onPageChange: ((position) {
//页面滑动监听print(position);
            }),
          )),
constVipText("固定Tab不滑动",
alignment: Alignment.topLeft,
marginTop: 10,
style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(
height: 80,
child: VipTabBarView(
tabTitleList: tabs2,
tabBodyList: bodyList2,
isScrollable: false,
          )),
constVipText("修改指示器颜色",
alignment: Alignment.topLeft,
marginTop: 10,
style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(
height: 80,
child: VipTabBarView(
tabTitleList: tabs2,
tabBodyList: bodyList2,
isScrollable: false,
labelColor: Colors.red,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.red,
          )),
constVipText("修改指示器大小",
alignment: Alignment.topLeft,
marginTop: 10,
style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(
height: 80,
child: VipTabBarView(
tabTitleList: tabs2,
tabBodyList: bodyList2,
isScrollable: false,
labelColor: Colors.red,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.red,
indicatorSize: TabBarIndicatorSize.label,
          )),
constVipText("图片指示器",
alignment: Alignment.topLeft,
marginTop: 10,
style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(
height: 80,
child: VipTabBarView(
tabImageList: tabImages,
tabBodyList: bodyList2,
isScrollable: false,
labelColor: Colors.red,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.red,
indicatorSize: TabBarIndicatorSize.label,
          )),
constVipText("左图右文指示器",
alignment: Alignment.topLeft,
marginTop: 10,
style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(
height: 80,
child: VipTabBarView(
tabIconAndTextList: [
VipTabBarBean(
"Java", "https://www.vipandroid.cn/ming/pic/new_java.png"),
VipTabBarBean("Android",
"https://www.vipandroid.cn/ming/pic/new_android.png"),
VipTabBarBean("Kotlin",
"https://www.vipandroid.cn/ming/pic/new_kotlin.png"),
            ],
tabBodyList: bodyList2,
isScrollable: false,
labelColor: Colors.red,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.red,
indicatorSize: TabBarIndicatorSize.label,
          ))
    ]);
  }
}

四、相关总结


在Flutter中我们使用Tab选项卡和底部的页面结合使用时,一定要考虑懒加载,否则,在有网络请求的时候,每次切换页面的时候,数据都会重新加载,这给用户体验是相当的不好,具体如何实现,大家可以去网上搜索搜索,有大把的文章概述,这里就不赘述了,好了铁子们,本篇文章就先到这里,希望可以帮助到大家。

相关文章
|
3月前
|
Android开发
Flutter控件的显示与隐藏
Flutter控件的显示与隐藏
151 3
|
17天前
|
存储 缓存 Dart
Flutter&鸿蒙next 封装 Dio 网络请求详解:登录身份验证与免登录缓存
本文详细介绍了如何在 Flutter 中使用 Dio 封装网络请求,实现用户登录身份验证及免登录缓存功能。首先在 `pubspec.yaml` 中添加 Dio 和 `shared_preferences` 依赖,然后创建 `NetworkService` 类封装 Dio 的功能,包括请求拦截、响应拦截、Token 存储和登录请求。最后,通过一个登录界面示例展示了如何在实际应用中使用 `NetworkService` 进行身份验证。希望本文能帮助你在 Flutter 中更好地处理网络请求和用户认证。
132 1
|
17天前
|
Dart UED 开发者
Flutter&鸿蒙next中的按钮封装:自定义样式与交互
在Flutter应用开发中,按钮是用户界面的重要组成部分。Flutter提供了多种内置按钮组件,但有时这些样式无法满足特定设计需求。因此,封装一个自定义按钮组件变得尤为重要。自定义按钮组件可以确保应用中所有按钮的一致性、可维护性和可扩展性,同时提供更高的灵活性,支持自定义颜色、形状和点击事件。本文介绍了如何创建一个名为CustomButton的自定义按钮组件,并详细说明了其样式、形状、颜色和点击事件的处理方法。
67 1
|
17天前
|
开发工具 UED
Flutter&鸿蒙next中封装一个输入框组件
本文介绍了如何创建一个简单的Flutter播客应用。首先,通过`flutter create`命令创建项目;接着,在`lib`目录下封装一个自定义输入框组件`CustomInput`;然后,在主应用文件`main.dart`中使用该输入框组件,实现简单的UI布局和功能;最后,通过`flutter run`启动应用。本文还提供了后续扩展建议,如状态管理、网络请求和UI优化。
94 1
|
18天前
|
存储 缓存 JavaScript
Flutter 学习之封装 WebView
【10月更文挑战第24天】通过以上的探讨,我们可以看出,在 Flutter 中封装 WebView 是非常有必要的,它可以提高代码的复用性、增强可维护性、提供统一接口。在实际应用中,我们需要根据具体的需求和场景,选择合适的封装方法和技术,以实现更好的效果。
|
15天前
|
开发工具
Flutter&鸿蒙next中封装一个列表组件
Flutter&鸿蒙next中封装一个列表组件
30 0
|
17天前
|
Dart 安全 UED
Flutter&鸿蒙next中的表单封装:提升开发效率与用户体验
在移动应用开发中,表单是用户与应用交互的重要界面。本文介绍了如何在Flutter中封装表单,以提升开发效率和用户体验。通过代码复用、集中管理和一致性的优势,封装表单组件可以简化开发流程。文章详细讲解了Flutter表单的基础、封装方法和表单验证技巧,帮助开发者构建健壮且用户友好的应用。
58 0
|
4月前
|
JSON Dart API
Flutter dio http 封装指南说明
本文介绍了如何实现一个通用、可重构的 Dio 基础类,包括单例访问、日志记录、常见操作封装以及请求、输出、报错拦截等功能。
110 2
Flutter dio http 封装指南说明
|
3月前
|
开发者 监控 开发工具
如何将JSF应用送上云端?揭秘在Google Cloud Platform上部署JSF应用的神秘步骤
【8月更文挑战第31天】本文详细介绍如何在Google Cloud Platform (GCP) 上部署JavaServer Faces (JSF) 应用。首先,确保已准备好JSF应用并通过Maven构建WAR包。接着,使用Google Cloud SDK登录并配置GCP环境。然后,创建`app.yaml`文件以配置Google App Engine,并使用`gcloud app deploy`命令完成部署。最后,通过`gcloud app browse`访问应用,并利用GCP的监控和日志服务进行管理和故障排查。整个过程简单高效,帮助开发者轻松部署和管理JSF应用。
60 0
|
4月前
flutter 导航组件 AppBar (含顶部选项卡TabBar,抽屉菜单 drawer ,自定义导航图标)
flutter 导航组件 AppBar (含顶部选项卡TabBar,抽屉菜单 drawer ,自定义导航图标)
68 1