Tab tab encapsulation of Flutter control

Tab tab, this is a very common and highly weighted component, just open an App, such as CSDN, as shown in the figure below, there is a Tab tab at the top of the home page, this function can be said to exist in almost every App.

In Android, we can use TabLayout+ViewPager to easily implement a Tab indicator + page sliding, while in Flutter, we can tell you responsibly that it is also very simple to implement, mainly using TabBar and TabBarView , to give a particularly simple example, as shown in the following code, which is the effect of a very simple Tab tab + bottom page.

@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)));
  }

The code effect is as follows:

Is it very simple to implement in Flutter? Since it is already so simple, why do we need to encapsulate another layer? To put it bluntly, one is for expansion, to expand the functions that the system cannot satisfy, and the other is for the convenience of calling. ok, without further ado, let's start today's overview.

Today's content is roughly as follows:

1. List of encapsulation effects

2. Determine the package attributes and extended attributes

3. Source code and specific use

4. Relevant summary

1. List of encapsulation effects

All the effects are realized based on native, as shown in the figure below:

2. Determine the package attributes and extended attributes

Basically, the effect of encapsulation is as shown in the figure above. Which attributes to encapsulate, about system attributes, such as the color of the indicator, the color of selected and unselected labels, etc., can be thrown out for users to selectively use .

The required extended attributes make the custom Tab more flexible and meet different actual needs. For example, text indicators, picture indicators, graphic indicators, etc., can be added flexibly.

The specific attributes are as follows. In the actual package, you can dynamically and flexibly set them according to your own needs.

Attributes

type

overview

tabTitleList

List<String>

A collection of titles for the tab indicator, in text form

tabImageList

List<String>

The title collection of the tab indicator, in the form of pictures

tabWidgetList

List<Widget>

The title collection of the tab indicator, in the form of Widget

tabIconAndTextList

List<TabBarBean>

The title collection of the tab indicator, in the form of the left picture and the right text

tabBodyList

List<Widget>

The page corresponding to the tab indicator

onPageChange

Function(int)

page slide callback

indicatorColor

Color

the color of the indicator

labelColor

Color

label color

unselectedLabelColor

Color

Color of unselected tabs

indicatorSize

TabBarIndicatorSize

Whether the size of the indicator is the same as the text width or fills the

indicatorHeight

double

indicatorHeight

isScrollable

bool

Whether the indicator supports sliding

tabImageWidth

double

The width of the picture indicator is only used for picture indicators and text indicators

tabImageHeight

double

The height of the picture indicator is only used for picture indicators and text indicators

tabIconAndTextMargin

double

The left picture right text indicator, the distance between the icon and the text

tabHeight

double

tab height

3. Source code and specific use

The source code is relatively simple. It only makes a simple package for TabBar and TabBarView, and supports multiple formats of Tab types. Since a Tab controller is required, a stateful StatefulWidget is used here. The source code as a whole is as follows:

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,
      ),
    );
  }
}

easy to use

It can be easily achieved by passing a collection of titles and a collection of pages.

  @override
  Widget build(BuildContext context) {
    return const VipTabBarView(
      tabTitleList:  ["条目一", "条目二"],
      tabBodyList: [
        Text("第一个页面"),//可以是任意的Widget
        Text("第二个页面"),//可以是任意的Widget
      ],
    );
  }

all cases

Corresponding to the encapsulation effect of the first article, you can directly copy and view the effect.

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.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"
    ]; //图片指示器
    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.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,
          ))
    ]);
  }
}

4. Relevant summary

In Flutter, when we use the Tab tab in combination with the bottom page, we must consider lazy loading. Otherwise, when there is a network request, the data will be reloaded every time the page is switched, which is quite good for the user experience. Not good, how to achieve it, you can go to the Internet to search and search, there are a lot of article overviews, so I won’t go into details here, well, friends, this article will come here first, I hope it can help everyone.

Guess you like

Origin blog.csdn.net/ming_147/article/details/130793683
Tab
Tab
Tab
Tab