我正在参与「启航计划」

Tab选项卡,这是一个十分常见且权重很高的一个组件,随便打开一个App,比方,如下图,首页顶部便是一个Tab选项卡,这个功用能够说,几乎每个App都会存在。

Flutter控件之Tab选项卡封装

在Android中,咱们能够运用TabLayout+ViewPager,轻松的完成一个Tab指示器+页面滑动,而在Flutter傍边呢,能够很负责任的告诉咱们,也是很简略的就能够完成,主要运用到了TabBar和TabBarView,举一个特别简略的比方,如下代码所示,便是十分简略的Tab选项卡+底部页面的作用。

@override
  Widget build(BuildContext context) {
    List<Widget> tabs = []; //tab指示器
    List<Widget> bodyList = []; //tab指示器下面的内容Widget
    for (int i = 0; i < 9; i++) {
      tabs.add(Tab(text: "条目$i"));
      bodyList.add(Text("条目$i"));//内容可所以恣意的Widget,比方列表等
    }
    return DefaultTabController(
      // 标签数量
        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控件之Tab选项卡封装

在Flutter傍边完成起来是不是也是十分的简略呢,既然现已如此的简略了,为什么咱们还要再封装一层呢?说白了一是为了扩展,扩展一下体系无法满意的功用,二是为了调用起来得心应手。ok,废话不多说,开始今天的概述。

今天的内容大概如下:

1、封装作用一览

2、确定封装特点和拓宽特点

3、源码和详细运用

4、相关总结

一、封装作用一览

一切的作用都是依据原生而完成的,如下图所示:

Flutter控件之Tab选项卡封装

二、确定封装特点和拓宽特点

基本上封装的作用就如上图所示,要封装哪些特点,关于体系的特点,比方指示器的色彩,标签选中和未选中的色彩等等,都能够抛出去,让运用者选择性进行运用。

而需要的拓宽的特点,就使得自定义的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组件
class VipTabBarView extends StatefulWidget {
  final List<String>? tabTitleList; //tab指示器的标题调集,文字方式
  final List<String>? tabImageList; //tab指示器的标题调集,图片方式
  final List<Widget>? tabWidgetList; //tab指示器的标题调集,Widget方式
  final List<VipTabBarBean>? tabIconAndTextList; //tab指示器的标题调集,左图右文方式
  final List<Widget>? tabBodyList; //tab指示器的页面
  final Function(int)? onPageChange; //页面滑动回调
  final Color? indicatorColor; //指示器的色彩
  final Color? labelColor; //标签的色彩
  final Color? unselectedLabelColor; //未选中标签的色彩
  final TabBarIndicatorSize? indicatorSize; //指示器的巨细 是和文字宽度相同仍是充溢
  final double? indicatorHeight; //指示器的高度
  final bool? isScrollable; //指示器是否支撑滑动
  final double? tabImageWidth; //图片指示器的宽 仅用于图片指示器和图文指示器
  final double? tabImageHeight; //图片指示器的高 仅用于图片指示器和图文指示器
  final double? tabIconAndTextMargin; //左图右文指示器,icon间隔文字的间隔
  final double? tabHeight; //tab高度
  const VipTabBarView(
      {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});
  @override
  State<VipTabBarView> createState() => _GWMTabBarViewState();
}
///左图右文的对象
class VipTabBarBean {
  String title;
  String icon;
  VipTabBarBean(this.title, this.icon);
}
class _GWMTabBarViewState extends State<VipTabBarView>
    with SingleTickerProviderStateMixin {
  // 标签控制器
  late TabController _tabController;
  @override
  void initState() {
    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);
      }
    });
  }
  @override
  void dispose() {
    super.dispose();
    // 杀死控制器
    _tabController.dispose();
  }
  /*
   * 指示器点击
   */
  void onPage(position) {}
  @override
  Widget build(BuildContext context) {
    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) {
        Widget view;
        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,
          );
        }
        return Tab(icon: view, height: widget.tabHeight);
      }).toList();
    }
    //自定义Widget
    if (widget.tabWidgetList != null) {
      tabList = widget.tabWidgetList!;
    }
    //左图右文方式
    if (widget.tabIconAndTextList != null) {
      tabList = widget.tabIconAndTextList!.map((e) {
        return VipText(
          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();
    }
    return Scaffold(
      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,
      ),
    );
  }
}

简略运用

传一个标题调集和页面调集就能够轻松完成了。

  @override
  Widget build(BuildContext context) {
    return const VipTabBarView(
      tabTitleList:  ["条目一", "条目二"],
      tabBodyList: [
        Text("第一个页面"),//可所以恣意的Widget
        Text("第二个页面"),//可所以恣意的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组件作用页面
class TabBarPage extends StatefulWidget {
  const TabBarPage({super.key});
  @override
  State<TabBarPage> createState() => _TabBarPageState();
}
class _TabBarPageState extends State<TabBarPage> {
  @override
  Widget build(BuildContext context) {
    var tabs = ["条目一", "条目二", "条目三", "条目四", "条目五", "条目六", "条目七", "条目八"];
    var tabs2 = ["条目一", "条目二", "条目三"];
    var tabImages = [
      "https://www.6hu.cc/files/2023/05/1684856137-ae6cc97a5bd331d.png",
      "",
      "https://www.6hu.cc/files/2023/05/1684856146-bbd8f7c602ebd0d.png"
    ]; //图片指示器
    var bodyList = tabs
        .map((e) => VipText(e, backgroundColor: Colors.amberAccent))
        .toList();
    var bodyList2 = tabs2
        .map((e) => VipText(e, backgroundColor: Colors.amberAccent))
        .toList();
    return Column(children: [
      const VipText("多个Tab滑动",
          alignment: Alignment.topLeft,
          marginTop: 10,
          style: TextStyle(fontWeight: FontWeight.bold)),
      SizedBox(
          height: 80,
          child: VipTabBarView(
            tabTitleList: tabs,
            tabBodyList: bodyList,
            onPageChange: ((position) {
              //页面滑动监听
              print(position);
            }),
          )),
      const VipText("固定Tab不滑动",
          alignment: Alignment.topLeft,
          marginTop: 10,
          style: TextStyle(fontWeight: FontWeight.bold)),
      SizedBox(
          height: 80,
          child: VipTabBarView(
            tabTitleList: tabs2,
            tabBodyList: bodyList2,
            isScrollable: false,
          )),
      const VipText("修正指示器色彩",
          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,
          )),
      const VipText("修正指示器巨细",
          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,
          )),
      const VipText("图片指示器",
          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,
          )),
      const VipText("左图右文指示器",
          alignment: Alignment.topLeft,
          marginTop: 10,
          style: TextStyle(fontWeight: FontWeight.bold)),
      SizedBox(
          height: 80,
          child: VipTabBarView(
            tabIconAndTextList: [
              VipTabBarBean(
                  "Java", "https://www.6hu.cc/files/2023/05/1684856137-ae6cc97a5bd331d.png"),
              VipTabBarBean("Android",
                  ""),
              VipTabBarBean("Kotlin",
                  "https://www.6hu.cc/files/2023/05/1684856146-bbd8f7c602ebd0d.png"),
            ],
            tabBodyList: bodyList2,
            isScrollable: false,
            labelColor: Colors.red,
            unselectedLabelColor: Colors.black,
            indicatorColor: Colors.red,
            indicatorSize: TabBarIndicatorSize.label,
          ))
    ]);
  }
}

四、相关总结

在Flutter中咱们运用Tab选项卡和底部的页面结合运用时,一定要考虑懒加载,不然,在有网络恳求的时分,每次切换页面的时分,数据都会重新加载,这给用户体验是相当的欠好,详细如何完成,咱们能够去网上查找查找,有大把的文章概述,这儿就不赘述了,好了铁子们,本篇文章就先到这儿,期望能够帮助到咱们。